mirror of
https://github.com/antos-rde/antosdk-apps.git
synced 2025-04-05 04:26:45 +02:00
Merge branch 'origin/2.0.x' to master
All checks were successful
gitea-sync/antosdk-apps/pipeline/head This commit looks good
All checks were successful
gitea-sync/antosdk-apps/pipeline/head This commit looks good
This commit is contained in:
commit
23b64bef4a
@ -17,8 +17,10 @@
|
|||||||
"data": ["build","build/debug","build/release"]
|
"data": ["build","build/debug","build/release"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ts-import",
|
"name": "ts-antos-sdk",
|
||||||
"data": ["sdk://core/ts/core.d.ts", "sdk://core/ts/jquery.d.ts","sdk://core/ts/antos.d.ts"]
|
"data": {
|
||||||
|
"version": "2.0.x"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ts-compile",
|
"name": "ts-compile",
|
||||||
|
Binary file not shown.
@ -5,6 +5,8 @@ It is used to show the change logs of the current AntOS version
|
|||||||
|
|
||||||
## Change logs
|
## Change logs
|
||||||
### v0.1.1-b
|
### v0.1.1-b
|
||||||
|
* Increase default window size
|
||||||
|
### v0.1.1-b
|
||||||
* add missing css file
|
* add missing css file
|
||||||
|
|
||||||
### v0.1.0-b
|
### v0.1.0-b
|
||||||
|
@ -1,16 +1,8 @@
|
|||||||
<afx-app-window apptitle="__(About AntOS)" width="450" height="500" data-id="About">
|
<afx-app-window apptitle="__(About AntOS)" width="600" height="500" data-id="About">
|
||||||
<afx-vbox >
|
<afx-vbox padding="10">
|
||||||
<div data-height="10"></div>
|
|
||||||
<afx-hbox data-id="wrapper">
|
|
||||||
<div data-width="10"></div>
|
|
||||||
<div data-id="container"></div>
|
<div data-id="container"></div>
|
||||||
<div data-width="10"></div>
|
<afx-hbox data-height="35" style="text-align: right;">
|
||||||
</afx-hbox>
|
|
||||||
<div data-height="5"></div>
|
|
||||||
<afx-hbox data-height="23" style="text-align: right;">
|
|
||||||
<afx-button data-id="btnclose" text="__(Close)"></afx-button>
|
<afx-button data-id="btnclose" text="__(Close)"></afx-button>
|
||||||
<div data-width="5"></div>
|
|
||||||
</afx-hbox>
|
</afx-hbox>
|
||||||
<div data-height="10"></div>
|
|
||||||
</afx-vbox>
|
</afx-vbox>
|
||||||
</afx-app-window>
|
</afx-app-window>
|
@ -5,6 +5,8 @@ It is used to show the change logs of the current AntOS version
|
|||||||
|
|
||||||
## Change logs
|
## Change logs
|
||||||
### v0.1.1-b
|
### v0.1.1-b
|
||||||
|
* Increase default window size
|
||||||
|
### v0.1.1-b
|
||||||
* add missing css file
|
* add missing css file
|
||||||
|
|
||||||
### v0.1.0-b
|
### v0.1.0-b
|
||||||
|
@ -4,14 +4,12 @@ afx-app-window[data-id = "About"] a:hover
|
|||||||
{
|
{
|
||||||
color:#df3154;
|
color:#df3154;
|
||||||
}
|
}
|
||||||
afx-app-window[data-id = "About"] afx-hbox[data-id="wrapper"]
|
|
||||||
|
afx-app-window[data-id = "About"] div[data-id="container"]
|
||||||
{
|
{
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y:auto;
|
overflow-y:auto;
|
||||||
}
|
/*text-align: justify;*/
|
||||||
afx-app-window[data-id = "About"] div[data-id="container"]
|
|
||||||
{
|
|
||||||
text-align: justify;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
afx-app-window[data-id = "About"] img
|
afx-app-window[data-id = "About"] img
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"author": "Xuan Sang LE",
|
"author": "Xuan Sang LE",
|
||||||
"email": "xsang.le@gmail.com"
|
"email": "xsang.le@gmail.com"
|
||||||
},
|
},
|
||||||
"version":"0.1.1-b",
|
"version":"0.1.2-b",
|
||||||
"category":"Utility",
|
"category":"Utility",
|
||||||
"iconclass":"fa fa-question-circle",
|
"iconclass":"fa fa-question-circle",
|
||||||
"mimes":["none"],
|
"mimes":["none"],
|
||||||
|
@ -1,16 +1,8 @@
|
|||||||
<afx-app-window apptitle="__(About AntOS)" width="450" height="500" data-id="About">
|
<afx-app-window apptitle="__(About AntOS)" width="600" height="500" data-id="About">
|
||||||
<afx-vbox >
|
<afx-vbox padding="10">
|
||||||
<div data-height="10"></div>
|
|
||||||
<afx-hbox data-id="wrapper">
|
|
||||||
<div data-width="10"></div>
|
|
||||||
<div data-id="container"></div>
|
<div data-id="container"></div>
|
||||||
<div data-width="10"></div>
|
<afx-hbox data-height="35" style="text-align: right;">
|
||||||
</afx-hbox>
|
|
||||||
<div data-height="5"></div>
|
|
||||||
<afx-hbox data-height="23" style="text-align: right;">
|
|
||||||
<afx-button data-id="btnclose" text="__(Close)"></afx-button>
|
<afx-button data-id="btnclose" text="__(Close)"></afx-button>
|
||||||
<div data-width="5"></div>
|
|
||||||
</afx-hbox>
|
</afx-hbox>
|
||||||
<div data-height="10"></div>
|
|
||||||
</afx-vbox>
|
</afx-vbox>
|
||||||
</afx-app-window>
|
</afx-app-window>
|
Binary file not shown.
@ -4,14 +4,12 @@ afx-app-window[data-id = "About"] a:hover
|
|||||||
{
|
{
|
||||||
color:#df3154;
|
color:#df3154;
|
||||||
}
|
}
|
||||||
afx-app-window[data-id = "About"] afx-hbox[data-id="wrapper"]
|
|
||||||
|
afx-app-window[data-id = "About"] div[data-id="container"]
|
||||||
{
|
{
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y:auto;
|
overflow-y:auto;
|
||||||
}
|
/*text-align: justify;*/
|
||||||
afx-app-window[data-id = "About"] div[data-id="container"]
|
|
||||||
{
|
|
||||||
text-align: justify;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
afx-app-window[data-id = "About"] img
|
afx-app-window[data-id = "About"] img
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"author": "Xuan Sang LE",
|
"author": "Xuan Sang LE",
|
||||||
"email": "xsang.le@gmail.com"
|
"email": "xsang.le@gmail.com"
|
||||||
},
|
},
|
||||||
"version":"0.1.1-b",
|
"version":"0.1.2-b",
|
||||||
"category":"Utility",
|
"category":"Utility",
|
||||||
"iconclass":"fa fa-question-circle",
|
"iconclass":"fa fa-question-circle",
|
||||||
"mimes":["none"],
|
"mimes":["none"],
|
||||||
|
@ -6,3 +6,6 @@ It's built on top of google-diff-match-patch library. That lib handles the hard
|
|||||||
Github page: [https://github.com/ace-diff/ace-diff](https://github.com/ace-diff/ace-diff).
|
Github page: [https://github.com/ace-diff/ace-diff](https://github.com/ace-diff/ace-diff).
|
||||||
|
|
||||||
The ACE diff depends on the ACECore package.
|
The ACE diff depends on the ACECore package.
|
||||||
|
|
||||||
|
## Change logs
|
||||||
|
- v0.1.1-a: add dependencies
|
Binary file not shown.
@ -3,6 +3,8 @@
|
|||||||
This simple application show the current running AntOS processes
|
This simple application show the current running AntOS processes
|
||||||
|
|
||||||
## Change logs
|
## Change logs
|
||||||
|
### v0.0.8-b
|
||||||
|
* Increase default window size
|
||||||
|
|
||||||
### v0.0.6-a
|
### v0.0.6-a
|
||||||
* Fix process type identification bug
|
* Fix process type identification bug
|
||||||
|
82
ActivityMonitor/build.json
Normal file
82
ActivityMonitor/build.json
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"name": "ActivityMonitor",
|
||||||
|
"targets": {
|
||||||
|
"init": {
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "vfs-mkdir",
|
||||||
|
"data": [
|
||||||
|
"build",
|
||||||
|
"build/debug",
|
||||||
|
"build/release"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"coffee": {
|
||||||
|
"require": [
|
||||||
|
"coffee"
|
||||||
|
],
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "coffee-compile",
|
||||||
|
"data": {
|
||||||
|
"src": [
|
||||||
|
"main.coffee"
|
||||||
|
],
|
||||||
|
"dest": "build/debug/main.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"uglify": {
|
||||||
|
"require": [
|
||||||
|
"terser"
|
||||||
|
],
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "terser-uglify",
|
||||||
|
"data": [
|
||||||
|
"build/debug/main.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"copy": {
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "vfs-cp",
|
||||||
|
"data": {
|
||||||
|
"src": [
|
||||||
|
"scheme.html",
|
||||||
|
"package.json",
|
||||||
|
"README.md",
|
||||||
|
"main.css"
|
||||||
|
],
|
||||||
|
"dest": "build/debug"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"require": [
|
||||||
|
"zip"
|
||||||
|
],
|
||||||
|
"depend": [
|
||||||
|
"init",
|
||||||
|
"coffee",
|
||||||
|
"uglify",
|
||||||
|
"copy"
|
||||||
|
],
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "zip-mk",
|
||||||
|
"data": {
|
||||||
|
"src": "build/debug",
|
||||||
|
"dest": "build/release/ActivityMonitor.zip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,8 @@
|
|||||||
This simple application show the current running AntOS processes
|
This simple application show the current running AntOS processes
|
||||||
|
|
||||||
## Change logs
|
## Change logs
|
||||||
|
### v0.0.8-b
|
||||||
|
* Increase default window size
|
||||||
|
|
||||||
### v0.0.6-a
|
### v0.0.6-a
|
||||||
* Fix process type identification bug
|
* Fix process type identification bug
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"author": "Xuan Sang LE",
|
"author": "Xuan Sang LE",
|
||||||
"email": "xsang.le@gmail.com"
|
"email": "xsang.le@gmail.com"
|
||||||
},
|
},
|
||||||
"version":"0.0.7-b",
|
"version":"0.0.8-b",
|
||||||
"category":"System",
|
"category":"System",
|
||||||
"iconclass":"fa fa-heartbeat",
|
"iconclass":"fa fa-heartbeat",
|
||||||
"mimes":["none"]
|
"mimes":["none"]
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
<afx-app-window data-id = "am-window" apptitle="" width="400" height="300">
|
<afx-app-window data-id = "am-window" apptitle="" width="500" height="400">
|
||||||
<afx-hbox>
|
<afx-vbox padding="7">
|
||||||
<div data-width="7"></div>
|
|
||||||
<afx-vbox>
|
|
||||||
<div data-height="7"></div>
|
|
||||||
<afx-grid-view data-id = "mygrid"></afx-grid-view>
|
<afx-grid-view data-id = "mygrid"></afx-grid-view>
|
||||||
<afx-button data-height="30" data-id = "btkill" text = "__(Kill process)" iconclass="fa fa-times"></afx-button>
|
<afx-button data-height="35" data-id = "btkill" text = "__(Kill process)" iconclass="fa fa-times"></afx-button>
|
||||||
</afx-vbox>
|
</afx-vbox>
|
||||||
<div data-width="7"></div>
|
|
||||||
</afx-hbox>
|
|
||||||
</afx-app-window>
|
</afx-app-window>
|
Binary file not shown.
@ -7,7 +7,7 @@
|
|||||||
"author": "Xuan Sang LE",
|
"author": "Xuan Sang LE",
|
||||||
"email": "xsang.le@gmail.com"
|
"email": "xsang.le@gmail.com"
|
||||||
},
|
},
|
||||||
"version":"0.0.7-b",
|
"version":"0.0.8-b",
|
||||||
"category":"System",
|
"category":"System",
|
||||||
"iconclass":"fa fa-heartbeat",
|
"iconclass":"fa fa-heartbeat",
|
||||||
"mimes":["none"]
|
"mimes":["none"]
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
<afx-app-window data-id = "am-window" apptitle="" width="400" height="300">
|
<afx-app-window data-id = "am-window" apptitle="" width="500" height="400">
|
||||||
<afx-hbox>
|
<afx-vbox padding="7">
|
||||||
<div data-width="7"></div>
|
|
||||||
<afx-vbox>
|
|
||||||
<div data-height="7"></div>
|
|
||||||
<afx-grid-view data-id = "mygrid"></afx-grid-view>
|
<afx-grid-view data-id = "mygrid"></afx-grid-view>
|
||||||
<afx-button data-height="30" data-id = "btkill" text = "__(Kill process)" iconclass="fa fa-times"></afx-button>
|
<afx-button data-height="35" data-id = "btkill" text = "__(Kill process)" iconclass="fa fa-times"></afx-button>
|
||||||
</afx-vbox>
|
</afx-vbox>
|
||||||
<div data-width="7"></div>
|
|
||||||
</afx-hbox>
|
|
||||||
</afx-app-window>
|
</afx-app-window>
|
@ -5,6 +5,9 @@ the editor that powers VS Code.
|
|||||||
The editor functionality can be extended by its extension mechanism.
|
The editor functionality can be extended by its extension mechanism.
|
||||||
Extension can be developed/released/isntalled by the editor itself.
|
Extension can be developed/released/isntalled by the editor itself.
|
||||||
### Change logs
|
### Change logs
|
||||||
|
- 0.2.6-b: Fix resizer bug on new UI API
|
||||||
|
- 0.2.5-b: Fix setting bug with new AntOS setting API
|
||||||
|
- 0.2.3-b: Minor changes to adapt the core UI to the new AntOS 2.0.x
|
||||||
- 0.2.3-b: Allow reload current file via context menu in case of external changes
|
- 0.2.3-b: Allow reload current file via context menu in case of external changes
|
||||||
- 0.2.2-b: Support horizotal scrolling on horizotal tabbars
|
- 0.2.2-b: Support horizotal scrolling on horizotal tabbars
|
||||||
- 0.2.1-b: Add open file to right, editor actions are only attached to code editor
|
- 0.2.1-b: Add open file to right, editor actions are only attached to code editor
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<afx-vbox>
|
<afx-vbox>
|
||||||
<afx-hbox data-id="wrapper">
|
<afx-hbox data-id="wrapper">
|
||||||
<afx-vbox data-width = "200" min-width="200" data-id = "sidebar">
|
<afx-vbox data-width = "200" min-width="200" data-id = "sidebar">
|
||||||
<afx-tab-container data-id="sidebar-tab-container" dir="row" tabbarwidth="30">
|
<afx-tab-container data-id="sidebar-tab-container" dir="row" tabbarwidth="40">
|
||||||
<!--File tab-->
|
<!--File tab-->
|
||||||
<afx-hbox data-height="100%" iconclass="bi bi-files" >
|
<afx-hbox data-height="100%" iconclass="bi bi-files" >
|
||||||
<afx-vbox>
|
<afx-vbox>
|
||||||
@ -15,7 +15,7 @@
|
|||||||
<!--extension tab-->
|
<!--extension tab-->
|
||||||
<afx-hbox data-height="100%" iconclass="bi bi-puzzle" >
|
<afx-hbox data-height="100%" iconclass="bi bi-puzzle" >
|
||||||
<afx-vbox>
|
<afx-vbox>
|
||||||
<input data-id="txt_ext_search" type="text" data-height="23">
|
<input data-id="txt_ext_search" type="text" data-height="30">
|
||||||
<afx-list-view data-id="extension-list"></afx-list-view>
|
<afx-list-view data-id="extension-list"></afx-list-view>
|
||||||
</afx-vbox>
|
</afx-vbox>
|
||||||
</afx-hbox>
|
</afx-hbox>
|
||||||
@ -26,17 +26,17 @@
|
|||||||
<afx-vbox data-id="editor-main-container">
|
<afx-vbox data-id="editor-main-container">
|
||||||
<afx-hbox>
|
<afx-hbox>
|
||||||
<afx-vbox data-id="left-panel">
|
<afx-vbox data-id="left-panel">
|
||||||
<afx-tab-bar closable="true" data-height="26" data-id = "left-tabbar"></afx-tab-bar>
|
<afx-tab-bar closable="true" data-height="35" data-id = "left-tabbar"></afx-tab-bar>
|
||||||
<div data-id="left-editorarea"></div>
|
<div data-id="left-editorarea"></div>
|
||||||
</afx-vbox>
|
</afx-vbox>
|
||||||
<afx-resizer data-width="3"></afx-resizer>
|
<afx-resizer data-width="3"></afx-resizer>
|
||||||
<afx-vbox data-id="right-panel">
|
<afx-vbox data-id="right-panel">
|
||||||
<afx-tab-bar closable="true" data-height="26" data-id = "right-tabbar"></afx-tab-bar>
|
<afx-tab-bar closable="true" data-height="35" data-id = "right-tabbar"></afx-tab-bar>
|
||||||
<div data-id="right-editorarea"></div>
|
<div data-id="right-editorarea"></div>
|
||||||
</afx-vbox>
|
</afx-vbox>
|
||||||
</afx-hbox>
|
</afx-hbox>
|
||||||
<afx-resizer data-height = "3" dir = "ve" attachnext = "true" ></afx-resizer>
|
<afx-resizer data-height = "3" attachnext = "true" ></afx-resizer>
|
||||||
<afx-tab-container data-id = "bottombar" data-height="150" min-height="150" tabbarheight= "22">
|
<afx-tab-container data-id = "bottombar" data-height="150" min-height="150" tabbarheight= "35">
|
||||||
<afx-hbox tabname="__(Output)" iconclass = "fa fa-file-text" class = "bottom-tab-content">
|
<afx-hbox tabname="__(Output)" iconclass = "fa fa-file-text" class = "bottom-tab-content">
|
||||||
<afx-button text = "" data-id="logger-clear" iconclass="fa fa-trash" data-width="21"></afx-button>
|
<afx-button text = "" data-id="logger-clear" iconclass="fa fa-trash" data-width="21"></afx-button>
|
||||||
<div data-id="output-tab" iconclass = "fa fa-file-text" >
|
<div data-id="output-tab" iconclass = "fa fa-file-text" >
|
||||||
|
@ -27,8 +27,10 @@
|
|||||||
"require":["ts"],
|
"require":["ts"],
|
||||||
"jobs": [
|
"jobs": [
|
||||||
{
|
{
|
||||||
"name": "ts-import",
|
"name": "ts-antos-sdk",
|
||||||
"data": ["sdk://core/ts/core.d.ts", "sdk://core/ts/jquery.d.ts","sdk://core/ts/antos.d.ts"]
|
"data": {
|
||||||
|
"version": "2.0.x"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ts-compile",
|
"name": "ts-compile",
|
||||||
|
24
Antedit/build/debug/README.md
Normal file
24
Antedit/build/debug/README.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Antedit
|
||||||
|
Simple yet powerful text/code editor based on the Monaco editor,
|
||||||
|
the editor that powers VS Code.
|
||||||
|
|
||||||
|
The editor functionality can be extended by its extension mechanism.
|
||||||
|
Extension can be developed/released/isntalled by the editor itself.
|
||||||
|
### Change logs
|
||||||
|
- 0.2.6-b: Fix resizer bug on new UI API
|
||||||
|
- 0.2.5-b: Fix setting bug with new AntOS setting API
|
||||||
|
- 0.2.3-b: Minor changes to adapt the core UI to the new AntOS 2.0.x
|
||||||
|
- 0.2.3-b: Allow reload current file via context menu in case of external changes
|
||||||
|
- 0.2.2-b: Support horizotal scrolling on horizotal tabbars
|
||||||
|
- 0.2.1-b: Add open file to right, editor actions are only attached to code editor
|
||||||
|
- 0.2.0-b: Support diff mode in editor + fix new Monaco version compatible bug
|
||||||
|
- 0.1.17-b: Fix extension keybinding bug with the new monaco editor
|
||||||
|
- 0.1.16-b: use the new version of monaco editor
|
||||||
|
- 0.1.14-b: improve output log display
|
||||||
|
- 0.1.13-b: Allow file upload in file view, add menu context in tabbar
|
||||||
|
- 0.1.12-b: fix recent files not adding correctly
|
||||||
|
- 0.1.11-b: fix file type parsing from path
|
||||||
|
- 0.1.10-b: Antedit now has it own extension manager
|
||||||
|
- 0.1.9-a: Allow output text selection
|
||||||
|
- 0.1.8-a: Allow to change language mode
|
||||||
|
- 0.1.7-a: Add keyboard shortcut support to extension actions
|
19
Antedit/build/debug/extensions/EditorExtensionMaker/main.tpl
Normal file
19
Antedit/build/debug/extensions/EditorExtensionMaker/main.tpl
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
(function() {
|
||||||
|
// import the CodePad application module
|
||||||
|
const App = this.OS.application.Antedit;
|
||||||
|
|
||||||
|
// define the extension
|
||||||
|
App.extensions.{0} = class {0} extends App.EditorBaseExtension {
|
||||||
|
constructor(app) {
|
||||||
|
super("{0}",app);
|
||||||
|
}
|
||||||
|
|
||||||
|
test() {
|
||||||
|
return this.notify("Test action is invoked");
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}).call(this);
|
15
Antedit/build/debug/extensions/EditorExtensionMaker/meta.tpl
Normal file
15
Antedit/build/debug/extensions/EditorExtensionMaker/meta.tpl
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"javascripts": ["{0}.js"],
|
||||||
|
"copies": [],
|
||||||
|
"meta": {
|
||||||
|
"name": "{0}",
|
||||||
|
"text": "{0}",
|
||||||
|
"version": "0.0.1-a",
|
||||||
|
"actions" : [
|
||||||
|
{
|
||||||
|
"text": "__(Example action)",
|
||||||
|
"name": "test"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
33
Antedit/build/debug/extensions/extensions.json
Normal file
33
Antedit/build/debug/extensions/extensions.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "EditorExtensionMaker",
|
||||||
|
"text": "Antedit Extension",
|
||||||
|
"version": "0.0.1-a",
|
||||||
|
"actions" : [
|
||||||
|
{
|
||||||
|
"text": "__(New Extension)",
|
||||||
|
"name": "create"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "__(Build)",
|
||||||
|
"name": "build"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "__(Run)",
|
||||||
|
"name": "run"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "__(Build release)",
|
||||||
|
"name": "release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "__(Install from file)",
|
||||||
|
"name": "install"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "__(Install from URL)",
|
||||||
|
"name": "installFromURL"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
175
Antedit/build/debug/main.css
Normal file
175
Antedit/build/debug/main.css
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view > div.list-container
|
||||||
|
{
|
||||||
|
/*border-top: 1px solid #272822;*/
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-size: 12px;
|
||||||
|
scrollbar-width: none;
|
||||||
|
/*scrollbar-color: #656565 transparent;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view > div.list-container::-webkit-scrollbar {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view > div.list-container::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view > div.list-container::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #656565;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view > div.list-container > ul
|
||||||
|
{
|
||||||
|
width: intrinsic;
|
||||||
|
width: -moz-max-content;
|
||||||
|
width: -webkit-max-content;
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view ul afx-list-item:nth-child(even) li.selected,
|
||||||
|
afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view > div.list-container > ul > afx-list-item > li.selected{
|
||||||
|
background-color:#272822;
|
||||||
|
color:white;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view afx-list-view i.closable:before {
|
||||||
|
color:#afafaf;
|
||||||
|
}
|
||||||
|
afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view ul afx-list-item:nth-child(even) li,
|
||||||
|
afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view > div.list-container > ul li{
|
||||||
|
background-color:#333333;
|
||||||
|
color:#afafaf;
|
||||||
|
border-radius: 0;
|
||||||
|
border: 0;
|
||||||
|
border-right: 1px solid #272822;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] afx-tab-bar[data-id="left-tabbar"]> afx-list-view > div.list-container > ul li,
|
||||||
|
afx-app-window[data-id = "antedit"] afx-tab-bar[data-id="right-tabbar"]> afx-list-view > div.list-container > ul li
|
||||||
|
{
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] afx-tab-container[data-id="sidebar-tab-container"] afx-tab-bar> afx-list-view > div.list-container {
|
||||||
|
background-color: #333333;
|
||||||
|
}
|
||||||
|
afx-app-window[data-id = "antedit"] afx-tab-container[data-id="sidebar-tab-container"] afx-tab-bar> afx-list-view > div.list-container > ul li{
|
||||||
|
float: none;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] .afx-window-wrapper afx-vbox[data-id = "sidebar"]{
|
||||||
|
background-color:#272822;
|
||||||
|
}
|
||||||
|
afx-app-window[data-id = "antedit"] div.afx-window-content {
|
||||||
|
background-color:#333333;
|
||||||
|
}
|
||||||
|
afx-app-window[data-id = "antedit"] afx-resizer {
|
||||||
|
background-color:#272822;
|
||||||
|
border-right: 1px solid #656565;
|
||||||
|
border-bottom: 1px solid #656565;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] .bottom-tab-content {
|
||||||
|
background-color:#272822;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] .afx-window-wrapper afx-tree-view{
|
||||||
|
color: white;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
afx-app-window[data-id = "antedit"] .afx-window-wrapper afx-tree-view afx-tree-view-item ul li{
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
afx-app-window[data-id = "antedit"] .afx-window-wrapper .afx_tree_item_selected ul{
|
||||||
|
background-color: #116cd6;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] afx-file-view afx-tree-view .afx-tree-view-item:before{
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] .afx-window-wrapper div[data-id="statctn"]{
|
||||||
|
color: white;
|
||||||
|
background-color: #007acc;
|
||||||
|
padding-right: 10px;
|
||||||
|
padding-top: 5px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] .afx-window-wrapper div[data-id="statctn"] afx-label {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] div[data-id="output-tab"] {
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] div[data-id="output-tab"] pre {
|
||||||
|
margin: 3px;
|
||||||
|
white-space: pre-wrap; /* css-3 */
|
||||||
|
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||||
|
white-space: -pre-wrap; /* Opera 4-6 */
|
||||||
|
white-space: -o-pre-wrap; /* Opera 7 */
|
||||||
|
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] div[data-id="output-tab"] pre.code-pad-log-error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] div[data-id="output-tab"] pre.code-pad-log-warn {
|
||||||
|
color: orange;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] div[data-id="output-tab"] pre.code-pad-log-info {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] afx-button[ data-id="logger-clear" ] button{
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item afx-label i.label-text{
|
||||||
|
font-weight: bold !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
padding-left:15px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item p[data-id="ext-list-item-b-p"] {
|
||||||
|
text-align: right;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item > li {
|
||||||
|
background-color: transparent !important;
|
||||||
|
padding-right: 5px !important;
|
||||||
|
}
|
||||||
|
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item > li.selected {
|
||||||
|
background-color: #116cd6 !important;
|
||||||
|
}
|
||||||
|
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item button {
|
||||||
|
height: 22px;
|
||||||
|
width: 24px;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
afx-app-window[data-id = "antedit"] input[data-id="txt_ext_search"] {
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 0;
|
||||||
|
border-color: #333;
|
||||||
|
color: white;
|
||||||
|
}
|
1
Antedit/build/debug/main.js
Normal file
1
Antedit/build/debug/main.js
Normal file
File diff suppressed because one or more lines are too long
89
Antedit/build/debug/package.json
Normal file
89
Antedit/build/debug/package.json
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"pkgname": "Antedit",
|
||||||
|
"app": "Antedit",
|
||||||
|
"name": "Antos Editor",
|
||||||
|
"description": "Antos text/code editor",
|
||||||
|
"info": {
|
||||||
|
"author": "Xuan Sang LE",
|
||||||
|
"email": "mrsang@iohub.dev"
|
||||||
|
},
|
||||||
|
"version": "0.2.6-b",
|
||||||
|
"category": "Development",
|
||||||
|
"iconclass": "bi bi-journal-code",
|
||||||
|
"mimes": [
|
||||||
|
"text/.*",
|
||||||
|
"[^/]*/json.*",
|
||||||
|
"[^/]*/.*ml",
|
||||||
|
"[^/]*/javascript",
|
||||||
|
"dir"
|
||||||
|
],
|
||||||
|
"dependencies": [
|
||||||
|
"MonacoCore@0.33.0-r"
|
||||||
|
],
|
||||||
|
"locale": {
|
||||||
|
"en_GB": {
|
||||||
|
"Output": "Output",
|
||||||
|
"Close tab": "Close tab",
|
||||||
|
"Close without saving ?": "Close without saving ?",
|
||||||
|
"Unable to open: {0}": "Unable to open: {0}",
|
||||||
|
"Unable to save file: {0}": "Unable to save file: {0}",
|
||||||
|
"Save as": "Save as",
|
||||||
|
"New extension at": "New extension at",
|
||||||
|
"Select extension archive": "Select extension archive",
|
||||||
|
"Current folder is not found": "Current folder is not found",
|
||||||
|
"Select build directory": "Select build directory",
|
||||||
|
"Unable to read meta-data": "Unable to read meta-data",
|
||||||
|
"ExtensionName": "ExtensionName",
|
||||||
|
"Files generated in {0}": "Files generated in {0}",
|
||||||
|
"Unable to build extension:{0}": "Unable to build extension:{0}",
|
||||||
|
"Unable to read meta-data:{0}": "Unable to read meta-data:{0}",
|
||||||
|
"Invalid extension meta-data": "Invalid extension meta-data",
|
||||||
|
"Unable to run extension:{0}": "Unable to run extension:{0}",
|
||||||
|
"Archive created at {0}": "Archive created at {0}",
|
||||||
|
"Unable to read meta-data: {0}": "Unable to read meta-data: {0}",
|
||||||
|
"Extension installed": "Extension installed",
|
||||||
|
"Unable to install extension: {0}": "Unable to install extension: {0}",
|
||||||
|
"Enter URI": "Enter URI",
|
||||||
|
"Please enter extension URI:": "Please enter extension URI:",
|
||||||
|
"Unable to create extension directories: {0}": "Unable to create extension directories: {0}",
|
||||||
|
"New file": "New file",
|
||||||
|
"New folder": "New folder",
|
||||||
|
"Rename": "Rename",
|
||||||
|
"Delete": "Delete",
|
||||||
|
"File name": "File name",
|
||||||
|
"Folder name": "Folder name",
|
||||||
|
"Quit": "Quit",
|
||||||
|
"View": "View",
|
||||||
|
"Toggle bottom bar": "Toggle bottom bar",
|
||||||
|
"Toggle split view": "Toggle split view",
|
||||||
|
"Unable to move file/folder": "Unable to move file/folder",
|
||||||
|
"Editor": "Editor",
|
||||||
|
"Change language mode": "Change language mode",
|
||||||
|
"Select language": "Select language",
|
||||||
|
"Unable to disable split view: Please save changes of modified files on the right panel": "Unable to disable split view: Please save changes of modified files on the right panel",
|
||||||
|
"File": "File",
|
||||||
|
"New": "New",
|
||||||
|
"Open Recent": "Open Recent",
|
||||||
|
"Open": "Open",
|
||||||
|
"Open Folder": "Open Folder",
|
||||||
|
"Save": "Save",
|
||||||
|
"Fail to create: {0}": "Fail to create: {0}",
|
||||||
|
"Fail to rename: {0}": "Fail to rename: {0}",
|
||||||
|
"Fail to delete: {0}": "Fail to delete: {0}",
|
||||||
|
"Open file": "Open file",
|
||||||
|
"Open folder": "Open folder",
|
||||||
|
"Cannot load extension meta data": "Cannot load extension meta data",
|
||||||
|
"unable to load extension: {0}": "unable to load extension: {0}",
|
||||||
|
"Unable to find extension: {0}": "Unable to find extension: {0}",
|
||||||
|
"Unable to find action: {0}": "Unable to find action: {0}",
|
||||||
|
"Unable to preload extension": "Unable to preload extension",
|
||||||
|
"Example action": "Example action",
|
||||||
|
"New Extension": "New Extension",
|
||||||
|
"Build": "Build",
|
||||||
|
"Run": "Run",
|
||||||
|
"Build release": "Build release",
|
||||||
|
"Install extension from file": "Install extension from file",
|
||||||
|
"Install extension from URL": "Install extension from URL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
Antedit/build/debug/scheme.html
Normal file
54
Antedit/build/debug/scheme.html
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<afx-app-window apptitle="Antos Editor" width="600" height="400" data-id="antedit">
|
||||||
|
<afx-vbox>
|
||||||
|
<afx-hbox data-id="wrapper">
|
||||||
|
<afx-vbox data-width = "200" min-width="200" data-id = "sidebar">
|
||||||
|
<afx-tab-container data-id="sidebar-tab-container" dir="row" tabbarwidth="40">
|
||||||
|
<!--File tab-->
|
||||||
|
<afx-hbox data-height="100%" iconclass="bi bi-files" >
|
||||||
|
<afx-vbox>
|
||||||
|
<div data-height="5"></div>
|
||||||
|
<afx-file-view chdir="false" data-id = "fileview" view="tree" status = "false">
|
||||||
|
</afx-file-view>
|
||||||
|
</afx-vbox>
|
||||||
|
</afx-hbox>
|
||||||
|
|
||||||
|
<!--extension tab-->
|
||||||
|
<afx-hbox data-height="100%" iconclass="bi bi-puzzle" >
|
||||||
|
<afx-vbox>
|
||||||
|
<input data-id="txt_ext_search" type="text" data-height="30">
|
||||||
|
<afx-list-view data-id="extension-list"></afx-list-view>
|
||||||
|
</afx-vbox>
|
||||||
|
</afx-hbox>
|
||||||
|
|
||||||
|
</afx-tab-container>
|
||||||
|
</afx-vbox>
|
||||||
|
<afx-resizer data-width = "3" ></afx-resizer>
|
||||||
|
<afx-vbox data-id="editor-main-container">
|
||||||
|
<afx-hbox>
|
||||||
|
<afx-vbox data-id="left-panel">
|
||||||
|
<afx-tab-bar closable="true" data-height="35" data-id = "left-tabbar"></afx-tab-bar>
|
||||||
|
<div data-id="left-editorarea"></div>
|
||||||
|
</afx-vbox>
|
||||||
|
<afx-resizer data-width="3"></afx-resizer>
|
||||||
|
<afx-vbox data-id="right-panel">
|
||||||
|
<afx-tab-bar closable="true" data-height="35" data-id = "right-tabbar"></afx-tab-bar>
|
||||||
|
<div data-id="right-editorarea"></div>
|
||||||
|
</afx-vbox>
|
||||||
|
</afx-hbox>
|
||||||
|
<afx-resizer data-height = "3" attachnext = "true" ></afx-resizer>
|
||||||
|
<afx-tab-container data-id = "bottombar" data-height="150" min-height="150" tabbarheight= "35">
|
||||||
|
<afx-hbox tabname="__(Output)" iconclass = "fa fa-file-text" class = "bottom-tab-content">
|
||||||
|
<afx-button text = "" data-id="logger-clear" iconclass="fa fa-trash" data-width="21"></afx-button>
|
||||||
|
<div data-id="output-tab" iconclass = "fa fa-file-text" >
|
||||||
|
</div>
|
||||||
|
</afx-hbox>
|
||||||
|
</afx-tab-container>
|
||||||
|
</afx-vbox>
|
||||||
|
</afx-hbox>
|
||||||
|
<div data-height="20" data-id="statctn">
|
||||||
|
<afx-label text=" " data-id = "current-file-lbl" style="float:left;"></afx-label>
|
||||||
|
<afx-label data-id="langstat" style="float:right; padding-right: 10px;"></afx-label>
|
||||||
|
<afx-label data-id="editorstat" style="float:right;"></afx-label>
|
||||||
|
</div>
|
||||||
|
</afx-vbox>
|
||||||
|
</afx-app-window>
|
Binary file not shown.
@ -43,12 +43,16 @@ afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view > div.list-conta
|
|||||||
color:#afafaf;
|
color:#afafaf;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
padding-top: 5px;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
padding-right: 20px;
|
|
||||||
border-right: 1px solid #272822;
|
border-right: 1px solid #272822;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id = "antedit"] afx-tab-bar[data-id="left-tabbar"]> afx-list-view > div.list-container > ul li,
|
||||||
|
afx-app-window[data-id = "antedit"] afx-tab-bar[data-id="right-tabbar"]> afx-list-view > div.list-container > ul li
|
||||||
|
{
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
afx-app-window[data-id = "antedit"] afx-tab-container[data-id="sidebar-tab-container"] afx-tab-bar> afx-list-view > div.list-container {
|
afx-app-window[data-id = "antedit"] afx-tab-container[data-id="sidebar-tab-container"] afx-tab-bar> afx-list-view > div.list-container {
|
||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"author": "Xuan Sang LE",
|
"author": "Xuan Sang LE",
|
||||||
"email": "mrsang@iohub.dev"
|
"email": "mrsang@iohub.dev"
|
||||||
},
|
},
|
||||||
"version": "0.2.3-b",
|
"version": "0.2.6-b",
|
||||||
"category": "Development",
|
"category": "Development",
|
||||||
"iconclass": "bi bi-journal-code",
|
"iconclass": "bi bi-journal-code",
|
||||||
"mimes": [
|
"mimes": [
|
||||||
|
@ -59,8 +59,7 @@ namespace OS {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _wr(t: string, d: any): Promise<any> {
|
protected _wr(t: string): Promise<any> {
|
||||||
this.cache = d;
|
|
||||||
return new Promise((resolve, reject) =>
|
return new Promise((resolve, reject) =>
|
||||||
{
|
{
|
||||||
resolve({
|
resolve({
|
||||||
|
@ -561,7 +561,7 @@ namespace OS {
|
|||||||
this.loadExtensionMetaData();
|
this.loadExtensionMetaData();
|
||||||
this.toggleSideBar();
|
this.toggleSideBar();
|
||||||
this.toggleSplitMode();
|
this.toggleSplitMode();
|
||||||
this.applyAllSetting();
|
//this.applyAllSetting();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -718,7 +718,7 @@ namespace OS {
|
|||||||
|
|
||||||
showOutput(toggle: boolean = false): void {
|
showOutput(toggle: boolean = false): void {
|
||||||
if (toggle)
|
if (toggle)
|
||||||
this.showBottomBar(true);
|
this.setting.showBottomBar = true;
|
||||||
this.bottombar.selectedIndex = 0;
|
this.bottombar.selectedIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -749,7 +749,6 @@ namespace OS {
|
|||||||
* @memberof Antedit
|
* @memberof Antedit
|
||||||
*/
|
*/
|
||||||
public showBottomBar(v: boolean): void {
|
public showBottomBar(v: boolean): void {
|
||||||
this.setting.showBottomBar = v;
|
|
||||||
if (v) {
|
if (v) {
|
||||||
$(this.bottombar).show();
|
$(this.bottombar).show();
|
||||||
}
|
}
|
||||||
@ -765,7 +764,7 @@ namespace OS {
|
|||||||
* @memberof Antedit
|
* @memberof Antedit
|
||||||
*/
|
*/
|
||||||
private toggleBottomBar(): void {
|
private toggleBottomBar(): void {
|
||||||
this.showBottomBar(!this.setting.showBottomBar);
|
this.setting.showBottomBar = !this.setting.showBottomBar;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -817,7 +816,7 @@ namespace OS {
|
|||||||
dataid: "recent",
|
dataid: "recent",
|
||||||
nodes: recent,
|
nodes: recent,
|
||||||
onchildselect: (
|
onchildselect: (
|
||||||
e: GUI.TagEventType<GUI.tag.MenuEventData>,
|
e: GUI.TagEventType<GUI.tag.StackMenuEventData>,
|
||||||
r: Antedit
|
r: Antedit
|
||||||
) => {
|
) => {
|
||||||
const handle = e.data.item.data.text.asFileHandle();
|
const handle = e.data.item.data.text.asFileHandle();
|
||||||
@ -849,7 +848,7 @@ namespace OS {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
onchildselect: (
|
onchildselect: (
|
||||||
e: GUI.TagEventType<GUI.tag.MenuEventData>,
|
e: GUI.TagEventType<GUI.tag.StackMenuEventData>,
|
||||||
r: Antedit
|
r: Antedit
|
||||||
) => {
|
) => {
|
||||||
return this.menuAction(e.data.item.data.dataid, r);
|
return this.menuAction(e.data.item.data.dataid, r);
|
||||||
@ -866,9 +865,9 @@ namespace OS {
|
|||||||
* @memberof Antedit
|
* @memberof Antedit
|
||||||
*/
|
*/
|
||||||
private ctxFileMenuHandle(
|
private ctxFileMenuHandle(
|
||||||
e: GUI.TagEventType<GUI.tag.MenuEventData>
|
e: GUI.TagEventType<GUI.tag.StackMenuEventData>
|
||||||
): void {
|
): void {
|
||||||
const el = e.data.item as GUI.tag.MenuEntryTag;
|
const el = e.data.item;
|
||||||
if (!el) {
|
if (!el) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1150,7 +1149,7 @@ namespace OS {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
onchildselect: (
|
onchildselect: (
|
||||||
e: GUI.TagEventType<GUI.tag.MenuEventData>,
|
e: GUI.TagEventType<GUI.tag.StackMenuEventData>,
|
||||||
r: EditorFileHandle
|
r: EditorFileHandle
|
||||||
) => {
|
) => {
|
||||||
switch (e.data.item.data.dataid) {
|
switch (e.data.item.data.dataid) {
|
||||||
|
Binary file not shown.
@ -17,8 +17,10 @@
|
|||||||
"data": ["build","build/debug","build/release"]
|
"data": ["build","build/debug","build/release"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ts-import",
|
"name": "ts-antos-sdk",
|
||||||
"data": ["sdk://core/ts/core.d.ts", "sdk://core/ts/jquery.d.ts","sdk://core/ts/antos.d.ts"]
|
"data": {
|
||||||
|
"version": "2.0.x"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ts-compile",
|
"name": "ts-compile",
|
||||||
|
Binary file not shown.
@ -9,7 +9,10 @@ Small application for zip file manager
|
|||||||
|
|
||||||
## Changle log
|
## Changle log
|
||||||
|
|
||||||
### v0.0.2-a
|
### v0.0.4-a
|
||||||
|
* Fix file dialog bug when extract zip content
|
||||||
|
|
||||||
|
### v0.0.3-a
|
||||||
* Change category to utility
|
* Change category to utility
|
||||||
|
|
||||||
### v0.0.2-a
|
### v0.0.2-a
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
<afx-app-window apptitle="Archive" width="250" height="300" data-id="Archive">
|
<afx-app-window apptitle="Archive" width="500" height="400" data-id="Archive">
|
||||||
<afx-vbox>
|
<afx-vbox>
|
||||||
<afx-tree-view data-id="filetree"></afx-tree-view>
|
<afx-tree-view data-id="filetree"></afx-tree-view>
|
||||||
<div data-height="5"></div>
|
<afx-hbox data-height="55" padding="10">
|
||||||
<afx-hbox data-height="30">
|
|
||||||
<div data-width="10"></div>
|
|
||||||
<div style="text-align: left;">
|
<div style="text-align: left;">
|
||||||
<afx-button iconclass="fa fa-plus-circle" data-id="btaradd"></afx-button>
|
<afx-button iconclass="fa fa-plus-circle" data-id="btaradd"></afx-button>
|
||||||
<afx-button iconclass="fa fa-minus-circle" data-id="btardel"></afx-button>
|
<afx-button iconclass="fa fa-minus-circle" data-id="btardel"></afx-button>
|
||||||
@ -11,7 +9,6 @@
|
|||||||
<div style="text-align: right;">
|
<div style="text-align: right;">
|
||||||
<afx-button text="__(Extract)" data-id="btarxtract"></afx-button>
|
<afx-button text="__(Extract)" data-id="btarxtract"></afx-button>
|
||||||
</div>
|
</div>
|
||||||
<div data-width="10"></div>
|
|
||||||
</afx-hbox>
|
</afx-hbox>
|
||||||
</afx-vbox>
|
</afx-vbox>
|
||||||
</afx-app-window>
|
</afx-app-window>
|
82
Archive/build.json
Normal file
82
Archive/build.json
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"name": "Archive",
|
||||||
|
"targets": {
|
||||||
|
"init": {
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "vfs-mkdir",
|
||||||
|
"data": [
|
||||||
|
"build",
|
||||||
|
"build/debug",
|
||||||
|
"build/release"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"coffee": {
|
||||||
|
"require": [
|
||||||
|
"coffee"
|
||||||
|
],
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "coffee-compile",
|
||||||
|
"data": {
|
||||||
|
"src": [
|
||||||
|
"coffees/main.coffee"
|
||||||
|
],
|
||||||
|
"dest": "build/debug/main.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"uglify": {
|
||||||
|
"require": [
|
||||||
|
"terser"
|
||||||
|
],
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "terser-uglify",
|
||||||
|
"data": [
|
||||||
|
"build/debug/main.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"copy": {
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "vfs-cp",
|
||||||
|
"data": {
|
||||||
|
"src": [
|
||||||
|
"assets/scheme.html",
|
||||||
|
"package.json",
|
||||||
|
"README.md",
|
||||||
|
"css/main.css"
|
||||||
|
],
|
||||||
|
"dest": "build/debug"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"require": [
|
||||||
|
"zip"
|
||||||
|
],
|
||||||
|
"depend": [
|
||||||
|
"init",
|
||||||
|
"coffee",
|
||||||
|
"uglify",
|
||||||
|
"copy"
|
||||||
|
],
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "zip-mk",
|
||||||
|
"data": {
|
||||||
|
"src": "build/debug",
|
||||||
|
"dest": "build/release/Archive.zip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,10 @@ Small application for zip file manager
|
|||||||
|
|
||||||
## Changle log
|
## Changle log
|
||||||
|
|
||||||
### v0.0.2-a
|
### v0.0.4-a
|
||||||
|
* Fix file dialog bug when extract zip content
|
||||||
|
|
||||||
|
### v0.0.3-a
|
||||||
* Change category to utility
|
* Change category to utility
|
||||||
|
|
||||||
### v0.0.2-a
|
### v0.0.2-a
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
afx-app-window[data-id="Archive"] afx-tree-view .afx-tree-view-folder-close:before{
|
afx-app-window[data-id="Archive"] afx-tree-view .afx-tree-view-folder-close:before{
|
||||||
content: "\f07b";
|
content: "\f07b";
|
||||||
font-family: "FontAwesome";
|
font-family: "FontAwesome";
|
||||||
|
File diff suppressed because one or more lines are too long
@ -7,7 +7,7 @@
|
|||||||
"author": "Xuan Sang LE",
|
"author": "Xuan Sang LE",
|
||||||
"email": "mrsang@lxsang.me"
|
"email": "mrsang@lxsang.me"
|
||||||
},
|
},
|
||||||
"version":"0.0.3-a",
|
"version":"0.0.4-a",
|
||||||
"category":"Utility",
|
"category":"Utility",
|
||||||
"iconclass":"fa fa-archive",
|
"iconclass":"fa fa-archive",
|
||||||
"mimes":["application/zip"],
|
"mimes":["application/zip"],
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
<afx-app-window apptitle="Archive" width="250" height="300" data-id="Archive">
|
<afx-app-window apptitle="Archive" width="500" height="400" data-id="Archive">
|
||||||
<afx-vbox>
|
<afx-vbox>
|
||||||
<afx-tree-view data-id="filetree"></afx-tree-view>
|
<afx-tree-view data-id="filetree"></afx-tree-view>
|
||||||
<div data-height="5"></div>
|
<afx-hbox data-height="55" padding="10">
|
||||||
<afx-hbox data-height="30">
|
|
||||||
<div data-width="10"></div>
|
|
||||||
<div style="text-align: left;">
|
<div style="text-align: left;">
|
||||||
<afx-button iconclass="fa fa-plus-circle" data-id="btaradd"></afx-button>
|
<afx-button iconclass="fa fa-plus-circle" data-id="btaradd"></afx-button>
|
||||||
<afx-button iconclass="fa fa-minus-circle" data-id="btardel"></afx-button>
|
<afx-button iconclass="fa fa-minus-circle" data-id="btardel"></afx-button>
|
||||||
@ -11,7 +9,6 @@
|
|||||||
<div style="text-align: right;">
|
<div style="text-align: right;">
|
||||||
<afx-button text="__(Extract)" data-id="btarxtract"></afx-button>
|
<afx-button text="__(Extract)" data-id="btarxtract"></afx-button>
|
||||||
</div>
|
</div>
|
||||||
<div data-width="10"></div>
|
|
||||||
</afx-hbox>
|
</afx-hbox>
|
||||||
</afx-vbox>
|
</afx-vbox>
|
||||||
</afx-app-window>
|
</afx-app-window>
|
Binary file not shown.
@ -20,7 +20,7 @@ class Archive extends this.OS.application.BaseApplication
|
|||||||
item = @filetree.selectedItem
|
item = @filetree.selectedItem
|
||||||
return @notify __("Please select file/folder to extract") unless item
|
return @notify __("Please select file/folder to extract") unless item
|
||||||
treedata = item.data
|
treedata = item.data
|
||||||
@openDialog "FileDialog", { title: __("Select a folder"), mimes: ["dir"] }
|
@openDialog "FileDialog", { title: __("Select a folder"), type: "dir" }
|
||||||
.then (d) =>
|
.then (d) =>
|
||||||
@xtract(treedata, d.file.path)
|
@xtract(treedata, d.file.path)
|
||||||
.then () => @notify __("extract successful: {0}", treedata.path)
|
.then () => @notify __("extract successful: {0}", treedata.path)
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"author": "Xuan Sang LE",
|
"author": "Xuan Sang LE",
|
||||||
"email": "mrsang@lxsang.me"
|
"email": "mrsang@lxsang.me"
|
||||||
},
|
},
|
||||||
"version":"0.0.3-a",
|
"version":"0.0.4-a",
|
||||||
"category":"Utility",
|
"category":"Utility",
|
||||||
"iconclass":"fa fa-archive",
|
"iconclass":"fa fa-archive",
|
||||||
"mimes":["application/zip"],
|
"mimes":["application/zip"],
|
||||||
|
27
Blogger/README.md
Normal file
27
Blogger/README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Blogger
|
||||||
|
|
||||||
|
Blackend for my blog at https://blog.iohub.dev
|
||||||
|
|
||||||
|
|
||||||
|
## Change logs
|
||||||
|
|
||||||
|
### v0.2.x-a
|
||||||
|
* Patch 13: fix bug on blog save
|
||||||
|
* Patch 12: support send mail via SSL
|
||||||
|
* Patch 11: Add TFIDF analyse functionality
|
||||||
|
* Patch 10: Migrate code to typescript, use SQLiteDB lib for database access
|
||||||
|
* Patch 9: Update to use the new MDE library
|
||||||
|
* Patch 8: Support for antOS 2.0.x
|
||||||
|
* Patch 7: Fix sendmail API security bug
|
||||||
|
* Patch 6: Chage libraries load order
|
||||||
|
* Patch 5: Add user photo to portfolio
|
||||||
|
* Patch 4: Add package dependencies
|
||||||
|
* Patch 3: Correct JSON text decoding
|
||||||
|
* Patch 2: Bug fix rendering content
|
||||||
|
* Patch 0-1 Important change: Store raw post content to the database instead of base64 string as before
|
||||||
|
|
||||||
|
### v0.1.x-a
|
||||||
|
* Patch 3-4: Enhance youtube video embedding feature in markdown
|
||||||
|
* Patch 2: CV Category now can be created when database is not created yet
|
||||||
|
* Patch 1: Fix package archive broken
|
||||||
|
* Patch 0: Change default email of the sender
|
83
Blogger/api/ai/analyse.lua
Normal file
83
Blogger/api/ai/analyse.lua
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
local args = ...
|
||||||
|
|
||||||
|
local ret = {
|
||||||
|
error = false,
|
||||||
|
result = nil
|
||||||
|
}
|
||||||
|
local __dir__ = debug.getinfo(1).source:match("@?(.*/)")
|
||||||
|
LOG_DEBUG("CURRENT PATH:%s", __dir__)
|
||||||
|
local cluster = loadfile(__dir__.."/cluster.lua")()
|
||||||
|
local dbpath = require("vfs").ospath(args.dbpath)
|
||||||
|
LOG_DEBUG("DB PATH:%s", dbpath)
|
||||||
|
|
||||||
|
local gettext = {}
|
||||||
|
gettext.get = function(file)
|
||||||
|
local db = DBModel:new{db=file}
|
||||||
|
db:open()
|
||||||
|
if not db then return nil end
|
||||||
|
local data, sort = db:find("blogs", {
|
||||||
|
where = { publish = 1 },
|
||||||
|
fields = {"id", "content"}
|
||||||
|
})
|
||||||
|
db:close()
|
||||||
|
if not data or #data == 0 then return nil end
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
gettext.stopwords = function(ospath)
|
||||||
|
local words = {}
|
||||||
|
for line in io.lines(ospath) do
|
||||||
|
words[line] = true
|
||||||
|
end
|
||||||
|
return words
|
||||||
|
end
|
||||||
|
|
||||||
|
local data = gettext.get(dbpath)
|
||||||
|
local documents = {}
|
||||||
|
if data then
|
||||||
|
local sw = gettext.stopwords(__dir__.."/stopwords.txt")
|
||||||
|
for k, v in pairs(data) do
|
||||||
|
local bag = cluster.bow(data[k].content, sw)
|
||||||
|
documents[data[k].id] = bag
|
||||||
|
end
|
||||||
|
|
||||||
|
cluster.tfidf(documents)
|
||||||
|
-- indexing all terms to cache file
|
||||||
|
local cache_file = dbpath..".index.json"
|
||||||
|
local f = io.open(cache_file, "w")
|
||||||
|
if f then
|
||||||
|
local indexes = {}
|
||||||
|
for id, doc in pairs(documents) do
|
||||||
|
for term,v in pairs(doc) do
|
||||||
|
if not indexes[term] then
|
||||||
|
indexes[term] = {}
|
||||||
|
end
|
||||||
|
indexes[term][tostring(id)] = doc[term].tfidf
|
||||||
|
end
|
||||||
|
end
|
||||||
|
f:write(JSON.encode(indexes))
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
--
|
||||||
|
--local v = cluster.search("arm", documents)
|
||||||
|
--echo(JSON.encode(v))
|
||||||
|
local vectors, maxv, size = cluster.get_vectors(documents)
|
||||||
|
local analytical = DBModel:new{db=dbpath}
|
||||||
|
analytical:open()
|
||||||
|
-- purge the table
|
||||||
|
analytical:delete("st_similarity", nil)
|
||||||
|
-- get similarity and put to the table
|
||||||
|
for id, v in pairs(vectors) do
|
||||||
|
local top = cluster.top_similarity(id, vectors, args.top, 0.1)
|
||||||
|
for a, b in pairs(top) do
|
||||||
|
local record = {pid = id, sid = a, score = b}
|
||||||
|
analytical:insert("st_similarity", record)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
analytical:close()
|
||||||
|
ret.result = "Analyse complete"
|
||||||
|
else
|
||||||
|
ret.error = "Unable to query database for post"
|
||||||
|
end
|
||||||
|
|
||||||
|
return ret
|
346
Blogger/api/ai/cluster.lua
Normal file
346
Blogger/api/ai/cluster.lua
Normal file
@ -0,0 +1,346 @@
|
|||||||
|
local doclassify = {}
|
||||||
|
local st = require("stmr")
|
||||||
|
doclassify.bow = function(data, stopwords)
|
||||||
|
-- first step get a table of worlds that contain
|
||||||
|
-- world: occurences
|
||||||
|
local bag = {}
|
||||||
|
for w in data:gmatch('%w+') do
|
||||||
|
local word = w:lower()
|
||||||
|
if not stopwords[word] then
|
||||||
|
word = st.stmr(word)
|
||||||
|
if bag[word] then
|
||||||
|
bag[word].count = bag[word].count + 1
|
||||||
|
else
|
||||||
|
bag[word] = {count=0, tf=0, tfidf=0.0}
|
||||||
|
bag[word].count = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- now calculate the tf of the bag
|
||||||
|
for k,v in pairs(bag) do
|
||||||
|
bag[k].tf = math.log(1 + bag[k].count)
|
||||||
|
end
|
||||||
|
return bag
|
||||||
|
end
|
||||||
|
doclassify.len = function(table)
|
||||||
|
local cnt = 0
|
||||||
|
for k,v in pairs(table) do cnt = cnt+1 end
|
||||||
|
return cnt
|
||||||
|
end
|
||||||
|
doclassify.tfidf = function(documents)
|
||||||
|
-- now for each term in a bag, calculate
|
||||||
|
-- the inverse document frequency, which
|
||||||
|
-- is a measure of how much information
|
||||||
|
-- the word provides, that is, whether the
|
||||||
|
-- term is common or rare across all documents
|
||||||
|
local ndoc = doclassify.len(documents)
|
||||||
|
for k,bag in pairs(documents) do
|
||||||
|
-- for eacht term in bag
|
||||||
|
-- calculate its idf across all documents
|
||||||
|
for term,b in pairs(bag) do
|
||||||
|
local n = 0
|
||||||
|
for id,doc in pairs(documents) do
|
||||||
|
if doc[term] then n = n+1 end
|
||||||
|
end
|
||||||
|
--echo("term:"..term.." appears in"..n.." documents")
|
||||||
|
b.tfidf = b.tf*math.log(ndoc/n)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.search = function(term, documents)
|
||||||
|
local r = {}
|
||||||
|
for id, doc in pairs(documents) do
|
||||||
|
if doc[term:lower()] then
|
||||||
|
r[id] = doc[term].tfidf
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.get_vectors = function(documents)
|
||||||
|
-- get a list of vector from documents
|
||||||
|
local index = 0
|
||||||
|
local vectors = {}
|
||||||
|
local maps = {}
|
||||||
|
local terms = {}
|
||||||
|
local maxv = 0
|
||||||
|
|
||||||
|
for id in pairs(documents) do
|
||||||
|
maps[id] = {}
|
||||||
|
vectors[id] = {}
|
||||||
|
end
|
||||||
|
-- first loop, get the term
|
||||||
|
for id, doc in pairs(documents) do
|
||||||
|
for k,v in pairs(doc) do
|
||||||
|
-- get max value
|
||||||
|
if v.tfidf > maxv then
|
||||||
|
maxv = v.tfidf
|
||||||
|
end
|
||||||
|
-- get the term
|
||||||
|
if not terms[k] then
|
||||||
|
index = index + 1
|
||||||
|
terms[k] = index
|
||||||
|
end
|
||||||
|
for pid in pairs(documents) do
|
||||||
|
if not maps[pid][k] then
|
||||||
|
if id == pid then
|
||||||
|
maps[pid][k] = v.tfidf
|
||||||
|
else
|
||||||
|
maps[pid][k] = 0
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if maps[pid][k] == 0 and id == pid then
|
||||||
|
maps[pid][k] = v.tfidf
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- reindexing the vectors
|
||||||
|
for id in pairs(documents) do
|
||||||
|
for k,v in pairs(maps[id]) do
|
||||||
|
vectors[id][terms[k]] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--echo("Max tfidf "..maxv.." in document #"..maxid.." of term "..term)
|
||||||
|
return vectors, maxv, index, terms
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.similarity = function(va, vb)
|
||||||
|
-- using cosin similarity
|
||||||
|
local dotp = 0
|
||||||
|
local maga = 0
|
||||||
|
local magb = 0
|
||||||
|
for k = 1,#va do
|
||||||
|
dotp = dotp + va[k]*vb[k]
|
||||||
|
maga = maga + va[k]*va[k]
|
||||||
|
magb = magb + vb[k]*vb[k]
|
||||||
|
end
|
||||||
|
maga = math.sqrt(maga)
|
||||||
|
magb = math.sqrt(magb)
|
||||||
|
local d = 0
|
||||||
|
if maga ~= 0 and magb ~= 0 then
|
||||||
|
d = dotp/ (magb*maga)
|
||||||
|
end
|
||||||
|
return d
|
||||||
|
end
|
||||||
|
doclassify.similarities = function(v1, collection)
|
||||||
|
local similarities = {}
|
||||||
|
assert(#v1 == #(collection[1]), "Incorrect vectors size")
|
||||||
|
for i=1,#collection do
|
||||||
|
similarities[i] = doclassify.similarity(v1, collection[i])
|
||||||
|
end
|
||||||
|
return similarities
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.mean_similarity = function(v1, v2)
|
||||||
|
assert(#v1 == #v2, "Incorrect vectors size")
|
||||||
|
local similarities = {}
|
||||||
|
for i = 1,#v1 do similarities[i] = doclassify.similarity(v1[i], v2[i]) end
|
||||||
|
return doclassify.mean(similarities)
|
||||||
|
end
|
||||||
|
doclassify.similarity_chart = function(id, vectors)
|
||||||
|
local vs = {}
|
||||||
|
local cnt = 0
|
||||||
|
local lut = {}
|
||||||
|
for k,v in pairs(vectors) do
|
||||||
|
if k ~= id then
|
||||||
|
cnt = cnt + 1
|
||||||
|
vs[cnt] = v
|
||||||
|
lut[cnt] = k
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not vs[1] then return {} end
|
||||||
|
return doclassify.similarities(vectors[id], vs), lut
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.top_similarity = function(id, vectors, n, th)
|
||||||
|
local chart,lut = doclassify.similarity_chart(id,vectors)
|
||||||
|
--echo(JSON.encode(chart))
|
||||||
|
--echo(JSON.encode(lut))
|
||||||
|
if not lut or #lut <= 0 then return nil end
|
||||||
|
local top = {}
|
||||||
|
|
||||||
|
local j=0
|
||||||
|
local goon = true
|
||||||
|
if not th then
|
||||||
|
goon = false
|
||||||
|
th = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
while j < n or goon
|
||||||
|
do
|
||||||
|
local i,maxv = doclassify.argmax(chart)
|
||||||
|
top[lut[i]] = maxv
|
||||||
|
chart[i] = 0.0
|
||||||
|
j=j+1
|
||||||
|
if maxv < th and goon then
|
||||||
|
goon = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--for j=1,n do
|
||||||
|
-- local i,maxv = doclassify.argmax(chart)
|
||||||
|
-- top[lut[i]] = maxv
|
||||||
|
-- chart[i] = 0.0
|
||||||
|
--end
|
||||||
|
return top
|
||||||
|
|
||||||
|
end
|
||||||
|
doclassify.save_vectors = function(vectors, name)
|
||||||
|
local f = io.open(name,"w")
|
||||||
|
if f == nil then return false end
|
||||||
|
for id, v in pairs(vectors) do
|
||||||
|
f:write(id)
|
||||||
|
for i=1,#v do f:write(","..v[i]) end
|
||||||
|
f:write("\n")
|
||||||
|
end
|
||||||
|
f:close()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
doclassify.save_topchart = function(vectors, name,n)
|
||||||
|
local f = io.open(name,"w")
|
||||||
|
if f == nil then return false end
|
||||||
|
for k,v in pairs(vectors) do
|
||||||
|
local top = doclassify.top_similarity(k,vectors,n, 0.1)
|
||||||
|
for a,b in pairs(top) do
|
||||||
|
f:write(k.." "..a.." "..b.."\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
f:close()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
doclassify.kmean = function(nclass, documents, maxstep, ids)
|
||||||
|
-- now
|
||||||
|
local vectors, maxv, size = doclassify.get_vectors(documents)
|
||||||
|
-- random centroids
|
||||||
|
local centroids = {}
|
||||||
|
local old_centroids = {}
|
||||||
|
local clusters = {}
|
||||||
|
--for pid in pairs(documents) do clusters[pid] = 0 end
|
||||||
|
-- add noise to mean_vector
|
||||||
|
for i = 1,nclass do
|
||||||
|
if ids == nil then
|
||||||
|
centroids[i] = doclassify.random(size,math.floor(maxv))
|
||||||
|
else
|
||||||
|
centroids[i] = vectors[ids[i]]
|
||||||
|
end
|
||||||
|
old_centroids[i] = doclassify.zeros(size)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- loop until convergence or maxstep reached
|
||||||
|
local similarity = doclassify.mean_similarity(centroids, old_centroids)
|
||||||
|
local step = maxstep
|
||||||
|
while 1.0-similarity > 1e-9 and step > 0 do
|
||||||
|
clusters = {}
|
||||||
|
--echo(JSON.encode(centroids))
|
||||||
|
for id,v in pairs(vectors) do
|
||||||
|
local similarities = doclassify.similarities(v, centroids)
|
||||||
|
--echo(JSON.encode(similarities))
|
||||||
|
local cluster, maxvalue = doclassify.argmax(similarities)
|
||||||
|
--echo("doc #"..id.." is in clusters #"..cluster.." max value is "..maxvalue)
|
||||||
|
clusters[id] = cluster
|
||||||
|
end
|
||||||
|
-- storing the old centroids
|
||||||
|
old_centroids = centroids
|
||||||
|
-- calculate new centroids
|
||||||
|
local new_centroids = {}
|
||||||
|
for class in pairs(centroids) do
|
||||||
|
local cnt = 0
|
||||||
|
local cvectors = {}
|
||||||
|
for id,v in pairs(vectors) do
|
||||||
|
if clusters[id] == class then
|
||||||
|
cnt = cnt + 1
|
||||||
|
cvectors[cnt] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
new_centroids[class] = doclassify.mean_vector(cvectors, size)
|
||||||
|
end
|
||||||
|
centroids = new_centroids
|
||||||
|
--echo(JSON.encode(centroids))
|
||||||
|
--echo(JSON.encode(old_centroids))
|
||||||
|
similarity = doclassify.mean_similarity(centroids, old_centroids)
|
||||||
|
echo("step #"..step..", similarity "..similarity)
|
||||||
|
step = step - 1
|
||||||
|
end
|
||||||
|
local results = {}
|
||||||
|
for i = 1,nclass do
|
||||||
|
local list = {}
|
||||||
|
local cnt = 0
|
||||||
|
for id,c in pairs(clusters) do
|
||||||
|
if c == i then
|
||||||
|
cnt = cnt + 1
|
||||||
|
list[cnt] = id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
results[i] = list
|
||||||
|
end
|
||||||
|
return results, clusters, centroids
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.zeros = function(n)
|
||||||
|
local vector = {}
|
||||||
|
for i = 1,n do vector[i] = 0.0 end
|
||||||
|
return vector
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.random = function(n,maxv)
|
||||||
|
local vector = {}
|
||||||
|
for i=1,n do
|
||||||
|
vector[i] = math.random() + math.random(0, maxv)
|
||||||
|
end
|
||||||
|
return vector
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.sum = function(v)
|
||||||
|
local sum = 0.0
|
||||||
|
for i=1,#v do sum = sum + v[i] end
|
||||||
|
return sum
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.mean = function(v)
|
||||||
|
return doclassify.sum(v)/#v
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.mean_vector = function(vectors, size)
|
||||||
|
local means = doclassify.zeros(size)
|
||||||
|
if not vectors or #vectors == 0 then return means end
|
||||||
|
--local size = #(vectors[1])
|
||||||
|
local times = 0
|
||||||
|
for k,v in pairs(vectors) do
|
||||||
|
for i=1,#v do means[i] = means[i] + v[i] end
|
||||||
|
times = times + 1
|
||||||
|
end
|
||||||
|
for i = 1,size do means[i] = means[i]/times end
|
||||||
|
return means
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.argmin = function(v)
|
||||||
|
local minv = 0.0
|
||||||
|
local mini = 0.0
|
||||||
|
for i = 1,#v do
|
||||||
|
if v[i] <= minv then
|
||||||
|
mini = i
|
||||||
|
minv = v[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--echo("min index"..mini.." val "..minv)
|
||||||
|
return mini, minv
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.argmax = function(v)
|
||||||
|
local maxv = 0.0
|
||||||
|
local maxi = 0.0
|
||||||
|
for i = 1,#v do
|
||||||
|
if v[i] >= maxv then
|
||||||
|
maxi = i
|
||||||
|
maxv = v[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return maxi,maxv
|
||||||
|
end
|
||||||
|
|
||||||
|
return doclassify
|
151
Blogger/api/ai/stopwords.txt
Normal file
151
Blogger/api/ai/stopwords.txt
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
i
|
||||||
|
me
|
||||||
|
my
|
||||||
|
myself
|
||||||
|
we
|
||||||
|
our
|
||||||
|
ours
|
||||||
|
ourselves
|
||||||
|
you
|
||||||
|
your
|
||||||
|
yours
|
||||||
|
yourself
|
||||||
|
yourselves
|
||||||
|
he
|
||||||
|
him
|
||||||
|
his
|
||||||
|
himself
|
||||||
|
she
|
||||||
|
her
|
||||||
|
hers
|
||||||
|
herself
|
||||||
|
it
|
||||||
|
its
|
||||||
|
itself
|
||||||
|
they
|
||||||
|
them
|
||||||
|
their
|
||||||
|
theirs
|
||||||
|
themselves
|
||||||
|
what
|
||||||
|
which
|
||||||
|
who
|
||||||
|
whom
|
||||||
|
this
|
||||||
|
that
|
||||||
|
these
|
||||||
|
those
|
||||||
|
am
|
||||||
|
is
|
||||||
|
are
|
||||||
|
was
|
||||||
|
were
|
||||||
|
be
|
||||||
|
been
|
||||||
|
being
|
||||||
|
have
|
||||||
|
has
|
||||||
|
had
|
||||||
|
having
|
||||||
|
do
|
||||||
|
does
|
||||||
|
did
|
||||||
|
doing
|
||||||
|
a
|
||||||
|
an
|
||||||
|
the
|
||||||
|
and
|
||||||
|
but
|
||||||
|
if
|
||||||
|
or
|
||||||
|
because
|
||||||
|
as
|
||||||
|
until
|
||||||
|
while
|
||||||
|
of
|
||||||
|
at
|
||||||
|
by
|
||||||
|
for
|
||||||
|
with
|
||||||
|
about
|
||||||
|
against
|
||||||
|
between
|
||||||
|
into
|
||||||
|
through
|
||||||
|
during
|
||||||
|
before
|
||||||
|
after
|
||||||
|
above
|
||||||
|
below
|
||||||
|
to
|
||||||
|
from
|
||||||
|
up
|
||||||
|
down
|
||||||
|
in
|
||||||
|
out
|
||||||
|
on
|
||||||
|
off
|
||||||
|
over
|
||||||
|
under
|
||||||
|
again
|
||||||
|
further
|
||||||
|
then
|
||||||
|
once
|
||||||
|
here
|
||||||
|
there
|
||||||
|
when
|
||||||
|
where
|
||||||
|
why
|
||||||
|
how
|
||||||
|
all
|
||||||
|
any
|
||||||
|
both
|
||||||
|
each
|
||||||
|
few
|
||||||
|
more
|
||||||
|
most
|
||||||
|
other
|
||||||
|
some
|
||||||
|
such
|
||||||
|
no
|
||||||
|
nor
|
||||||
|
not
|
||||||
|
only
|
||||||
|
own
|
||||||
|
same
|
||||||
|
so
|
||||||
|
than
|
||||||
|
too
|
||||||
|
very
|
||||||
|
s
|
||||||
|
t
|
||||||
|
can
|
||||||
|
will
|
||||||
|
just
|
||||||
|
don
|
||||||
|
should
|
||||||
|
now
|
||||||
|
a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
d
|
||||||
|
e
|
||||||
|
f
|
||||||
|
g
|
||||||
|
h
|
||||||
|
i
|
||||||
|
j
|
||||||
|
k
|
||||||
|
l
|
||||||
|
m
|
||||||
|
n
|
||||||
|
o
|
||||||
|
p
|
||||||
|
q
|
||||||
|
w
|
||||||
|
r
|
||||||
|
s
|
||||||
|
t
|
||||||
|
x
|
||||||
|
y
|
||||||
|
z
|
50
Blogger/api/ai/test.lua
Normal file
50
Blogger/api/ai/test.lua
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
local path = require("fs/vfs").ospath("home://aiws/blog-clustering")
|
||||||
|
local gettext = loadfile(path.."/gettext.lua")()
|
||||||
|
local cluster = loadfile(path.."/cluster.lua")()
|
||||||
|
|
||||||
|
local refresh = false
|
||||||
|
|
||||||
|
local file = "/home/mrsang/test.csv"
|
||||||
|
if refresh then
|
||||||
|
local data = gettext.get({publish=1})
|
||||||
|
local documents = {}
|
||||||
|
if data then
|
||||||
|
local sw = gettext.stopwords("home://aiws/blog-clustering/stopwords.txt")
|
||||||
|
for k,v in pairs(data) do
|
||||||
|
local bag = cluster.bow(data[k].content, sw)
|
||||||
|
documents[data[k].id] = bag
|
||||||
|
end
|
||||||
|
cluster.tfidf(documents)
|
||||||
|
--local v = cluster.search("arm", documents)
|
||||||
|
--echo(JSON.encode(v))
|
||||||
|
local vectors, maxv, size = cluster.get_vectors(documents)
|
||||||
|
local s = cluster.save_topchart(vectors,file, 3)
|
||||||
|
if s then echo("file saved") else echo("error save file") end
|
||||||
|
--echo(JSON.encode(r))
|
||||||
|
--r = cluster.similarity(vectors["14"],vectors["16"])
|
||||||
|
--echo("Similarity "..r)
|
||||||
|
|
||||||
|
--local c,l = cluster.kmean(3, documents, 10)
|
||||||
|
--echo(JSON.encode(c))
|
||||||
|
--echo(JSON.encode(l))
|
||||||
|
else
|
||||||
|
echo("Data missing")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local f = io.open(file,"r")
|
||||||
|
local result = {}
|
||||||
|
for line in f:lines() do
|
||||||
|
local arr = {}
|
||||||
|
local cnt = 0
|
||||||
|
for i in line:gmatch( "%S+") do
|
||||||
|
cnt = cnt + 1
|
||||||
|
arr[cnt] = i
|
||||||
|
end
|
||||||
|
if not result[arr[1]] then result[arr[1]] = {} end
|
||||||
|
result[arr[1]][arr[2]] = tonumber(arr[3])
|
||||||
|
end
|
||||||
|
f:close()
|
||||||
|
echo(JSON.encode(result))
|
||||||
|
--local r = cluster.top_similarity("2",vectors, 3)
|
||||||
|
--echo(JSON.encode(r))
|
||||||
|
end
|
72
Blogger/api/sendmail.lua
Normal file
72
Blogger/api/sendmail.lua
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
|
||||||
|
local data = ...
|
||||||
|
|
||||||
|
|
||||||
|
-- Michal Kottman, 2011, public domain
|
||||||
|
local socket = require 'socket'
|
||||||
|
local smtp = require 'socket.smtp'
|
||||||
|
local ssl = require 'ssl'
|
||||||
|
local https = require 'ssl.https'
|
||||||
|
local ltn12 = require 'ltn12'
|
||||||
|
|
||||||
|
function sslCreate()
|
||||||
|
local sock = socket.tcp()
|
||||||
|
return setmetatable({
|
||||||
|
connect = function(_, host, port)
|
||||||
|
local r, e = sock:connect(host, port)
|
||||||
|
if not r then return r, e end
|
||||||
|
sock = ssl.wrap(sock, {mode='client', protocol='tlsv1_2'})
|
||||||
|
return sock:dohandshake()
|
||||||
|
end
|
||||||
|
}, {
|
||||||
|
__index = function(t,n)
|
||||||
|
return function(_, ...)
|
||||||
|
return sock[n](sock, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function sendMail(user, password, to,subject, body)
|
||||||
|
local msg = {
|
||||||
|
headers = {
|
||||||
|
from = string.format("%s <%s@iohub.dev>", user, user),
|
||||||
|
to = string.format("%s <%s>",to.text, to.email),
|
||||||
|
subject = subject
|
||||||
|
},
|
||||||
|
body = body
|
||||||
|
}
|
||||||
|
|
||||||
|
local ok, err = smtp.send {
|
||||||
|
from = string.format('<%s@iohub.dev>', user),
|
||||||
|
rcpt = string.format('<%s>', to.email),
|
||||||
|
source = smtp.message(msg),
|
||||||
|
user = string.format('%s@iohub.dev', user),
|
||||||
|
password = password,
|
||||||
|
server = 'iohub.dev',
|
||||||
|
port = 465,
|
||||||
|
create = sslCreate
|
||||||
|
}
|
||||||
|
if not ok then
|
||||||
|
return false, error
|
||||||
|
else
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local error_msg = {}
|
||||||
|
local iserror = false
|
||||||
|
|
||||||
|
for k,v in pairs(data.to) do
|
||||||
|
LOG_DEBUG("Send email to:"..v.email)
|
||||||
|
local r,e = sendMail(data.user, data.password, v, data.title, data.content)
|
||||||
|
if not r then
|
||||||
|
iserror = true
|
||||||
|
table.insert(error_msg, v.email)
|
||||||
|
LOG_ERROR(string.format("Unable to send mail to %s: %s",v.email, e))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local result = {}
|
||||||
|
result.error = iserror
|
||||||
|
result.result = error_msg
|
||||||
|
return result
|
112
Blogger/build.json
Normal file
112
Blogger/build.json
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
{
|
||||||
|
"name": "Blogger",
|
||||||
|
"targets": {
|
||||||
|
"init": {
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "vfs-mkdir",
|
||||||
|
"data": [
|
||||||
|
"build",
|
||||||
|
"build/debug",
|
||||||
|
"build/release"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ts": {
|
||||||
|
"require": [
|
||||||
|
"ts"
|
||||||
|
],
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "ts-antos-sdk",
|
||||||
|
"data": {
|
||||||
|
"version": "2.0.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ts-compile",
|
||||||
|
"data": {
|
||||||
|
"src": [
|
||||||
|
"main.ts",
|
||||||
|
"dialogs.ts",
|
||||||
|
"tags.ts"
|
||||||
|
],
|
||||||
|
"dest": "build/debug/main.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"uglify": {
|
||||||
|
"require": [
|
||||||
|
"terser"
|
||||||
|
],
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "terser-uglify",
|
||||||
|
"data": [
|
||||||
|
"build/debug/main.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"copy": {
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "vfs-cp",
|
||||||
|
"data": {
|
||||||
|
"src": [
|
||||||
|
"scheme.html",
|
||||||
|
"api",
|
||||||
|
"package.json",
|
||||||
|
"README.md",
|
||||||
|
"main.css"
|
||||||
|
],
|
||||||
|
"dest": "build/debug"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locale": {
|
||||||
|
"require": ["locale"],
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name":"locale-gen",
|
||||||
|
"data": {
|
||||||
|
"src": "",
|
||||||
|
"exclude": ["build/", "api/"],
|
||||||
|
"locale": "en_GB",
|
||||||
|
"dest": "package.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"depend": [
|
||||||
|
"init",
|
||||||
|
"ts",
|
||||||
|
"copy"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"require": [
|
||||||
|
"zip"
|
||||||
|
],
|
||||||
|
"depend": [
|
||||||
|
"init",
|
||||||
|
"ts",
|
||||||
|
"uglify",
|
||||||
|
"copy"
|
||||||
|
],
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "zip-mk",
|
||||||
|
"data": {
|
||||||
|
"src": "build/debug",
|
||||||
|
"dest": "build/release/Blogger.zip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
Blogger/build/debug/README.md
Normal file
27
Blogger/build/debug/README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Blogger
|
||||||
|
|
||||||
|
Blackend for my blog at https://blog.iohub.dev
|
||||||
|
|
||||||
|
|
||||||
|
## Change logs
|
||||||
|
|
||||||
|
### v0.2.x-a
|
||||||
|
* Patch 13: fix bug on blog save
|
||||||
|
* Patch 12: support send mail via SSL
|
||||||
|
* Patch 11: Add TFIDF analyse functionality
|
||||||
|
* Patch 10: Migrate code to typescript, use SQLiteDB lib for database access
|
||||||
|
* Patch 9: Update to use the new MDE library
|
||||||
|
* Patch 8: Support for antOS 2.0.x
|
||||||
|
* Patch 7: Fix sendmail API security bug
|
||||||
|
* Patch 6: Chage libraries load order
|
||||||
|
* Patch 5: Add user photo to portfolio
|
||||||
|
* Patch 4: Add package dependencies
|
||||||
|
* Patch 3: Correct JSON text decoding
|
||||||
|
* Patch 2: Bug fix rendering content
|
||||||
|
* Patch 0-1 Important change: Store raw post content to the database instead of base64 string as before
|
||||||
|
|
||||||
|
### v0.1.x-a
|
||||||
|
* Patch 3-4: Enhance youtube video embedding feature in markdown
|
||||||
|
* Patch 2: CV Category now can be created when database is not created yet
|
||||||
|
* Patch 1: Fix package archive broken
|
||||||
|
* Patch 0: Change default email of the sender
|
83
Blogger/build/debug/api/ai/analyse.lua
Normal file
83
Blogger/build/debug/api/ai/analyse.lua
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
local args = ...
|
||||||
|
|
||||||
|
local ret = {
|
||||||
|
error = false,
|
||||||
|
result = nil
|
||||||
|
}
|
||||||
|
local __dir__ = debug.getinfo(1).source:match("@?(.*/)")
|
||||||
|
LOG_DEBUG("CURRENT PATH:%s", __dir__)
|
||||||
|
local cluster = loadfile(__dir__.."/cluster.lua")()
|
||||||
|
local dbpath = require("vfs").ospath(args.dbpath)
|
||||||
|
LOG_DEBUG("DB PATH:%s", dbpath)
|
||||||
|
|
||||||
|
local gettext = {}
|
||||||
|
gettext.get = function(file)
|
||||||
|
local db = DBModel:new{db=file}
|
||||||
|
db:open()
|
||||||
|
if not db then return nil end
|
||||||
|
local data, sort = db:find("blogs", {
|
||||||
|
where = { publish = 1 },
|
||||||
|
fields = {"id", "content"}
|
||||||
|
})
|
||||||
|
db:close()
|
||||||
|
if not data or #data == 0 then return nil end
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
gettext.stopwords = function(ospath)
|
||||||
|
local words = {}
|
||||||
|
for line in io.lines(ospath) do
|
||||||
|
words[line] = true
|
||||||
|
end
|
||||||
|
return words
|
||||||
|
end
|
||||||
|
|
||||||
|
local data = gettext.get(dbpath)
|
||||||
|
local documents = {}
|
||||||
|
if data then
|
||||||
|
local sw = gettext.stopwords(__dir__.."/stopwords.txt")
|
||||||
|
for k, v in pairs(data) do
|
||||||
|
local bag = cluster.bow(data[k].content, sw)
|
||||||
|
documents[data[k].id] = bag
|
||||||
|
end
|
||||||
|
|
||||||
|
cluster.tfidf(documents)
|
||||||
|
-- indexing all terms to cache file
|
||||||
|
local cache_file = dbpath..".index.json"
|
||||||
|
local f = io.open(cache_file, "w")
|
||||||
|
if f then
|
||||||
|
local indexes = {}
|
||||||
|
for id, doc in pairs(documents) do
|
||||||
|
for term,v in pairs(doc) do
|
||||||
|
if not indexes[term] then
|
||||||
|
indexes[term] = {}
|
||||||
|
end
|
||||||
|
indexes[term][tostring(id)] = doc[term].tfidf
|
||||||
|
end
|
||||||
|
end
|
||||||
|
f:write(JSON.encode(indexes))
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
--
|
||||||
|
--local v = cluster.search("arm", documents)
|
||||||
|
--echo(JSON.encode(v))
|
||||||
|
local vectors, maxv, size = cluster.get_vectors(documents)
|
||||||
|
local analytical = DBModel:new{db=dbpath}
|
||||||
|
analytical:open()
|
||||||
|
-- purge the table
|
||||||
|
analytical:delete("st_similarity", nil)
|
||||||
|
-- get similarity and put to the table
|
||||||
|
for id, v in pairs(vectors) do
|
||||||
|
local top = cluster.top_similarity(id, vectors, args.top, 0.1)
|
||||||
|
for a, b in pairs(top) do
|
||||||
|
local record = {pid = id, sid = a, score = b}
|
||||||
|
analytical:insert("st_similarity", record)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
analytical:close()
|
||||||
|
ret.result = "Analyse complete"
|
||||||
|
else
|
||||||
|
ret.error = "Unable to query database for post"
|
||||||
|
end
|
||||||
|
|
||||||
|
return ret
|
346
Blogger/build/debug/api/ai/cluster.lua
Normal file
346
Blogger/build/debug/api/ai/cluster.lua
Normal file
@ -0,0 +1,346 @@
|
|||||||
|
local doclassify = {}
|
||||||
|
local st = require("stmr")
|
||||||
|
doclassify.bow = function(data, stopwords)
|
||||||
|
-- first step get a table of worlds that contain
|
||||||
|
-- world: occurences
|
||||||
|
local bag = {}
|
||||||
|
for w in data:gmatch('%w+') do
|
||||||
|
local word = w:lower()
|
||||||
|
if not stopwords[word] then
|
||||||
|
word = st.stmr(word)
|
||||||
|
if bag[word] then
|
||||||
|
bag[word].count = bag[word].count + 1
|
||||||
|
else
|
||||||
|
bag[word] = {count=0, tf=0, tfidf=0.0}
|
||||||
|
bag[word].count = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- now calculate the tf of the bag
|
||||||
|
for k,v in pairs(bag) do
|
||||||
|
bag[k].tf = math.log(1 + bag[k].count)
|
||||||
|
end
|
||||||
|
return bag
|
||||||
|
end
|
||||||
|
doclassify.len = function(table)
|
||||||
|
local cnt = 0
|
||||||
|
for k,v in pairs(table) do cnt = cnt+1 end
|
||||||
|
return cnt
|
||||||
|
end
|
||||||
|
doclassify.tfidf = function(documents)
|
||||||
|
-- now for each term in a bag, calculate
|
||||||
|
-- the inverse document frequency, which
|
||||||
|
-- is a measure of how much information
|
||||||
|
-- the word provides, that is, whether the
|
||||||
|
-- term is common or rare across all documents
|
||||||
|
local ndoc = doclassify.len(documents)
|
||||||
|
for k,bag in pairs(documents) do
|
||||||
|
-- for eacht term in bag
|
||||||
|
-- calculate its idf across all documents
|
||||||
|
for term,b in pairs(bag) do
|
||||||
|
local n = 0
|
||||||
|
for id,doc in pairs(documents) do
|
||||||
|
if doc[term] then n = n+1 end
|
||||||
|
end
|
||||||
|
--echo("term:"..term.." appears in"..n.." documents")
|
||||||
|
b.tfidf = b.tf*math.log(ndoc/n)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.search = function(term, documents)
|
||||||
|
local r = {}
|
||||||
|
for id, doc in pairs(documents) do
|
||||||
|
if doc[term:lower()] then
|
||||||
|
r[id] = doc[term].tfidf
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.get_vectors = function(documents)
|
||||||
|
-- get a list of vector from documents
|
||||||
|
local index = 0
|
||||||
|
local vectors = {}
|
||||||
|
local maps = {}
|
||||||
|
local terms = {}
|
||||||
|
local maxv = 0
|
||||||
|
|
||||||
|
for id in pairs(documents) do
|
||||||
|
maps[id] = {}
|
||||||
|
vectors[id] = {}
|
||||||
|
end
|
||||||
|
-- first loop, get the term
|
||||||
|
for id, doc in pairs(documents) do
|
||||||
|
for k,v in pairs(doc) do
|
||||||
|
-- get max value
|
||||||
|
if v.tfidf > maxv then
|
||||||
|
maxv = v.tfidf
|
||||||
|
end
|
||||||
|
-- get the term
|
||||||
|
if not terms[k] then
|
||||||
|
index = index + 1
|
||||||
|
terms[k] = index
|
||||||
|
end
|
||||||
|
for pid in pairs(documents) do
|
||||||
|
if not maps[pid][k] then
|
||||||
|
if id == pid then
|
||||||
|
maps[pid][k] = v.tfidf
|
||||||
|
else
|
||||||
|
maps[pid][k] = 0
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if maps[pid][k] == 0 and id == pid then
|
||||||
|
maps[pid][k] = v.tfidf
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- reindexing the vectors
|
||||||
|
for id in pairs(documents) do
|
||||||
|
for k,v in pairs(maps[id]) do
|
||||||
|
vectors[id][terms[k]] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--echo("Max tfidf "..maxv.." in document #"..maxid.." of term "..term)
|
||||||
|
return vectors, maxv, index, terms
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.similarity = function(va, vb)
|
||||||
|
-- using cosin similarity
|
||||||
|
local dotp = 0
|
||||||
|
local maga = 0
|
||||||
|
local magb = 0
|
||||||
|
for k = 1,#va do
|
||||||
|
dotp = dotp + va[k]*vb[k]
|
||||||
|
maga = maga + va[k]*va[k]
|
||||||
|
magb = magb + vb[k]*vb[k]
|
||||||
|
end
|
||||||
|
maga = math.sqrt(maga)
|
||||||
|
magb = math.sqrt(magb)
|
||||||
|
local d = 0
|
||||||
|
if maga ~= 0 and magb ~= 0 then
|
||||||
|
d = dotp/ (magb*maga)
|
||||||
|
end
|
||||||
|
return d
|
||||||
|
end
|
||||||
|
doclassify.similarities = function(v1, collection)
|
||||||
|
local similarities = {}
|
||||||
|
assert(#v1 == #(collection[1]), "Incorrect vectors size")
|
||||||
|
for i=1,#collection do
|
||||||
|
similarities[i] = doclassify.similarity(v1, collection[i])
|
||||||
|
end
|
||||||
|
return similarities
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.mean_similarity = function(v1, v2)
|
||||||
|
assert(#v1 == #v2, "Incorrect vectors size")
|
||||||
|
local similarities = {}
|
||||||
|
for i = 1,#v1 do similarities[i] = doclassify.similarity(v1[i], v2[i]) end
|
||||||
|
return doclassify.mean(similarities)
|
||||||
|
end
|
||||||
|
doclassify.similarity_chart = function(id, vectors)
|
||||||
|
local vs = {}
|
||||||
|
local cnt = 0
|
||||||
|
local lut = {}
|
||||||
|
for k,v in pairs(vectors) do
|
||||||
|
if k ~= id then
|
||||||
|
cnt = cnt + 1
|
||||||
|
vs[cnt] = v
|
||||||
|
lut[cnt] = k
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not vs[1] then return {} end
|
||||||
|
return doclassify.similarities(vectors[id], vs), lut
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.top_similarity = function(id, vectors, n, th)
|
||||||
|
local chart,lut = doclassify.similarity_chart(id,vectors)
|
||||||
|
--echo(JSON.encode(chart))
|
||||||
|
--echo(JSON.encode(lut))
|
||||||
|
if not lut or #lut <= 0 then return nil end
|
||||||
|
local top = {}
|
||||||
|
|
||||||
|
local j=0
|
||||||
|
local goon = true
|
||||||
|
if not th then
|
||||||
|
goon = false
|
||||||
|
th = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
while j < n or goon
|
||||||
|
do
|
||||||
|
local i,maxv = doclassify.argmax(chart)
|
||||||
|
top[lut[i]] = maxv
|
||||||
|
chart[i] = 0.0
|
||||||
|
j=j+1
|
||||||
|
if maxv < th and goon then
|
||||||
|
goon = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--for j=1,n do
|
||||||
|
-- local i,maxv = doclassify.argmax(chart)
|
||||||
|
-- top[lut[i]] = maxv
|
||||||
|
-- chart[i] = 0.0
|
||||||
|
--end
|
||||||
|
return top
|
||||||
|
|
||||||
|
end
|
||||||
|
doclassify.save_vectors = function(vectors, name)
|
||||||
|
local f = io.open(name,"w")
|
||||||
|
if f == nil then return false end
|
||||||
|
for id, v in pairs(vectors) do
|
||||||
|
f:write(id)
|
||||||
|
for i=1,#v do f:write(","..v[i]) end
|
||||||
|
f:write("\n")
|
||||||
|
end
|
||||||
|
f:close()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
doclassify.save_topchart = function(vectors, name,n)
|
||||||
|
local f = io.open(name,"w")
|
||||||
|
if f == nil then return false end
|
||||||
|
for k,v in pairs(vectors) do
|
||||||
|
local top = doclassify.top_similarity(k,vectors,n, 0.1)
|
||||||
|
for a,b in pairs(top) do
|
||||||
|
f:write(k.." "..a.." "..b.."\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
f:close()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
doclassify.kmean = function(nclass, documents, maxstep, ids)
|
||||||
|
-- now
|
||||||
|
local vectors, maxv, size = doclassify.get_vectors(documents)
|
||||||
|
-- random centroids
|
||||||
|
local centroids = {}
|
||||||
|
local old_centroids = {}
|
||||||
|
local clusters = {}
|
||||||
|
--for pid in pairs(documents) do clusters[pid] = 0 end
|
||||||
|
-- add noise to mean_vector
|
||||||
|
for i = 1,nclass do
|
||||||
|
if ids == nil then
|
||||||
|
centroids[i] = doclassify.random(size,math.floor(maxv))
|
||||||
|
else
|
||||||
|
centroids[i] = vectors[ids[i]]
|
||||||
|
end
|
||||||
|
old_centroids[i] = doclassify.zeros(size)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- loop until convergence or maxstep reached
|
||||||
|
local similarity = doclassify.mean_similarity(centroids, old_centroids)
|
||||||
|
local step = maxstep
|
||||||
|
while 1.0-similarity > 1e-9 and step > 0 do
|
||||||
|
clusters = {}
|
||||||
|
--echo(JSON.encode(centroids))
|
||||||
|
for id,v in pairs(vectors) do
|
||||||
|
local similarities = doclassify.similarities(v, centroids)
|
||||||
|
--echo(JSON.encode(similarities))
|
||||||
|
local cluster, maxvalue = doclassify.argmax(similarities)
|
||||||
|
--echo("doc #"..id.." is in clusters #"..cluster.." max value is "..maxvalue)
|
||||||
|
clusters[id] = cluster
|
||||||
|
end
|
||||||
|
-- storing the old centroids
|
||||||
|
old_centroids = centroids
|
||||||
|
-- calculate new centroids
|
||||||
|
local new_centroids = {}
|
||||||
|
for class in pairs(centroids) do
|
||||||
|
local cnt = 0
|
||||||
|
local cvectors = {}
|
||||||
|
for id,v in pairs(vectors) do
|
||||||
|
if clusters[id] == class then
|
||||||
|
cnt = cnt + 1
|
||||||
|
cvectors[cnt] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
new_centroids[class] = doclassify.mean_vector(cvectors, size)
|
||||||
|
end
|
||||||
|
centroids = new_centroids
|
||||||
|
--echo(JSON.encode(centroids))
|
||||||
|
--echo(JSON.encode(old_centroids))
|
||||||
|
similarity = doclassify.mean_similarity(centroids, old_centroids)
|
||||||
|
echo("step #"..step..", similarity "..similarity)
|
||||||
|
step = step - 1
|
||||||
|
end
|
||||||
|
local results = {}
|
||||||
|
for i = 1,nclass do
|
||||||
|
local list = {}
|
||||||
|
local cnt = 0
|
||||||
|
for id,c in pairs(clusters) do
|
||||||
|
if c == i then
|
||||||
|
cnt = cnt + 1
|
||||||
|
list[cnt] = id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
results[i] = list
|
||||||
|
end
|
||||||
|
return results, clusters, centroids
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.zeros = function(n)
|
||||||
|
local vector = {}
|
||||||
|
for i = 1,n do vector[i] = 0.0 end
|
||||||
|
return vector
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.random = function(n,maxv)
|
||||||
|
local vector = {}
|
||||||
|
for i=1,n do
|
||||||
|
vector[i] = math.random() + math.random(0, maxv)
|
||||||
|
end
|
||||||
|
return vector
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.sum = function(v)
|
||||||
|
local sum = 0.0
|
||||||
|
for i=1,#v do sum = sum + v[i] end
|
||||||
|
return sum
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.mean = function(v)
|
||||||
|
return doclassify.sum(v)/#v
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.mean_vector = function(vectors, size)
|
||||||
|
local means = doclassify.zeros(size)
|
||||||
|
if not vectors or #vectors == 0 then return means end
|
||||||
|
--local size = #(vectors[1])
|
||||||
|
local times = 0
|
||||||
|
for k,v in pairs(vectors) do
|
||||||
|
for i=1,#v do means[i] = means[i] + v[i] end
|
||||||
|
times = times + 1
|
||||||
|
end
|
||||||
|
for i = 1,size do means[i] = means[i]/times end
|
||||||
|
return means
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.argmin = function(v)
|
||||||
|
local minv = 0.0
|
||||||
|
local mini = 0.0
|
||||||
|
for i = 1,#v do
|
||||||
|
if v[i] <= minv then
|
||||||
|
mini = i
|
||||||
|
minv = v[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--echo("min index"..mini.." val "..minv)
|
||||||
|
return mini, minv
|
||||||
|
end
|
||||||
|
|
||||||
|
doclassify.argmax = function(v)
|
||||||
|
local maxv = 0.0
|
||||||
|
local maxi = 0.0
|
||||||
|
for i = 1,#v do
|
||||||
|
if v[i] >= maxv then
|
||||||
|
maxi = i
|
||||||
|
maxv = v[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return maxi,maxv
|
||||||
|
end
|
||||||
|
|
||||||
|
return doclassify
|
151
Blogger/build/debug/api/ai/stopwords.txt
Normal file
151
Blogger/build/debug/api/ai/stopwords.txt
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
i
|
||||||
|
me
|
||||||
|
my
|
||||||
|
myself
|
||||||
|
we
|
||||||
|
our
|
||||||
|
ours
|
||||||
|
ourselves
|
||||||
|
you
|
||||||
|
your
|
||||||
|
yours
|
||||||
|
yourself
|
||||||
|
yourselves
|
||||||
|
he
|
||||||
|
him
|
||||||
|
his
|
||||||
|
himself
|
||||||
|
she
|
||||||
|
her
|
||||||
|
hers
|
||||||
|
herself
|
||||||
|
it
|
||||||
|
its
|
||||||
|
itself
|
||||||
|
they
|
||||||
|
them
|
||||||
|
their
|
||||||
|
theirs
|
||||||
|
themselves
|
||||||
|
what
|
||||||
|
which
|
||||||
|
who
|
||||||
|
whom
|
||||||
|
this
|
||||||
|
that
|
||||||
|
these
|
||||||
|
those
|
||||||
|
am
|
||||||
|
is
|
||||||
|
are
|
||||||
|
was
|
||||||
|
were
|
||||||
|
be
|
||||||
|
been
|
||||||
|
being
|
||||||
|
have
|
||||||
|
has
|
||||||
|
had
|
||||||
|
having
|
||||||
|
do
|
||||||
|
does
|
||||||
|
did
|
||||||
|
doing
|
||||||
|
a
|
||||||
|
an
|
||||||
|
the
|
||||||
|
and
|
||||||
|
but
|
||||||
|
if
|
||||||
|
or
|
||||||
|
because
|
||||||
|
as
|
||||||
|
until
|
||||||
|
while
|
||||||
|
of
|
||||||
|
at
|
||||||
|
by
|
||||||
|
for
|
||||||
|
with
|
||||||
|
about
|
||||||
|
against
|
||||||
|
between
|
||||||
|
into
|
||||||
|
through
|
||||||
|
during
|
||||||
|
before
|
||||||
|
after
|
||||||
|
above
|
||||||
|
below
|
||||||
|
to
|
||||||
|
from
|
||||||
|
up
|
||||||
|
down
|
||||||
|
in
|
||||||
|
out
|
||||||
|
on
|
||||||
|
off
|
||||||
|
over
|
||||||
|
under
|
||||||
|
again
|
||||||
|
further
|
||||||
|
then
|
||||||
|
once
|
||||||
|
here
|
||||||
|
there
|
||||||
|
when
|
||||||
|
where
|
||||||
|
why
|
||||||
|
how
|
||||||
|
all
|
||||||
|
any
|
||||||
|
both
|
||||||
|
each
|
||||||
|
few
|
||||||
|
more
|
||||||
|
most
|
||||||
|
other
|
||||||
|
some
|
||||||
|
such
|
||||||
|
no
|
||||||
|
nor
|
||||||
|
not
|
||||||
|
only
|
||||||
|
own
|
||||||
|
same
|
||||||
|
so
|
||||||
|
than
|
||||||
|
too
|
||||||
|
very
|
||||||
|
s
|
||||||
|
t
|
||||||
|
can
|
||||||
|
will
|
||||||
|
just
|
||||||
|
don
|
||||||
|
should
|
||||||
|
now
|
||||||
|
a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
d
|
||||||
|
e
|
||||||
|
f
|
||||||
|
g
|
||||||
|
h
|
||||||
|
i
|
||||||
|
j
|
||||||
|
k
|
||||||
|
l
|
||||||
|
m
|
||||||
|
n
|
||||||
|
o
|
||||||
|
p
|
||||||
|
q
|
||||||
|
w
|
||||||
|
r
|
||||||
|
s
|
||||||
|
t
|
||||||
|
x
|
||||||
|
y
|
||||||
|
z
|
50
Blogger/build/debug/api/ai/test.lua
Normal file
50
Blogger/build/debug/api/ai/test.lua
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
local path = require("fs/vfs").ospath("home://aiws/blog-clustering")
|
||||||
|
local gettext = loadfile(path.."/gettext.lua")()
|
||||||
|
local cluster = loadfile(path.."/cluster.lua")()
|
||||||
|
|
||||||
|
local refresh = false
|
||||||
|
|
||||||
|
local file = "/home/mrsang/test.csv"
|
||||||
|
if refresh then
|
||||||
|
local data = gettext.get({publish=1})
|
||||||
|
local documents = {}
|
||||||
|
if data then
|
||||||
|
local sw = gettext.stopwords("home://aiws/blog-clustering/stopwords.txt")
|
||||||
|
for k,v in pairs(data) do
|
||||||
|
local bag = cluster.bow(data[k].content, sw)
|
||||||
|
documents[data[k].id] = bag
|
||||||
|
end
|
||||||
|
cluster.tfidf(documents)
|
||||||
|
--local v = cluster.search("arm", documents)
|
||||||
|
--echo(JSON.encode(v))
|
||||||
|
local vectors, maxv, size = cluster.get_vectors(documents)
|
||||||
|
local s = cluster.save_topchart(vectors,file, 3)
|
||||||
|
if s then echo("file saved") else echo("error save file") end
|
||||||
|
--echo(JSON.encode(r))
|
||||||
|
--r = cluster.similarity(vectors["14"],vectors["16"])
|
||||||
|
--echo("Similarity "..r)
|
||||||
|
|
||||||
|
--local c,l = cluster.kmean(3, documents, 10)
|
||||||
|
--echo(JSON.encode(c))
|
||||||
|
--echo(JSON.encode(l))
|
||||||
|
else
|
||||||
|
echo("Data missing")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local f = io.open(file,"r")
|
||||||
|
local result = {}
|
||||||
|
for line in f:lines() do
|
||||||
|
local arr = {}
|
||||||
|
local cnt = 0
|
||||||
|
for i in line:gmatch( "%S+") do
|
||||||
|
cnt = cnt + 1
|
||||||
|
arr[cnt] = i
|
||||||
|
end
|
||||||
|
if not result[arr[1]] then result[arr[1]] = {} end
|
||||||
|
result[arr[1]][arr[2]] = tonumber(arr[3])
|
||||||
|
end
|
||||||
|
f:close()
|
||||||
|
echo(JSON.encode(result))
|
||||||
|
--local r = cluster.top_similarity("2",vectors, 3)
|
||||||
|
--echo(JSON.encode(r))
|
||||||
|
end
|
72
Blogger/build/debug/api/sendmail.lua
Normal file
72
Blogger/build/debug/api/sendmail.lua
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
|
||||||
|
local data = ...
|
||||||
|
|
||||||
|
|
||||||
|
-- Michal Kottman, 2011, public domain
|
||||||
|
local socket = require 'socket'
|
||||||
|
local smtp = require 'socket.smtp'
|
||||||
|
local ssl = require 'ssl'
|
||||||
|
local https = require 'ssl.https'
|
||||||
|
local ltn12 = require 'ltn12'
|
||||||
|
|
||||||
|
function sslCreate()
|
||||||
|
local sock = socket.tcp()
|
||||||
|
return setmetatable({
|
||||||
|
connect = function(_, host, port)
|
||||||
|
local r, e = sock:connect(host, port)
|
||||||
|
if not r then return r, e end
|
||||||
|
sock = ssl.wrap(sock, {mode='client', protocol='tlsv1_2'})
|
||||||
|
return sock:dohandshake()
|
||||||
|
end
|
||||||
|
}, {
|
||||||
|
__index = function(t,n)
|
||||||
|
return function(_, ...)
|
||||||
|
return sock[n](sock, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function sendMail(user, password, to,subject, body)
|
||||||
|
local msg = {
|
||||||
|
headers = {
|
||||||
|
from = string.format("%s <%s@iohub.dev>", user, user),
|
||||||
|
to = string.format("%s <%s>",to.text, to.email),
|
||||||
|
subject = subject
|
||||||
|
},
|
||||||
|
body = body
|
||||||
|
}
|
||||||
|
|
||||||
|
local ok, err = smtp.send {
|
||||||
|
from = string.format('<%s@iohub.dev>', user),
|
||||||
|
rcpt = string.format('<%s>', to.email),
|
||||||
|
source = smtp.message(msg),
|
||||||
|
user = string.format('%s@iohub.dev', user),
|
||||||
|
password = password,
|
||||||
|
server = 'iohub.dev',
|
||||||
|
port = 465,
|
||||||
|
create = sslCreate
|
||||||
|
}
|
||||||
|
if not ok then
|
||||||
|
return false, error
|
||||||
|
else
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local error_msg = {}
|
||||||
|
local iserror = false
|
||||||
|
|
||||||
|
for k,v in pairs(data.to) do
|
||||||
|
LOG_DEBUG("Send email to:"..v.email)
|
||||||
|
local r,e = sendMail(data.user, data.password, v, data.title, data.content)
|
||||||
|
if not r then
|
||||||
|
iserror = true
|
||||||
|
table.insert(error_msg, v.email)
|
||||||
|
LOG_ERROR(string.format("Unable to send mail to %s: %s",v.email, e))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local result = {}
|
||||||
|
result.error = iserror
|
||||||
|
result.result = error_msg
|
||||||
|
return result
|
78
Blogger/build/debug/main.css
Normal file
78
Blogger/build/debug/main.css
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
afx-app-window[data-id="blogger-win"] afx-hbox[data-id="user-container"] afx-label i.label-text{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window .lbl-header i.label-text{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-hbox[data-id="cv-container"] .cat-header{
|
||||||
|
border-bottom: 1px solid #cbcbcb;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] .cv-side-bar-btn
|
||||||
|
{
|
||||||
|
text-align: right;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "cv-sec-list"] > .list-container > ul .afx-cv-sec-title .label-text{
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "cv-sec-list"] afx-blogger-cvsection-item afx-label {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
}
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "cv-sec-list"] afx-blogger-cvsection-item p {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "cv-sec-list"] afx-blogger-cvsection-item .afx-cv-sec-period,
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "cv-sec-list"] afx-blogger-cvsection-item .afx-cv-sec-loc {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "cv-sec-list"] afx-blogger-cvsection-item afx-cv-sec-content{
|
||||||
|
text-align: justify;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "cv-sec-list"] > div.list-container > ul li.selected {
|
||||||
|
border: 1px solid #116cd6;
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "cv-sec-list"] .closable::before{
|
||||||
|
content: "\f014";
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "cv-sec-list"] .period-end::before{
|
||||||
|
content: "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id ='blogger-win'] .editor-toolbar{
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "blog-list"] > div.list-container > ul li afx-label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "blog-list"] > div.list-container > ul .afx-blogpost-title .label-text{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "blog-list"] > div.list-container > ul .blog-dates .label-text{
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "blog-list"] > div.list-container > ul li.selected {
|
||||||
|
background-color: #116cd6;
|
||||||
|
color:white;
|
||||||
|
}
|
1
Blogger/build/debug/main.js
Normal file
1
Blogger/build/debug/main.js
Normal file
File diff suppressed because one or more lines are too long
98
Blogger/build/debug/package.json
Normal file
98
Blogger/build/debug/package.json
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
"app": "Blogger",
|
||||||
|
"name": "Blogging application",
|
||||||
|
"description": "Backend manager for blogging",
|
||||||
|
"info": {
|
||||||
|
"author": "Xuan Sang LE",
|
||||||
|
"email": "xsang.le@gmail.com"
|
||||||
|
},
|
||||||
|
"version": "0.2.13-a",
|
||||||
|
"category": "Internet",
|
||||||
|
"iconclass": "fa fa-book",
|
||||||
|
"dependencies": [
|
||||||
|
"SimpleMDE@2.18.0-r",
|
||||||
|
"Katex@0.11.1-r",
|
||||||
|
"SQLiteDB@0.1.0-a"
|
||||||
|
],
|
||||||
|
"mimes": [
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"locales": {
|
||||||
|
"en_GB": {
|
||||||
|
"Pick a parent": "Pick a parent",
|
||||||
|
"Category name": "Category name",
|
||||||
|
"Ok": "Ok",
|
||||||
|
"Cancel": "Cancel",
|
||||||
|
"Title": "Title",
|
||||||
|
"Subtitle": "Subtitle",
|
||||||
|
"Location": "Location",
|
||||||
|
"From": "From",
|
||||||
|
"Save": "Save",
|
||||||
|
"Content": "Content",
|
||||||
|
"IO Hub mail username/password": "IO Hub mail username/password",
|
||||||
|
"Send": "Send",
|
||||||
|
"Please select a parent category": "Please select a parent category",
|
||||||
|
"Please enter category name": "Please enter category name",
|
||||||
|
"Parent can not be the category itself": "Parent can not be the category itself",
|
||||||
|
"Title or content must not be blank": "Title or content must not be blank",
|
||||||
|
"No email selected": "No email selected",
|
||||||
|
"Unable to send mail to: {0}": "Unable to send mail to: {0}",
|
||||||
|
"Error sending mail: {0}": "Error sending mail: {0}",
|
||||||
|
"Open/Create database": "Open/Create database",
|
||||||
|
"Open/create new database": "Open/create new database",
|
||||||
|
"Unable to init database file: {0}": "Unable to init database file: {0}",
|
||||||
|
"Select image file": "Select image file",
|
||||||
|
"Unable to get file": "Unable to get file",
|
||||||
|
"Add category": "Add category",
|
||||||
|
"cv-cat-add: {0}": "cv-cat-add: {0}",
|
||||||
|
"Edit category": "Edit category",
|
||||||
|
"cv-cat-edit: {0}": "cv-cat-edit: {0}",
|
||||||
|
"Delete category": "Delete category",
|
||||||
|
"Do you really want to delete: {0}?": "Do you really want to delete: {0}?",
|
||||||
|
"cv-cat-del: {0}": "cv-cat-del: {0}",
|
||||||
|
"Please select a category": "Please select a category",
|
||||||
|
"New section entry for {0}": "New section entry for {0}",
|
||||||
|
"cv-sec-add: {0}": "cv-sec-add: {0}",
|
||||||
|
"Please select a section to move": "Please select a section to move",
|
||||||
|
"Move to": "Move to",
|
||||||
|
"cv-sec-move: {0}": "cv-sec-move: {0}",
|
||||||
|
"Please select a section to edit": "Please select a section to edit",
|
||||||
|
"Modify section entry": "Modify section entry",
|
||||||
|
"cv-sec-edit: {0}": "cv-sec-edit: {0}",
|
||||||
|
"Cannot delete the section: {0}": "Cannot delete the section: {0}",
|
||||||
|
"New": "New",
|
||||||
|
"Cannot export file for embedding to text": "Cannot export file for embedding to text",
|
||||||
|
"Preview": "Preview",
|
||||||
|
"Send mail": "Send mail",
|
||||||
|
"No post selected": "No post selected",
|
||||||
|
"Emails sent": "Emails sent",
|
||||||
|
"Error sending mails: {0}": "Error sending mails: {0}",
|
||||||
|
"No record found for ID {}": "No record found for ID {}",
|
||||||
|
"Cannot fetch the entry content": "Cannot fetch the entry content",
|
||||||
|
"Delete a post": "Delete a post",
|
||||||
|
"Do you really want to delete this post ?": "Do you really want to delete this post ?",
|
||||||
|
"Cannot fetch user data": "Cannot fetch user data",
|
||||||
|
"Full name must be entered": "Full name must be entered",
|
||||||
|
"User data updated": "User data updated",
|
||||||
|
"Cannot save user data: {0}": "Cannot save user data: {0}",
|
||||||
|
"Unable to load categories": "Unable to load categories",
|
||||||
|
"Found {0} sections": "Found {0} sections",
|
||||||
|
"Please insert a title in the text: beginning with heading": "Please insert a title in the text: beginning with heading",
|
||||||
|
"Please enter tags": "Please enter tags",
|
||||||
|
"Cannot save blog: {0}": "Cannot save blog: {0}",
|
||||||
|
"No more record to load": "No more record to load",
|
||||||
|
"Full name": "Full name",
|
||||||
|
"Address": "Address",
|
||||||
|
"Phone": "Phone",
|
||||||
|
"Email": "Email",
|
||||||
|
"Url": "Url",
|
||||||
|
"Photo": "Photo",
|
||||||
|
"Short biblio": "Short biblio",
|
||||||
|
"Categories": "Categories",
|
||||||
|
"Load more": "Load more",
|
||||||
|
"Tags": "Tags",
|
||||||
|
"Created: {0}": "Created: {0}",
|
||||||
|
"Updated: {0}": "Updated: {0}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
Blogger/build/debug/scheme.html
Normal file
91
Blogger/build/debug/scheme.html
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<afx-app-window data-id = "blogger-win" apptitle="Blogger" width="650" height="500">
|
||||||
|
<afx-hbox padding="5">
|
||||||
|
<afx-tab-container data-id = "tabcontainer" dir = "row" tabbarwidth= "40">
|
||||||
|
|
||||||
|
<afx-hbox data-id="user-container" data-height="100%" iconclass="fa fa-user-circle">
|
||||||
|
<afx-vbox>
|
||||||
|
<afx-hbox data-height = "30">
|
||||||
|
<afx-label data-width= "70" text = "__(Full name)"></afx-label>
|
||||||
|
<input type = "text" name="fullname" input-class = "user-input"></input>
|
||||||
|
</afx-hbox>
|
||||||
|
<afx-hbox data-height = "30">
|
||||||
|
<afx-label text = "__(Address)" data-width= "70"></afx-label>
|
||||||
|
<input type = "text" name="address" input-class = "user-input"></input>
|
||||||
|
</afx-hbox>
|
||||||
|
<afx-hbox data-height = "30">
|
||||||
|
<afx-label text = "__(Phone)" data-width= "70"></afx-label>
|
||||||
|
<input type = "text" name="Phone" input-class = "user-input"></input>
|
||||||
|
</afx-hbox>
|
||||||
|
<afx-hbox data-height = "30">
|
||||||
|
<afx-label text = "__(Email)" data-width= "70"></afx-label>
|
||||||
|
<input type = "text" name="email" input-class = "user-input"></input>
|
||||||
|
</afx-hbox>
|
||||||
|
<afx-hbox data-height = "30">
|
||||||
|
<afx-label text = "__(Url)" data-width= "70"></afx-label>
|
||||||
|
<input type = "text" name="url" input-class = "user-input"></input>
|
||||||
|
</afx-hbox>
|
||||||
|
<afx-hbox data-height = "30">
|
||||||
|
<afx-label text = "__(Photo)" data-width= "70"></afx-label>
|
||||||
|
<input type = "text" name="photo" data-id="photo" readonly="readonly" input-class = "user-input"></input>
|
||||||
|
</afx-hbox>
|
||||||
|
<afx-label data-height = "30" text = "__(Short biblio)"></afx-label>
|
||||||
|
<textarea name="shortbiblio" input-class = "user-input"></textarea>
|
||||||
|
<afx-hbox data-height = "35" style="text-align: right;">
|
||||||
|
<afx-button iconclass = "fa fa-save" data-id = "bt-user-save" text = "__(Save)"></afx-button>
|
||||||
|
</afx-hbox>
|
||||||
|
</afx-vbox>
|
||||||
|
</afx-hbox>
|
||||||
|
|
||||||
|
<afx-hbox data-id="cv-container" data-height="100%" iconclass="fa fa-info-circle">
|
||||||
|
<div data-width="5"></div>
|
||||||
|
<afx-vbox data-width="150" min-width="100">
|
||||||
|
<afx-label class="lbl-header" data-height = "23" text = "__(Categories)" iconclass = "fa fa-bars"></afx-label>
|
||||||
|
<afx-tree-view data-id = "cv-list" ></afx-tree-view>
|
||||||
|
<div data-height="35" class = "cv-side-bar-btn">
|
||||||
|
<afx-button data-id = "cv-cat-add" text = "" iconclass = "fa fa-plus-circle"></afx-button>
|
||||||
|
<afx-button data-id = "cv-cat-del" text = "" iconclass = "fa fa-minus-circle"></afx-button>
|
||||||
|
<afx-button data-id = "cv-cat-edit" text = "" iconclass = "fa fa-pencil-square-o"></afx-button>
|
||||||
|
</div>
|
||||||
|
</afx-vbox>
|
||||||
|
<afx-resizer data-width = "2"></afx-resizer>
|
||||||
|
<afx-vbox>
|
||||||
|
<afx-list-view data-id = "cv-sec-list" ></afx-list-view>
|
||||||
|
<afx-hbox data-height="35" >
|
||||||
|
<afx-label data-id = "cv-sec-status"></afx-label>
|
||||||
|
<div class = "cv-side-bar-btn">
|
||||||
|
<afx-button data-id = "cv-sec-add" text = "" iconclass = "fa fa-plus-circle"></afx-button>
|
||||||
|
<afx-button data-id = "cv-sec-edit" text = "" iconclass = "fa fa-pencil-square-o"></afx-button>
|
||||||
|
<afx-button data-id = "cv-sec-move" text = "" iconclass = "fa fa-exchange"></afx-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</afx-hbox>
|
||||||
|
</afx-vbox>
|
||||||
|
<div data-width="5"></div>
|
||||||
|
</afx-hbox>
|
||||||
|
|
||||||
|
|
||||||
|
<afx-hbox data-id = "blog-container" data-height="100%" iconclass="fa fa-book">
|
||||||
|
<afx-vbox>
|
||||||
|
<afx-list-view data-id = "blog-list" min-width="100" data-width="200"></afx-list-view>
|
||||||
|
<afx-button data-id = "blog-load-more" text = "__(Load more)" iconclass_end = "bi bi-chevron-double-right" data-height="content"></afx-button>
|
||||||
|
</afx-vbox>
|
||||||
|
<afx-resizer data-width = "3"></afx-resizer>
|
||||||
|
<afx-vbox>
|
||||||
|
<div data-id = "editor-container">
|
||||||
|
<textarea data-id="markarea" ></textarea>
|
||||||
|
</div>
|
||||||
|
<afx-label text = "__(Tags)" style="font-weight:bold;" data-height="25" ></afx-label>
|
||||||
|
<afx-hbox data-height="25">
|
||||||
|
<input type = "text" data-id = "input-tags" ></input>
|
||||||
|
<div data-width="5"></div>
|
||||||
|
<afx-switch data data-id = "blog-publish" data-width="30"></afx-switch>
|
||||||
|
<div data-width="5"></div>
|
||||||
|
</afx-hbox>
|
||||||
|
|
||||||
|
<div data-height="5"></div>
|
||||||
|
</afx-vbox>
|
||||||
|
</afx-hbox>
|
||||||
|
|
||||||
|
</afx-tab-container>
|
||||||
|
</afx-hbox>
|
||||||
|
</afx-app-window>
|
BIN
Blogger/build/release/Blogger.zip
Normal file
BIN
Blogger/build/release/Blogger.zip
Normal file
Binary file not shown.
282
Blogger/dialogs.ts
Normal file
282
Blogger/dialogs.ts
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||||
|
|
||||||
|
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||||
|
// License v3.0, see the LICENCE file for more information
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of
|
||||||
|
// the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||||
|
namespace OS {
|
||||||
|
export namespace application {
|
||||||
|
export namespace blogger {
|
||||||
|
declare var EasyMDE;
|
||||||
|
export class BloggerCategoryDialog extends OS.GUI.BasicDialog {
|
||||||
|
private tree: OS.GUI.tag.TreeViewTag;
|
||||||
|
private txtinput: HTMLInputElement;
|
||||||
|
constructor() {
|
||||||
|
super("BloggerCategoryDialog", BloggerCategoryDialog.scheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
super.main();
|
||||||
|
this.tree = this.find("tree") as OS.GUI.tag.TreeViewTag;
|
||||||
|
this.txtinput = this.find("txtinput") as HTMLInputElement;
|
||||||
|
|
||||||
|
(this.find("bt-ok") as OS.GUI.tag.ButtonTag).onbtclick = (e: any) => {
|
||||||
|
const sel = this.tree.selectedItem;
|
||||||
|
if (!sel) { return this.notify(__("Please select a parent category")); }
|
||||||
|
const seldata = sel.data;
|
||||||
|
const val = this.txtinput.value;
|
||||||
|
if ((val === "") && !this.data.selonly) { return this.notify(__("Please enter category name")); }
|
||||||
|
if (this.data.cat && (this.data.cat.id === seldata.id)) { return this.notify(__("Parent can not be the category itself")); }
|
||||||
|
if (this.handle) { this.handle({ p: seldata, value: val }); }
|
||||||
|
return this.quit();
|
||||||
|
};
|
||||||
|
|
||||||
|
(this.find("bt-cancel") as OS.GUI.tag.ButtonTag).onbtclick = (e: any) => {
|
||||||
|
return this.quit();
|
||||||
|
};
|
||||||
|
if (this.data && this.data.tree) {
|
||||||
|
if (this.data && this.data.cat) {
|
||||||
|
let seldata: GenericObject<any>;
|
||||||
|
this.txtinput.value = this.data.cat.name;
|
||||||
|
if (this.data.cat.pid === "0") {
|
||||||
|
seldata = this.data.tree;
|
||||||
|
} else {
|
||||||
|
seldata = this.findDataByID(this.data.cat.pid, this.data.tree.nodes);
|
||||||
|
}
|
||||||
|
if (seldata) { seldata.selected = true; }
|
||||||
|
}
|
||||||
|
this.tree.data = this.data.tree;
|
||||||
|
return this.tree.expandAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO set selected category name
|
||||||
|
|
||||||
|
findDataByID(id: number, list: GenericObject<any>[]) {
|
||||||
|
for (let data of list) {
|
||||||
|
if (data.id === id) { return data; }
|
||||||
|
if (data.nodes) {
|
||||||
|
this.findDataByID(id, data.nodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BloggerCategoryDialog.scheme = `\
|
||||||
|
<afx-app-window width='300' height='400'>
|
||||||
|
<afx-vbox padding="5">
|
||||||
|
<afx-label text="__(Pick a parent)" data-height="25" class="lbl-header" ></afx-label>
|
||||||
|
<afx-tree-view data-id="tree" ></afx-tree-view>
|
||||||
|
<afx-label text="__(Category name)" data-height="25" class="lbl-header" ></afx-label>
|
||||||
|
<input type="text" data-height="25" data-id = "txtinput"/ >
|
||||||
|
<afx-hbox data-height = '35'>
|
||||||
|
<div style=' text-align:right;'>
|
||||||
|
<afx-button data-id = "bt-ok" text = "__(Ok)"></afx-button>
|
||||||
|
<afx-button data-id = "bt-cancel" text = "__(Cancel)"></afx-button>
|
||||||
|
</div>
|
||||||
|
</afx-hbox>
|
||||||
|
</afx-vbox>
|
||||||
|
</afx-app-window>\
|
||||||
|
`;
|
||||||
|
|
||||||
|
// This dialog is use for cv section editing
|
||||||
|
|
||||||
|
export class BloggerCVSectionDiaglog extends OS.GUI.BasicDialog {
|
||||||
|
private editor: GenericObject<any>;
|
||||||
|
constructor() {
|
||||||
|
super("BloggerCVSectionDiaglog");
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
super.main();
|
||||||
|
this.editor = new EasyMDE({
|
||||||
|
autoDownloadFontAwesome: false,
|
||||||
|
element: this.find("contentarea"),
|
||||||
|
status: false,
|
||||||
|
toolbar: false
|
||||||
|
});
|
||||||
|
($((this.select('[class = "CodeMirror-scroll"]'))[0])).css("min-height", "50px");
|
||||||
|
($((this.select('[class="CodeMirror cm-s-paper CodeMirror-wrap"]'))[0])).css("min-height", "50px");
|
||||||
|
const inputs = this.select("[input-class='user-input']");
|
||||||
|
if (this.data && this.data.section) { for (let v of inputs) { ($(v)).val(this.data.section[(v as HTMLInputElement).name]); } }
|
||||||
|
if (this.data && this.data.section) { this.editor.value(this.data.section.content); }
|
||||||
|
(this.find("section-publish") as OS.GUI.tag.SwitchTag).swon = (this.data && this.data.section && Number(this.data.section.publish) ? true : false);
|
||||||
|
(this.find("bt-cv-sec-save") as OS.GUI.tag.ButtonTag).onbtclick = (e: any) => {
|
||||||
|
const data: GenericObject<any> = {};
|
||||||
|
for (let v of inputs) { data[(v as HTMLInputElement).name] = ($(v)).val(); }
|
||||||
|
data.content = this.editor.value();
|
||||||
|
if ((data.title === "") && (data.content === "")) { return this.notify(__("Title or content must not be blank")); }
|
||||||
|
//return @notify "Content must not be blank" if data.content is ""
|
||||||
|
if (this.data && this.data.section) { data.id = this.data.section.id; }
|
||||||
|
const val = (this.find("section-publish") as OS.GUI.tag.SwitchTag).swon;
|
||||||
|
if (val === true) {
|
||||||
|
data.publish = 1;
|
||||||
|
} else {
|
||||||
|
data.publish = 0;
|
||||||
|
}
|
||||||
|
if (this.handle) { this.handle(data); }
|
||||||
|
return this.quit();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.on("resize", () => this.resizeContent());
|
||||||
|
return this.resizeContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeContent() {
|
||||||
|
const container = this.find("editor-container");
|
||||||
|
const children = ($(".EasyMDEContainer", container)).children();
|
||||||
|
const cheight = ($(container)).height() - 30;
|
||||||
|
return ($(children[0])).css("height", cheight + "px");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BloggerCVSectionDiaglog.scheme = `\
|
||||||
|
<afx-app-window data-id = "blogger-cv-sec-win" apptitle="Porforlio section" width="450" height="400">
|
||||||
|
<afx-vbox padding="5">
|
||||||
|
<afx-hbox data-height = "30" >
|
||||||
|
<afx-label data-width= "70" text = "__(Title)"></afx-label>
|
||||||
|
<input type = "text" name="title" input-class = "user-input"></input>
|
||||||
|
</afx-hbox>
|
||||||
|
<afx-hbox data-height = "30" >
|
||||||
|
<afx-label text = "__(Subtitle)" data-width= "70"></afx-label>
|
||||||
|
<input type = "text" name="subtitle" input-class = "user-input"></input>
|
||||||
|
</afx-hbox>
|
||||||
|
<afx-hbox data-height = "30" >
|
||||||
|
<afx-label text = "__(Location)" data-width= "70"></afx-label>
|
||||||
|
<input type = "text" name="location" input-class = "user-input"></input>
|
||||||
|
</afx-hbox>
|
||||||
|
<afx-hbox data-height = "30" >
|
||||||
|
<afx-label text = "__(From)" data-width= "70"></afx-label>
|
||||||
|
<input type = "text" name="start" input-class = "user-input"></input>
|
||||||
|
<afx-label text = "To:" style="text-align:center;" data-width= "70"></afx-label>
|
||||||
|
<input type = "text" name="end" input-class = "user-input"></input>
|
||||||
|
</afx-hbox>
|
||||||
|
<afx-label data-height = "30" text = "Content" style = "margin-left:5px;"></afx-label>
|
||||||
|
<div data-id="editor-container">
|
||||||
|
<textarea name="content" data-id = "contentarea" ></textarea>
|
||||||
|
</div>
|
||||||
|
<div data-height = "35" style="text-align: right;">
|
||||||
|
<afx-switch data-id = "section-publish" data-width="30"></afx-switch>
|
||||||
|
<afx-button iconclass = "fa fa-save" data-id = "bt-cv-sec-save" text = "__(Save)"></afx-button>
|
||||||
|
</div>
|
||||||
|
</afx-vbox>
|
||||||
|
</afx-app-window>`;
|
||||||
|
|
||||||
|
// this dialog is for send mail
|
||||||
|
export class BloggerSendmailDiaglog extends OS.GUI.BasicDialog {
|
||||||
|
static template: string;
|
||||||
|
private maillinglist: OS.GUI.tag.StackMenuTag;
|
||||||
|
// TODO: convert to SQLite handle
|
||||||
|
private subdb: API.VFS.BaseFileHandle;
|
||||||
|
constructor() {
|
||||||
|
super("BloggerSendmailDiaglog");
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
super.main();
|
||||||
|
this.maillinglist = this.find("email-list") as OS.GUI.tag.StackMenuTag;
|
||||||
|
const title = (new RegExp("^#+(.*)\n", "g")).exec(this.data.content);
|
||||||
|
(this.find("mail-title") as HTMLInputElement).value = title[1];
|
||||||
|
const content = (this.data.content.substring(0, 500)) + "...";
|
||||||
|
(this.find("contentarea") as HTMLTextAreaElement).value = BloggerSendmailDiaglog.template.format(this.data.id, content);
|
||||||
|
const mlist = this.data.mails.map((el)=> {
|
||||||
|
return {
|
||||||
|
text: el.name,
|
||||||
|
email: el.email,
|
||||||
|
switch: true,
|
||||||
|
checked: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(mlist);
|
||||||
|
this.maillinglist.items = mlist;
|
||||||
|
|
||||||
|
return (this.find("bt-sendmail") as OS.GUI.tag.ButtonTag).onbtclick = (e: any) => {
|
||||||
|
const items = this.maillinglist.items;
|
||||||
|
const emails = [];
|
||||||
|
for (let v of items) {
|
||||||
|
if (v.checked === true) {
|
||||||
|
emails.push(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emails.length === 0) { return this.notify(__("No email selected")); }
|
||||||
|
// send the email
|
||||||
|
const data = {
|
||||||
|
path: `${this.meta().path}/api/sendmail.lua`,
|
||||||
|
parameters: {
|
||||||
|
to: emails,
|
||||||
|
title: (this.find("mail-title") as HTMLInputElement).value,
|
||||||
|
content: (this.find("contentarea") as HTMLTextAreaElement).value,
|
||||||
|
user: (this.find("mail-user") as HTMLInputElement).value,
|
||||||
|
password: (this.find("mail-password") as HTMLInputElement).value,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return this._api.apigateway(data, false)
|
||||||
|
.then((d) => {
|
||||||
|
if (d.error) {
|
||||||
|
const str = d.result.join(',');
|
||||||
|
return this.notify(__("Unable to send mail to: {0}", str)); }
|
||||||
|
return this.quit();
|
||||||
|
}).catch((e) => {
|
||||||
|
return this.error(__("Error sending mail: {0}", e.toString()), e);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BloggerSendmailDiaglog.scheme = `\
|
||||||
|
<afx-app-window data-id = "blogger-send-mail-win" apptitle="Send mail" width="600" height="400" resizable = "false">
|
||||||
|
<afx-hbox padding="5">
|
||||||
|
<afx-stack-menu data-width="200" data-id="email-list"></afx-stack-menu>
|
||||||
|
<afx-resizer data-width="3"></afx-resizer>
|
||||||
|
<afx-vbox >
|
||||||
|
<afx-label data-height="20" text = "__(Title)"></afx-label>
|
||||||
|
<input type = "text" data-height="25" name="title" data-id = "mail-title"></input>
|
||||||
|
<afx-label data-height = "20" text = "__(Content)" ></afx-label>
|
||||||
|
<textarea name="content" data-id = "contentarea" ></textarea>
|
||||||
|
<afx-label data-height="20" text = "__(IO Hub mail username/password)"></afx-label>
|
||||||
|
<afx-hbox data-height="25">
|
||||||
|
<input type = "text" name="username" data-id = "mail-user"></input>
|
||||||
|
<input type = "password" name="password" data-id = "mail-password"></input>
|
||||||
|
</afx-hbox>
|
||||||
|
<afx-hbox data-height = "30">
|
||||||
|
<div></div>
|
||||||
|
<afx-button iconclass = "fa fa-paper-plane" data-id = "bt-sendmail" data-width="content" text = "__(Send)"></afx-button>
|
||||||
|
</afx-hbox>
|
||||||
|
</afx-vbox>
|
||||||
|
</afx-hbox>
|
||||||
|
</afx-app-window>`;
|
||||||
|
|
||||||
|
|
||||||
|
BloggerSendmailDiaglog.template = `\
|
||||||
|
Hello,
|
||||||
|
|
||||||
|
Dany LE has just published a new post on his blog: https://blog.iohub.dev/post/id/{0}
|
||||||
|
|
||||||
|
==========
|
||||||
|
{1}
|
||||||
|
==========
|
||||||
|
|
||||||
|
|
||||||
|
Read the full article via:
|
||||||
|
https://blog.iohub.dev/post/id/{0}
|
||||||
|
|
||||||
|
You receive this email because you have been subscribed to his blog.
|
||||||
|
|
||||||
|
Have a nice day,
|
||||||
|
|
||||||
|
Sent from Blogger, an AntOS application\
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
Blogger/main.css
Normal file
78
Blogger/main.css
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
afx-app-window[data-id="blogger-win"] afx-hbox[data-id="user-container"] afx-label i.label-text{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window .lbl-header i.label-text{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-hbox[data-id="cv-container"] .cat-header{
|
||||||
|
border-bottom: 1px solid #cbcbcb;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] .cv-side-bar-btn
|
||||||
|
{
|
||||||
|
text-align: right;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "cv-sec-list"] > .list-container > ul .afx-cv-sec-title .label-text{
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "cv-sec-list"] afx-blogger-cvsection-item afx-label {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
}
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "cv-sec-list"] afx-blogger-cvsection-item p {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "cv-sec-list"] afx-blogger-cvsection-item .afx-cv-sec-period,
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "cv-sec-list"] afx-blogger-cvsection-item .afx-cv-sec-loc {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "cv-sec-list"] afx-blogger-cvsection-item afx-cv-sec-content{
|
||||||
|
text-align: justify;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "cv-sec-list"] > div.list-container > ul li.selected {
|
||||||
|
border: 1px solid #116cd6;
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "cv-sec-list"] .closable::before{
|
||||||
|
content: "\f014";
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "cv-sec-list"] .period-end::before{
|
||||||
|
content: "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id ='blogger-win'] .editor-toolbar{
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "blog-list"] > div.list-container > ul li afx-label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "blog-list"] > div.list-container > ul .afx-blogpost-title .label-text{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "blog-list"] > div.list-container > ul .blog-dates .label-text{
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
afx-app-window[data-id="blogger-win"] afx-list-view[ data-id = "blog-list"] > div.list-container > ul li.selected {
|
||||||
|
background-color: #116cd6;
|
||||||
|
color:white;
|
||||||
|
}
|
925
Blogger/main.ts
Normal file
925
Blogger/main.ts
Normal file
@ -0,0 +1,925 @@
|
|||||||
|
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||||
|
|
||||||
|
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||||
|
// License v3.0, see the LICENCE file for more information
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of
|
||||||
|
// the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||||
|
namespace OS {
|
||||||
|
export namespace application {
|
||||||
|
declare function renderMathInElement(el: HTMLElement):void;
|
||||||
|
declare var EasyMDE;
|
||||||
|
export class Blogger extends BaseApplication {
|
||||||
|
|
||||||
|
private user: GenericObject<any>;
|
||||||
|
private cvlist: GUI.tag.TreeViewTag;
|
||||||
|
private inputtags: HTMLInputElement;
|
||||||
|
private bloglist: GUI.tag.ListViewTag;
|
||||||
|
private seclist: GUI.tag.ListViewTag;
|
||||||
|
private tabcontainer: GUI.tag.TabContainerTag;
|
||||||
|
private editor: GenericObject<any>;
|
||||||
|
private previewOn: boolean;
|
||||||
|
// datatbase objects
|
||||||
|
private dbhandle: API.VFS.BaseFileHandle;
|
||||||
|
// database handles
|
||||||
|
private userdb: API.VFS.BaseFileHandle;
|
||||||
|
private cvcatdb: API.VFS.BaseFileHandle;
|
||||||
|
private cvsecdb: API.VFS.BaseFileHandle;
|
||||||
|
private blogdb: API.VFS.BaseFileHandle;
|
||||||
|
private subdb: API.VFS.BaseFileHandle;
|
||||||
|
|
||||||
|
private last_ctime: number;
|
||||||
|
|
||||||
|
constructor(args: any) {
|
||||||
|
super("Blogger", args);
|
||||||
|
this.previewOn = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async init_db() {
|
||||||
|
try {
|
||||||
|
const f = await this.openDialog("FileDialog",{
|
||||||
|
title: __("Open/create new database"),
|
||||||
|
file: "Untitled.db"
|
||||||
|
});
|
||||||
|
var d_1 = f.file.path.asFileHandle();
|
||||||
|
if(f.file.type==="file") {
|
||||||
|
d_1=d_1.parent();
|
||||||
|
}
|
||||||
|
const target=`${d_1.path}/${f.name}`.asFileHandle();
|
||||||
|
this.dbhandle=`sqlite://${target.genealogy.join("/")}`.asFileHandle();
|
||||||
|
const tables = await this.dbhandle.read();
|
||||||
|
/**
|
||||||
|
* Init following tables if not exist:
|
||||||
|
* - user
|
||||||
|
* - cvcat
|
||||||
|
* - cvsec
|
||||||
|
* - blogdb
|
||||||
|
*/
|
||||||
|
if(!tables.user)
|
||||||
|
{
|
||||||
|
this.dbhandle.cache = {
|
||||||
|
address: "TEXT",
|
||||||
|
Phone: "TEXT",
|
||||||
|
shortbiblio: "TEXT",
|
||||||
|
fullname: "TEXT",
|
||||||
|
email: "TEXT",url: "TEXT",
|
||||||
|
photo: "TEXT"
|
||||||
|
}
|
||||||
|
const r = await this.dbhandle.write("user");
|
||||||
|
if(r.error)
|
||||||
|
{
|
||||||
|
throw new Error(r.error as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!tables.cv_cat)
|
||||||
|
{
|
||||||
|
this.dbhandle.cache = {
|
||||||
|
publish: "NUMERIC",
|
||||||
|
name: "TEXT",
|
||||||
|
pid: "NUMERIC"
|
||||||
|
}
|
||||||
|
const r = await this.dbhandle.write("cv_cat");
|
||||||
|
if(r.error)
|
||||||
|
{
|
||||||
|
throw new Error(r.error as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!tables.cv_sections)
|
||||||
|
{
|
||||||
|
this.dbhandle.cache = {
|
||||||
|
title: "TEXT",
|
||||||
|
start: "NUMERIC",
|
||||||
|
location: "TEXT",
|
||||||
|
end: "NUMERIC",
|
||||||
|
content: "TEXT",
|
||||||
|
subtitle: "TEXT",
|
||||||
|
publish: "NUMERIC",
|
||||||
|
cid: "NUMERIC"
|
||||||
|
}
|
||||||
|
const r = await this.dbhandle.write("cv_sections");
|
||||||
|
if(r.error)
|
||||||
|
{
|
||||||
|
throw new Error(r.error as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!tables.blogs)
|
||||||
|
{
|
||||||
|
this.dbhandle.cache = {
|
||||||
|
tags: "TEXT",
|
||||||
|
content: "TEXT",
|
||||||
|
utime: "NUMERIC",
|
||||||
|
rendered: "TEXT",
|
||||||
|
title: "TEXT",
|
||||||
|
utimestr: "TEXT",
|
||||||
|
ctime: "NUMERIC",
|
||||||
|
ctimestr: "TEXT",
|
||||||
|
publish: "INTEGER DEFAULT 0",
|
||||||
|
}
|
||||||
|
const r = await this.dbhandle.write("blogs");
|
||||||
|
if(r.error)
|
||||||
|
{
|
||||||
|
throw new Error(r.error as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!tables.st_similarity)
|
||||||
|
{
|
||||||
|
this.dbhandle.cache = {
|
||||||
|
pid: "NUMERIC",
|
||||||
|
sid: "NUMERIC",
|
||||||
|
score: "NUMERIC"
|
||||||
|
}
|
||||||
|
const r = await this.dbhandle.write("st_similarity");
|
||||||
|
if(r.error)
|
||||||
|
{
|
||||||
|
throw new Error(r.error as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!tables.subscribers)
|
||||||
|
{
|
||||||
|
this.dbhandle.cache = {
|
||||||
|
name: "TEXT",
|
||||||
|
email: "TEXT"
|
||||||
|
}
|
||||||
|
const r = await this.dbhandle.write("subscribers");
|
||||||
|
if(r.error)
|
||||||
|
{
|
||||||
|
throw new Error(r.error as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.userdb = `${this.dbhandle.path}@user`.asFileHandle();
|
||||||
|
this.cvcatdb = `${this.dbhandle.path}@cv_cat`.asFileHandle();
|
||||||
|
this.cvsecdb = `${this.dbhandle.path}@cv_sections`.asFileHandle();
|
||||||
|
this.blogdb = `${this.dbhandle.path}@blogs`.asFileHandle();
|
||||||
|
this.subdb = `${this.dbhandle.path}@subscribers`.asFileHandle();
|
||||||
|
|
||||||
|
this.last_ctime = 0;
|
||||||
|
this.bloglist.data = [];
|
||||||
|
this.loadBlogs();
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
this.error(__("Unable to init database file: {0}",e.toString()),e);
|
||||||
|
this.dbhandle = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
menu() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
text: "__(Open/Create database)",
|
||||||
|
onmenuselect: (e) => {
|
||||||
|
this.init_db();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
this.user = {};
|
||||||
|
this.cvlist = this.find("cv-list") as GUI.tag.TreeViewTag;
|
||||||
|
this.cvlist.ontreeselect = (d) => {
|
||||||
|
if (!d) { return; }
|
||||||
|
const {
|
||||||
|
data
|
||||||
|
} = d.data.item;
|
||||||
|
return this.CVSectionByCID(Number(data.id));
|
||||||
|
};
|
||||||
|
|
||||||
|
this.inputtags = this.find("input-tags") as HTMLInputElement;
|
||||||
|
this.bloglist = this.find("blog-list") as GUI.tag.ListViewTag;
|
||||||
|
this.seclist = this.find("cv-sec-list") as GUI.tag.ListViewTag;
|
||||||
|
|
||||||
|
let el = this.find("photo") as HTMLInputElement;
|
||||||
|
$(el)
|
||||||
|
.on("click", async (e: any) => {
|
||||||
|
try {
|
||||||
|
const ret = await this.openDialog("FileDialog", {
|
||||||
|
title: __("Select image file"),
|
||||||
|
mimes: ["image/.*"]
|
||||||
|
});
|
||||||
|
return el.value = ret.file.path;
|
||||||
|
} catch (e) {
|
||||||
|
return this.error(__("Unable to get file"), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.tabcontainer = this.find("tabcontainer") as GUI.tag.TabContainerTag;
|
||||||
|
this.tabcontainer.ontabselect = (e) => {
|
||||||
|
return this.fetchData((e.data.container as GUI.tag.TileLayoutTag).aid);
|
||||||
|
};
|
||||||
|
|
||||||
|
(this.find("bt-user-save") as GUI.tag.ButtonTag).onbtclick = (e: any) => {
|
||||||
|
return this.saveUser();
|
||||||
|
};
|
||||||
|
|
||||||
|
(this.find("blog-load-more") as GUI.tag.ButtonTag).onbtclick = (e) => {
|
||||||
|
this.loadBlogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
(this.find("cv-cat-add") as GUI.tag.ButtonTag).onbtclick = async (e: any) => {
|
||||||
|
try {
|
||||||
|
const tree = await this.fetchCVCat();
|
||||||
|
const d = await this.openDialog(new blogger.BloggerCategoryDialog(), {
|
||||||
|
title: __("Add category"),
|
||||||
|
tree
|
||||||
|
});
|
||||||
|
this.cvcatdb.cache = {
|
||||||
|
name: d.value,
|
||||||
|
pid: d.p.id,
|
||||||
|
publish: 1
|
||||||
|
};
|
||||||
|
const r = await this.cvcatdb.write(undefined);
|
||||||
|
if(r.error)
|
||||||
|
{
|
||||||
|
throw new Error(r.error as string);
|
||||||
|
}
|
||||||
|
await this.refreshCVCat();
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
this.error(__("cv-cat-add: {0}", e.toString()), e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(this.find("cv-cat-edit") as GUI.tag.ButtonTag).onbtclick = async (e: any) => {
|
||||||
|
try {
|
||||||
|
const sel = this.cvlist.selectedItem;
|
||||||
|
if (!sel) { return; }
|
||||||
|
const cat = sel.data;
|
||||||
|
if (!cat) { return; }
|
||||||
|
const tree = await this.fetchCVCat();
|
||||||
|
const d = await this.openDialog(new blogger.BloggerCategoryDialog(), {
|
||||||
|
title: __("Edit category"),
|
||||||
|
tree, cat
|
||||||
|
});
|
||||||
|
const handle = cat.$vfs;
|
||||||
|
handle.cache = {
|
||||||
|
id: cat.id,
|
||||||
|
publish: cat.publish,
|
||||||
|
pid: d.p.id,
|
||||||
|
name: d.value
|
||||||
|
};
|
||||||
|
|
||||||
|
const r = await handle.write(undefined);
|
||||||
|
if(r.error)
|
||||||
|
{
|
||||||
|
throw new Error(r.error as string);
|
||||||
|
}
|
||||||
|
await this.refreshCVCat();
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
this.error(__("cv-cat-edit: {0}", e.toString()), e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(this.find("cv-cat-del") as GUI.tag.ButtonTag).onbtclick = async (e: any) => {
|
||||||
|
try {
|
||||||
|
const sel = this.cvlist.selectedItem;
|
||||||
|
if (!sel) { return; }
|
||||||
|
const cat = sel.data;
|
||||||
|
if (!cat) { return; }
|
||||||
|
const d = await this.openDialog("YesNoDialog", {
|
||||||
|
title: __("Delete category"),
|
||||||
|
iconclass: "fa fa-question-circle",
|
||||||
|
text: __("Do you really want to delete: {0}?", cat.name)
|
||||||
|
});
|
||||||
|
if (!d) { return; }
|
||||||
|
await this.deleteCVCat(cat);
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
this.error(__("cv-cat-del: {0}", e.toString()), e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(this.find("cv-sec-add") as GUI.tag.ButtonTag).onbtclick = async (e: any) => {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const sel = this.cvlist.selectedItem;
|
||||||
|
if (!sel) { return; }
|
||||||
|
const cat = sel.data;
|
||||||
|
if (!cat || (cat.id === "0")) { return this.toast(__("Please select a category")); }
|
||||||
|
const d = await this.openDialog(new blogger.BloggerCVSectionDiaglog(), {
|
||||||
|
title: __("New section entry for {0}", cat.name)
|
||||||
|
});
|
||||||
|
d.cid = Number(cat.id);
|
||||||
|
d.start = Number(d.start);
|
||||||
|
d.end = Number(d.end);
|
||||||
|
this.cvsecdb.cache = d;
|
||||||
|
// d.publish = 1
|
||||||
|
const r = await this.cvsecdb.write(undefined);
|
||||||
|
if(r.error)
|
||||||
|
{
|
||||||
|
throw new Error(r.error as string);
|
||||||
|
}
|
||||||
|
await this.CVSectionByCID(Number(cat.id));
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
this.error(__("cv-sec-add: {0}", e.toString()), e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(this.find("cv-sec-move") as GUI.tag.ButtonTag).onbtclick = async (e: any) => {
|
||||||
|
try {
|
||||||
|
const sel = this.seclist.selectedItem;
|
||||||
|
if (!sel) { return this.toast(__("Please select a section to move")); }
|
||||||
|
const sec = sel.data;
|
||||||
|
const handle = sec.$vfs;
|
||||||
|
console.log(handle);
|
||||||
|
const tree = await this.fetchCVCat();
|
||||||
|
const d = await this.openDialog(new blogger.BloggerCategoryDialog(), {
|
||||||
|
title: __("Move to"),
|
||||||
|
tree,
|
||||||
|
selonly: true
|
||||||
|
});
|
||||||
|
handle.cache = {
|
||||||
|
id: sec.id,
|
||||||
|
cid: d.p.id
|
||||||
|
};
|
||||||
|
const r = await handle.write(undefined);
|
||||||
|
if(r.error)
|
||||||
|
{
|
||||||
|
throw new Error(r.error as string);
|
||||||
|
}
|
||||||
|
await this.CVSectionByCID(sec.cid);
|
||||||
|
this.seclist.unselect();
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
this.error(__("cv-sec-move: {0}", e.toString()), e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(this.find("cv-sec-edit") as GUI.tag.ButtonTag).onbtclick = async (e: any) => {
|
||||||
|
try {
|
||||||
|
const sel = this.seclist.selectedItem;
|
||||||
|
if (!sel) { return this.toast(__("Please select a section to edit")); }
|
||||||
|
const sec = sel.data;
|
||||||
|
const d = await this.openDialog(new blogger.BloggerCVSectionDiaglog(), {
|
||||||
|
title: __("Modify section entry"),
|
||||||
|
section: sec
|
||||||
|
});
|
||||||
|
d.cid = Number(sec.cid);
|
||||||
|
d.start = Number(d.start);
|
||||||
|
d.end = Number(d.end);
|
||||||
|
|
||||||
|
const handle = sec.$vfs;
|
||||||
|
handle.cache = d;
|
||||||
|
//d.publish = Number sec.publish
|
||||||
|
const r = await handle.write(undefined);
|
||||||
|
if(r.error)
|
||||||
|
{
|
||||||
|
throw new Error(r.error as string);
|
||||||
|
}
|
||||||
|
await this.CVSectionByCID(Number(sec.cid));
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
this.error(__("cv-sec-edit: {0}", e.toString()), e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.seclist.onitemclose = (evt) => {
|
||||||
|
if (!evt) { return; }
|
||||||
|
const data = evt.data.item.data;
|
||||||
|
this.openDialog("YesNoDialog", {
|
||||||
|
iconclass: "fa fa-question-circle",
|
||||||
|
text: __("Do you really want to delete: {0}?", data.title)
|
||||||
|
}).then(async (b: any) => {
|
||||||
|
if (!b) { return; }
|
||||||
|
try {
|
||||||
|
const r = await this.cvsecdb.remove({
|
||||||
|
where: {
|
||||||
|
id: data.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(r.error)
|
||||||
|
{
|
||||||
|
throw new Error(r.error as string);
|
||||||
|
}
|
||||||
|
return this.seclist.delete(evt.data.item);
|
||||||
|
} catch(e) {
|
||||||
|
return this.error(__("Cannot delete the section: {0}",e.toString()),e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.editor = new EasyMDE({
|
||||||
|
element: this.find("markarea"),
|
||||||
|
autoDownloadFontAwesome: false,
|
||||||
|
autofocus: true,
|
||||||
|
tabSize: 4,
|
||||||
|
indentWithTabs: true,
|
||||||
|
toolbar: [
|
||||||
|
{
|
||||||
|
name: __("New"),
|
||||||
|
className: "fa fa-file",
|
||||||
|
action: (e: any) => {
|
||||||
|
this.bloglist.unselect();
|
||||||
|
return this.clearEditor();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: __("Save"),
|
||||||
|
className: "fa fa-save",
|
||||||
|
action: (e: any) => {
|
||||||
|
return this.saveBlog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, "|", "bold", "italic", "heading", "|", "quote", "code",
|
||||||
|
"unordered-list", "ordered-list", "|", "link",
|
||||||
|
"image", "table", "horizontal-rule",
|
||||||
|
{
|
||||||
|
name: "image",
|
||||||
|
className: "fa fa-file-image-o",
|
||||||
|
action: (_) => {
|
||||||
|
return this.openDialog("FileDialog", {
|
||||||
|
title: __("Select image file"),
|
||||||
|
mimes: ["image/.*"]
|
||||||
|
}).then((d) => {
|
||||||
|
return d.file.path.asFileHandle().publish()
|
||||||
|
.then((r: { result: any; }) => {
|
||||||
|
const doc = this.editor.codemirror.getDoc();
|
||||||
|
return doc.replaceSelection(``);
|
||||||
|
}).catch((e: any) => this.error(__("Cannot export file for embedding to text"), e));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Youtube",
|
||||||
|
className: "fa fa-youtube",
|
||||||
|
action: (e: any) => {
|
||||||
|
const doc = this.editor.codemirror.getDoc();
|
||||||
|
return doc.replaceSelection("[[youtube:]]");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"|",
|
||||||
|
{
|
||||||
|
name: __("Preview"),
|
||||||
|
className: "fa fa-eye no-disable",
|
||||||
|
action: (e: any) => {
|
||||||
|
this.previewOn = !this.previewOn;
|
||||||
|
EasyMDE.togglePreview(e);
|
||||||
|
///console.log @select ".editor-preview editor-preview-active"
|
||||||
|
renderMathInElement(this.find("editor-container"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"|",
|
||||||
|
{
|
||||||
|
name: __("Send mail"),
|
||||||
|
className: "fa fa-paper-plane",
|
||||||
|
action: async (e: any) => {
|
||||||
|
try {
|
||||||
|
const d = await this.subdb.read();
|
||||||
|
const sel = this.bloglist.selectedItem;
|
||||||
|
if (!sel) { return this.error(__("No post selected")); }
|
||||||
|
const data = sel.data;
|
||||||
|
await this.openDialog(new blogger.BloggerSendmailDiaglog(), {
|
||||||
|
title: __("Send mail"),
|
||||||
|
content: this.editor.value(),
|
||||||
|
mails: d,
|
||||||
|
id: data.id
|
||||||
|
});
|
||||||
|
this.toast(__("Emails sent"));
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
this.error(__("Error sending mails: {0}", e.toString()), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"|",
|
||||||
|
{
|
||||||
|
name: __("TFIDF analyse"),
|
||||||
|
className: "fa fa-area-chart",
|
||||||
|
action: async (e: any) => {
|
||||||
|
try {
|
||||||
|
const q = await this.openDialog("PromptDialog",{
|
||||||
|
title: __("TFIDF Analyse"),
|
||||||
|
text: __("Max number of related posts to keep per post?"),
|
||||||
|
value: "5"
|
||||||
|
});
|
||||||
|
const data = {
|
||||||
|
path: `${this.meta().path}/api/ai/analyse.lua`,
|
||||||
|
parameters: {
|
||||||
|
dbpath: this.dbhandle.info.file.path,
|
||||||
|
top: parseInt(q)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const d = await this._api.apigateway(data, false);
|
||||||
|
if (d.error) {
|
||||||
|
throw new Error(d.error);
|
||||||
|
}
|
||||||
|
this.toast(d.result);
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
this.error(__("Error analysing posts: {0}", e.toString()), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
this.bloglist.onlistselect = async (e: any) => {
|
||||||
|
const el = this.bloglist.selectedItem;
|
||||||
|
if (!el) { return; }
|
||||||
|
const sel = el.data;
|
||||||
|
if (!sel) { return; }
|
||||||
|
try {
|
||||||
|
const result=await this.blogdb.read({
|
||||||
|
where: {
|
||||||
|
id: Number(sel.id)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(!result || result.length == 0)
|
||||||
|
{
|
||||||
|
throw new Error(__("No record found for ID {}", sel.id).__());
|
||||||
|
}
|
||||||
|
const r = result[0];
|
||||||
|
this.editor.value(r.content);
|
||||||
|
this.inputtags.value=r.tags;
|
||||||
|
return (this.find("blog-publish") as GUI.tag.SwitchTag).swon=Number(r.publish)? true:false;
|
||||||
|
}
|
||||||
|
catch(e_1) {
|
||||||
|
return this.error(__("Cannot fetch the entry content"),e_1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.bloglist.onitemclose = (e) => {
|
||||||
|
if (!e) { return; }
|
||||||
|
const el = e.data.item;
|
||||||
|
const data = el.data;
|
||||||
|
this.openDialog("YesNoDialog", {
|
||||||
|
title: __("Delete a post"),
|
||||||
|
iconclass: "fa fa-question-circle",
|
||||||
|
text: __("Do you really want to delete this post ?")
|
||||||
|
}).then(async (b: any) => {
|
||||||
|
if (!b) { return; }
|
||||||
|
const r = await this.blogdb.remove({
|
||||||
|
where: {
|
||||||
|
id: Number(data.id)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(r.error)
|
||||||
|
{
|
||||||
|
throw new Error(r.error as string);
|
||||||
|
}
|
||||||
|
this.bloglist.delete(el);
|
||||||
|
this.bloglist.unselect();
|
||||||
|
return this.clearEditor();
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
this.bindKey("CTRL-S", () => {
|
||||||
|
const sel = this.tabcontainer.selectedTab;
|
||||||
|
if (!sel || ((sel.container as GUI.tag.TileLayoutTag).aid !== "blog-container")) { return; }
|
||||||
|
return this.saveBlog();
|
||||||
|
});
|
||||||
|
this.on("resize", () => {
|
||||||
|
return this.resizeContent();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.resizeContent();
|
||||||
|
return this.init_db();
|
||||||
|
}
|
||||||
|
// @fetchData 0
|
||||||
|
// USER TAB
|
||||||
|
private fetchData(idx: any) {
|
||||||
|
switch (idx) {
|
||||||
|
case "user-container": //user info
|
||||||
|
return this.userdb.read()
|
||||||
|
.then((d) => {
|
||||||
|
if(!d || d.length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.user = d[0];
|
||||||
|
const inputs = this.select("[input-class='user-input']");
|
||||||
|
return inputs.map((i,v) => ($(v)).val(this.user[(v as HTMLInputElement).name]));
|
||||||
|
}).catch((e: any) => this.error(__("Cannot fetch user data"), e));
|
||||||
|
case "cv-container": // category
|
||||||
|
return this.refreshCVCat();
|
||||||
|
default:
|
||||||
|
this.last_ctime = 0;
|
||||||
|
this.bloglist.data = [];
|
||||||
|
return this.loadBlogs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveUser() {
|
||||||
|
try {
|
||||||
|
const inputs = this.select("[input-class='user-input']");
|
||||||
|
for (let v of inputs) { this.user[(v as HTMLInputElement).name] = ($(v)).val(); }
|
||||||
|
if (!this.user.fullname || (this.user.fullname === "")) { return this.toast(__("Full name must be entered")); }
|
||||||
|
//console.log @user
|
||||||
|
let fp = this.userdb;
|
||||||
|
if(this.user && this.user.id)
|
||||||
|
{
|
||||||
|
fp = `${this.userdb.path}@${this.user.id}`.asFileHandle();
|
||||||
|
}
|
||||||
|
fp.cache = this.user
|
||||||
|
const r = await fp.write(undefined);
|
||||||
|
if(r.error)
|
||||||
|
{
|
||||||
|
throw new Error(r.error as string);
|
||||||
|
}
|
||||||
|
if(!this.user.id)
|
||||||
|
{
|
||||||
|
this.user.id = r.result;
|
||||||
|
}
|
||||||
|
this.toast(__("User data updated"));
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
this.error(__("Cannot save user data: {0}", e.toString()), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// PORFOLIO TAB
|
||||||
|
private refreshCVCat() {
|
||||||
|
return this.fetchCVCat().then((data: any) => {
|
||||||
|
this.cvlist.data = data;
|
||||||
|
return this.cvlist.expandAll();
|
||||||
|
}).catch((e: any) => this.error(__("Unable to load categories"), e));
|
||||||
|
}
|
||||||
|
|
||||||
|
private fetchCVCat() {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const data = {
|
||||||
|
text: "Porfolio",
|
||||||
|
id: 0,
|
||||||
|
nodes: []
|
||||||
|
};
|
||||||
|
const filter = {
|
||||||
|
order: ["name$asc"]
|
||||||
|
};
|
||||||
|
const d = await this.cvcatdb.read(filter);
|
||||||
|
this.catListToTree(d, data, 0);
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
reject(__e(e));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//it = (@cvlist.find "pid", "2")[0]
|
||||||
|
//@cvlist.set "selectedItem", it
|
||||||
|
|
||||||
|
private catListToTree(table: GenericObject<any>[], data: GenericObject<any>, id: number) {
|
||||||
|
const result = table.filter((e) => {
|
||||||
|
return e.pid == id
|
||||||
|
});
|
||||||
|
if (result.length === 0) {
|
||||||
|
return data.nodes = null;
|
||||||
|
}
|
||||||
|
for(let v of result)
|
||||||
|
{
|
||||||
|
v.nodes = [];
|
||||||
|
v.text = v.name;
|
||||||
|
this.catListToTree(table, v, v.id);
|
||||||
|
data.nodes.push(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private deleteCVCat(cat: GenericObject<any>): Promise<any> {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
let v: any;
|
||||||
|
const ids = [];
|
||||||
|
var func = function (c: GenericObject<any>) {
|
||||||
|
ids.push(c.id);
|
||||||
|
if (c.nodes) {
|
||||||
|
c.nodes.map((v) => func(v));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
func(cat);
|
||||||
|
// delete all content
|
||||||
|
let r = await this.cvsecdb.remove({
|
||||||
|
where: {
|
||||||
|
$or: {
|
||||||
|
cid: ids
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(r.error)
|
||||||
|
{
|
||||||
|
throw new Error(r.error as string);
|
||||||
|
}
|
||||||
|
r = await this.cvcatdb.remove({
|
||||||
|
where: {
|
||||||
|
$or: {
|
||||||
|
id: ids
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(r.error)
|
||||||
|
{
|
||||||
|
throw new Error(r.error as string);
|
||||||
|
}
|
||||||
|
await this.refreshCVCat();
|
||||||
|
this.seclist.data = [];
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
reject(__e(e));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private CVSectionByCID(cid: number): Promise<any> {
|
||||||
|
return new Promise( async (resolve, reject)=> {
|
||||||
|
try {
|
||||||
|
const d = await this.cvsecdb.read({
|
||||||
|
where: {cid},
|
||||||
|
order: [ "start$desc" ]
|
||||||
|
});
|
||||||
|
const items = [];
|
||||||
|
(this.find("cv-sec-status") as GUI.tag.LabelTag).text = __("Found {0} sections", d.length);
|
||||||
|
for (let v of d) {
|
||||||
|
v.closable = true;
|
||||||
|
v.tag = "afx-blogger-cvsection-item";
|
||||||
|
v.start = Number(v.start);
|
||||||
|
v.end = Number(v.end);
|
||||||
|
if (v.start < 1000) { v.start = undefined; }
|
||||||
|
if (v.end < 1000) { v.end = undefined; }
|
||||||
|
items.push(v);
|
||||||
|
}
|
||||||
|
this.seclist.data = items;
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
reject(__e(e));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// blog
|
||||||
|
private async saveBlog() {
|
||||||
|
try {
|
||||||
|
let sel = undefined;
|
||||||
|
const selel = this.bloglist.selectedItem;
|
||||||
|
if (selel) { sel = selel.data; }
|
||||||
|
const tags = this.inputtags.value;
|
||||||
|
const content = this.editor.value();
|
||||||
|
const title = (new RegExp("^#+(.*)\n", "g")).exec(content);
|
||||||
|
if (!title || (title.length !== 2)) { return this.toast(__("Please insert a title in the text: beginning with heading")); }
|
||||||
|
if (tags === "") { return this.toast(__("Please enter tags")); }
|
||||||
|
const d = new Date();
|
||||||
|
const data: GenericObject<any> = {
|
||||||
|
content,
|
||||||
|
title: title[1].trim(),
|
||||||
|
tags,
|
||||||
|
ctime: sel ? sel.ctime : d.timestamp(),
|
||||||
|
ctimestr: sel ? sel.ctimestr : d.toString(),
|
||||||
|
utime: d.timestamp(),
|
||||||
|
utimestr: d.toString(),
|
||||||
|
rendered: this.process(this.editor.options.previewRender(content)),
|
||||||
|
publish: (this.find("blog-publish") as GUI.tag.SwitchTag).swon ? 1 : 0
|
||||||
|
};
|
||||||
|
let handle = this.blogdb;
|
||||||
|
if (sel) {
|
||||||
|
data.id = sel.id;
|
||||||
|
handle = sel.$vfs;
|
||||||
|
}
|
||||||
|
//save the data
|
||||||
|
handle.cache = data;
|
||||||
|
const r = await handle.write(undefined);
|
||||||
|
if(r.error)
|
||||||
|
{
|
||||||
|
throw new Error(r.error as string);
|
||||||
|
}
|
||||||
|
if(!sel)
|
||||||
|
{
|
||||||
|
this.last_ctime = 0;
|
||||||
|
this.bloglist.data = [];
|
||||||
|
await this.loadBlogs();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//data.text = data.title;
|
||||||
|
selel.data.utime = data.utime;
|
||||||
|
selel.data.utimestr = data.utimestr;
|
||||||
|
selel.data = selel.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
this.error(__("Cannot save blog: {0}", e.toString()), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private process(text: string) {
|
||||||
|
// find video tag and rendered it
|
||||||
|
let found: any;
|
||||||
|
const embed = (id: any) => `\
|
||||||
|
<iframe
|
||||||
|
class = "embeded-video"
|
||||||
|
width="560" height="315"
|
||||||
|
src="https://www.youtube.com/embed/${id}"
|
||||||
|
frameborder="0" allow="encrypted-media" allowfullscreen
|
||||||
|
></iframe>\
|
||||||
|
`;
|
||||||
|
const re = /\[\[youtube:([^\]]*)\]\]/g;
|
||||||
|
const replace = [];
|
||||||
|
while ((found = re.exec(text)) !== null) {
|
||||||
|
replace.push(found);
|
||||||
|
}
|
||||||
|
if (!(replace.length > 0)) { return text; }
|
||||||
|
let ret = "";
|
||||||
|
let begin = 0;
|
||||||
|
for (let it of replace) {
|
||||||
|
ret += text.substring(begin, it.index);
|
||||||
|
ret += embed(it[1]);
|
||||||
|
begin = it.index + it[0].length;
|
||||||
|
}
|
||||||
|
ret += text.substring(begin, text.length);
|
||||||
|
//console.log ret
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearEditor() {
|
||||||
|
this.editor.value("");
|
||||||
|
this.inputtags.value = "";
|
||||||
|
return (this.find("blog-publish") as GUI.tag.SwitchTag).swon = false;
|
||||||
|
}
|
||||||
|
// load blog
|
||||||
|
private loadBlogs(): Promise<any> {
|
||||||
|
return new Promise( async (ok, reject)=> {
|
||||||
|
try {
|
||||||
|
const filter: GenericObject<any> = {
|
||||||
|
order: ["ctime$desc"],
|
||||||
|
fields: [
|
||||||
|
"id",
|
||||||
|
"title",
|
||||||
|
"ctimestr",
|
||||||
|
"ctime",
|
||||||
|
"utime",
|
||||||
|
"utimestr"
|
||||||
|
],
|
||||||
|
limit: 10,
|
||||||
|
};
|
||||||
|
if(this.last_ctime)
|
||||||
|
{
|
||||||
|
filter.where = { ctime$lt: this.last_ctime};
|
||||||
|
}
|
||||||
|
const r = await this.blogdb.read(filter);
|
||||||
|
if(r.length == 0)
|
||||||
|
{
|
||||||
|
this.toast(__("No more record to load"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.last_ctime = r[r.length - 1].ctime;
|
||||||
|
for (let v of r) {
|
||||||
|
v.tag = "afx-blogger-post-item";
|
||||||
|
this.bloglist.push(v);
|
||||||
|
}
|
||||||
|
this.clearEditor();
|
||||||
|
return this.bloglist.selected = -1;
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
reject(__e(e));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private resizeContent() {
|
||||||
|
const container = this.find("editor-container");
|
||||||
|
const children = ($(".EasyMDEContainer", container)).children();
|
||||||
|
const titlebar = (($(this.scheme)).find(".afx-window-top"))[0];
|
||||||
|
const toolbar = children[0];
|
||||||
|
const statusbar = children[3];
|
||||||
|
const cheight = ($(this.scheme)).height() - ($(titlebar)).height() - ($(toolbar)).height() - ($(statusbar)).height() - 90;
|
||||||
|
return ($(children[1])).css("height", cheight + "px");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Blogger.singleton = true;
|
||||||
|
Blogger.dependencies = [
|
||||||
|
"pkg://SimpleMDE/main.js",
|
||||||
|
"pkg://SimpleMDE/main.css",
|
||||||
|
"pkg://Katex/main.js",
|
||||||
|
"pkg://Katex/main.css",
|
||||||
|
"pkg://SQLiteDB/libsqlite.js",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
98
Blogger/package.json
Normal file
98
Blogger/package.json
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
"app": "Blogger",
|
||||||
|
"name": "Blogging application",
|
||||||
|
"description": "Backend manager for blogging",
|
||||||
|
"info": {
|
||||||
|
"author": "Xuan Sang LE",
|
||||||
|
"email": "xsang.le@gmail.com"
|
||||||
|
},
|
||||||
|
"version": "0.2.13-a",
|
||||||
|
"category": "Internet",
|
||||||
|
"iconclass": "fa fa-book",
|
||||||
|
"dependencies": [
|
||||||
|
"SimpleMDE@2.18.0-r",
|
||||||
|
"Katex@0.11.1-r",
|
||||||
|
"SQLiteDB@0.1.0-a"
|
||||||
|
],
|
||||||
|
"mimes": [
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"locales": {
|
||||||
|
"en_GB": {
|
||||||
|
"Pick a parent": "Pick a parent",
|
||||||
|
"Category name": "Category name",
|
||||||
|
"Ok": "Ok",
|
||||||
|
"Cancel": "Cancel",
|
||||||
|
"Title": "Title",
|
||||||
|
"Subtitle": "Subtitle",
|
||||||
|
"Location": "Location",
|
||||||
|
"From": "From",
|
||||||
|
"Save": "Save",
|
||||||
|
"Content": "Content",
|
||||||
|
"IO Hub mail username/password": "IO Hub mail username/password",
|
||||||
|
"Send": "Send",
|
||||||
|
"Please select a parent category": "Please select a parent category",
|
||||||
|
"Please enter category name": "Please enter category name",
|
||||||
|
"Parent can not be the category itself": "Parent can not be the category itself",
|
||||||
|
"Title or content must not be blank": "Title or content must not be blank",
|
||||||
|
"No email selected": "No email selected",
|
||||||
|
"Unable to send mail to: {0}": "Unable to send mail to: {0}",
|
||||||
|
"Error sending mail: {0}": "Error sending mail: {0}",
|
||||||
|
"Open/Create database": "Open/Create database",
|
||||||
|
"Open/create new database": "Open/create new database",
|
||||||
|
"Unable to init database file: {0}": "Unable to init database file: {0}",
|
||||||
|
"Select image file": "Select image file",
|
||||||
|
"Unable to get file": "Unable to get file",
|
||||||
|
"Add category": "Add category",
|
||||||
|
"cv-cat-add: {0}": "cv-cat-add: {0}",
|
||||||
|
"Edit category": "Edit category",
|
||||||
|
"cv-cat-edit: {0}": "cv-cat-edit: {0}",
|
||||||
|
"Delete category": "Delete category",
|
||||||
|
"Do you really want to delete: {0}?": "Do you really want to delete: {0}?",
|
||||||
|
"cv-cat-del: {0}": "cv-cat-del: {0}",
|
||||||
|
"Please select a category": "Please select a category",
|
||||||
|
"New section entry for {0}": "New section entry for {0}",
|
||||||
|
"cv-sec-add: {0}": "cv-sec-add: {0}",
|
||||||
|
"Please select a section to move": "Please select a section to move",
|
||||||
|
"Move to": "Move to",
|
||||||
|
"cv-sec-move: {0}": "cv-sec-move: {0}",
|
||||||
|
"Please select a section to edit": "Please select a section to edit",
|
||||||
|
"Modify section entry": "Modify section entry",
|
||||||
|
"cv-sec-edit: {0}": "cv-sec-edit: {0}",
|
||||||
|
"Cannot delete the section: {0}": "Cannot delete the section: {0}",
|
||||||
|
"New": "New",
|
||||||
|
"Cannot export file for embedding to text": "Cannot export file for embedding to text",
|
||||||
|
"Preview": "Preview",
|
||||||
|
"Send mail": "Send mail",
|
||||||
|
"No post selected": "No post selected",
|
||||||
|
"Emails sent": "Emails sent",
|
||||||
|
"Error sending mails: {0}": "Error sending mails: {0}",
|
||||||
|
"No record found for ID {}": "No record found for ID {}",
|
||||||
|
"Cannot fetch the entry content": "Cannot fetch the entry content",
|
||||||
|
"Delete a post": "Delete a post",
|
||||||
|
"Do you really want to delete this post ?": "Do you really want to delete this post ?",
|
||||||
|
"Cannot fetch user data": "Cannot fetch user data",
|
||||||
|
"Full name must be entered": "Full name must be entered",
|
||||||
|
"User data updated": "User data updated",
|
||||||
|
"Cannot save user data: {0}": "Cannot save user data: {0}",
|
||||||
|
"Unable to load categories": "Unable to load categories",
|
||||||
|
"Found {0} sections": "Found {0} sections",
|
||||||
|
"Please insert a title in the text: beginning with heading": "Please insert a title in the text: beginning with heading",
|
||||||
|
"Please enter tags": "Please enter tags",
|
||||||
|
"Cannot save blog: {0}": "Cannot save blog: {0}",
|
||||||
|
"No more record to load": "No more record to load",
|
||||||
|
"Full name": "Full name",
|
||||||
|
"Address": "Address",
|
||||||
|
"Phone": "Phone",
|
||||||
|
"Email": "Email",
|
||||||
|
"Url": "Url",
|
||||||
|
"Photo": "Photo",
|
||||||
|
"Short biblio": "Short biblio",
|
||||||
|
"Categories": "Categories",
|
||||||
|
"Load more": "Load more",
|
||||||
|
"Tags": "Tags",
|
||||||
|
"Created: {0}": "Created: {0}",
|
||||||
|
"Updated: {0}": "Updated: {0}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
Blogger/scheme.html
Normal file
91
Blogger/scheme.html
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<afx-app-window data-id = "blogger-win" apptitle="Blogger" width="650" height="500">
|
||||||
|
<afx-hbox padding="5">
|
||||||
|
<afx-tab-container data-id = "tabcontainer" dir = "row" tabbarwidth= "40">
|
||||||
|
|
||||||
|
<afx-hbox data-id="user-container" data-height="100%" iconclass="fa fa-user-circle">
|
||||||
|
<afx-vbox>
|
||||||
|
<afx-hbox data-height = "30">
|
||||||
|
<afx-label data-width= "70" text = "__(Full name)"></afx-label>
|
||||||
|
<input type = "text" name="fullname" input-class = "user-input"></input>
|
||||||
|
</afx-hbox>
|
||||||
|
<afx-hbox data-height = "30">
|
||||||
|
<afx-label text = "__(Address)" data-width= "70"></afx-label>
|
||||||
|
<input type = "text" name="address" input-class = "user-input"></input>
|
||||||
|
</afx-hbox>
|
||||||
|
<afx-hbox data-height = "30">
|
||||||
|
<afx-label text = "__(Phone)" data-width= "70"></afx-label>
|
||||||
|
<input type = "text" name="Phone" input-class = "user-input"></input>
|
||||||
|
</afx-hbox>
|
||||||
|
<afx-hbox data-height = "30">
|
||||||
|
<afx-label text = "__(Email)" data-width= "70"></afx-label>
|
||||||
|
<input type = "text" name="email" input-class = "user-input"></input>
|
||||||
|
</afx-hbox>
|
||||||
|
<afx-hbox data-height = "30">
|
||||||
|
<afx-label text = "__(Url)" data-width= "70"></afx-label>
|
||||||
|
<input type = "text" name="url" input-class = "user-input"></input>
|
||||||
|
</afx-hbox>
|
||||||
|
<afx-hbox data-height = "30">
|
||||||
|
<afx-label text = "__(Photo)" data-width= "70"></afx-label>
|
||||||
|
<input type = "text" name="photo" data-id="photo" readonly="readonly" input-class = "user-input"></input>
|
||||||
|
</afx-hbox>
|
||||||
|
<afx-label data-height = "30" text = "__(Short biblio)"></afx-label>
|
||||||
|
<textarea name="shortbiblio" input-class = "user-input"></textarea>
|
||||||
|
<afx-hbox data-height = "35" style="text-align: right;">
|
||||||
|
<afx-button iconclass = "fa fa-save" data-id = "bt-user-save" text = "__(Save)"></afx-button>
|
||||||
|
</afx-hbox>
|
||||||
|
</afx-vbox>
|
||||||
|
</afx-hbox>
|
||||||
|
|
||||||
|
<afx-hbox data-id="cv-container" data-height="100%" iconclass="fa fa-info-circle">
|
||||||
|
<div data-width="5"></div>
|
||||||
|
<afx-vbox data-width="150" min-width="100">
|
||||||
|
<afx-label class="lbl-header" data-height = "23" text = "__(Categories)" iconclass = "fa fa-bars"></afx-label>
|
||||||
|
<afx-tree-view data-id = "cv-list" ></afx-tree-view>
|
||||||
|
<div data-height="35" class = "cv-side-bar-btn">
|
||||||
|
<afx-button data-id = "cv-cat-add" text = "" iconclass = "fa fa-plus-circle"></afx-button>
|
||||||
|
<afx-button data-id = "cv-cat-del" text = "" iconclass = "fa fa-minus-circle"></afx-button>
|
||||||
|
<afx-button data-id = "cv-cat-edit" text = "" iconclass = "fa fa-pencil-square-o"></afx-button>
|
||||||
|
</div>
|
||||||
|
</afx-vbox>
|
||||||
|
<afx-resizer data-width = "2"></afx-resizer>
|
||||||
|
<afx-vbox>
|
||||||
|
<afx-list-view data-id = "cv-sec-list" ></afx-list-view>
|
||||||
|
<afx-hbox data-height="35" >
|
||||||
|
<afx-label data-id = "cv-sec-status"></afx-label>
|
||||||
|
<div class = "cv-side-bar-btn">
|
||||||
|
<afx-button data-id = "cv-sec-add" text = "" iconclass = "fa fa-plus-circle"></afx-button>
|
||||||
|
<afx-button data-id = "cv-sec-edit" text = "" iconclass = "fa fa-pencil-square-o"></afx-button>
|
||||||
|
<afx-button data-id = "cv-sec-move" text = "" iconclass = "fa fa-exchange"></afx-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</afx-hbox>
|
||||||
|
</afx-vbox>
|
||||||
|
<div data-width="5"></div>
|
||||||
|
</afx-hbox>
|
||||||
|
|
||||||
|
|
||||||
|
<afx-hbox data-id = "blog-container" data-height="100%" iconclass="fa fa-book">
|
||||||
|
<afx-vbox>
|
||||||
|
<afx-list-view data-id = "blog-list" min-width="100" data-width="200"></afx-list-view>
|
||||||
|
<afx-button data-id = "blog-load-more" text = "__(Load more)" iconclass_end = "bi bi-chevron-double-right" data-height="content"></afx-button>
|
||||||
|
</afx-vbox>
|
||||||
|
<afx-resizer data-width = "3"></afx-resizer>
|
||||||
|
<afx-vbox>
|
||||||
|
<div data-id = "editor-container">
|
||||||
|
<textarea data-id="markarea" ></textarea>
|
||||||
|
</div>
|
||||||
|
<afx-label text = "__(Tags)" style="font-weight:bold;" data-height="25" ></afx-label>
|
||||||
|
<afx-hbox data-height="25">
|
||||||
|
<input type = "text" data-id = "input-tags" ></input>
|
||||||
|
<div data-width="5"></div>
|
||||||
|
<afx-switch data data-id = "blog-publish" data-width="30"></afx-switch>
|
||||||
|
<div data-width="5"></div>
|
||||||
|
</afx-hbox>
|
||||||
|
|
||||||
|
<div data-height="5"></div>
|
||||||
|
</afx-vbox>
|
||||||
|
</afx-hbox>
|
||||||
|
|
||||||
|
</afx-tab-container>
|
||||||
|
</afx-hbox>
|
||||||
|
</afx-app-window>
|
104
Blogger/tags.ts
Normal file
104
Blogger/tags.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
|
||||||
|
interface Array<T> {
|
||||||
|
/**
|
||||||
|
* Check if the array includes an element
|
||||||
|
*
|
||||||
|
* @param {T} element to check
|
||||||
|
* @returns {boolean}
|
||||||
|
* @memberof Array
|
||||||
|
*/
|
||||||
|
includes(el: T): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace OS {
|
||||||
|
export namespace application {
|
||||||
|
export namespace blogger {
|
||||||
|
|
||||||
|
class CVSectionListItemTag extends OS.GUI.tag.ListViewItemTag {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
ondatachange() {
|
||||||
|
if (!this.data) { return; }
|
||||||
|
const v = this.data;
|
||||||
|
const nativel = ["content", "start", "end"];
|
||||||
|
this.closable = v.closable;
|
||||||
|
return (() => {
|
||||||
|
const result = [];
|
||||||
|
for (let k in this.refs) {
|
||||||
|
const el = this.refs[k];
|
||||||
|
if (v[k] && (v[k] !== "")) {
|
||||||
|
if (nativel.includes(k)) {
|
||||||
|
result.push($(el).text(v[k]));
|
||||||
|
} else {
|
||||||
|
result.push((el as OS.GUI.tag.LabelTag).text = v[k]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
reload() { }
|
||||||
|
|
||||||
|
init() { }
|
||||||
|
|
||||||
|
|
||||||
|
itemlayout() {
|
||||||
|
return {
|
||||||
|
el: "div", children: [
|
||||||
|
{ el: "afx-label", ref: "title", class: "afx-cv-sec-title" },
|
||||||
|
{ el: "afx-label", ref: "subtitle", class: "afx-cv-sec-subtitle" },
|
||||||
|
{ el: "p", ref: "content", class: "afx-cv-sec-content" },
|
||||||
|
{
|
||||||
|
el: "p", class: "afx-cv-sec-period", children: [
|
||||||
|
{ el: "i", ref: "start" },
|
||||||
|
{ el: "i", ref: "end", class: "period-end" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ el: "afx-label", ref: "location", class: "afx-cv-sec-loc" }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OS.GUI.tag.define("afx-blogger-cvsection-item", CVSectionListItemTag);
|
||||||
|
|
||||||
|
|
||||||
|
class BlogPostListItemTag extends OS.GUI.tag.ListViewItemTag {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
ondatachange() {
|
||||||
|
if (!this.data) { return; }
|
||||||
|
const v = this.data;
|
||||||
|
v.closable = true;
|
||||||
|
this.closable = v.closable;
|
||||||
|
(this.refs.title as OS.GUI.tag.LabelTag).text = v.title;
|
||||||
|
(this.refs.ctimestr as OS.GUI.tag.LabelTag).text = __("Created: {0}", v.ctimestr);
|
||||||
|
(this.refs.utimestr as OS.GUI.tag.LabelTag).text = __("Updated: {0}", v.utimestr);
|
||||||
|
}
|
||||||
|
|
||||||
|
reload() { }
|
||||||
|
|
||||||
|
init() { }
|
||||||
|
|
||||||
|
itemlayout() {
|
||||||
|
return {
|
||||||
|
el: "div", children: [
|
||||||
|
{ el: "afx-label", ref: "title", class: "afx-blogpost-title" },
|
||||||
|
{ el: "afx-label", ref: "ctimestr", class: "blog-dates" },
|
||||||
|
{ el: "afx-label", ref: "utimestr", class: "blog-dates" },
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OS.GUI.tag.define("afx-blogger-post-item", BlogPostListItemTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,8 @@ A back-end tool for my online document hub [https://doc.iohub.dev/antos/](https:
|
|||||||
|
|
||||||
|
|
||||||
## Change logs
|
## Change logs
|
||||||
|
### v0.2.5-a
|
||||||
|
* Use the new MDE library
|
||||||
### v0.2.3-a
|
### v0.2.3-a
|
||||||
* Chage app category name
|
* Chage app category name
|
||||||
### v0.2.1-a
|
### v0.2.1-a
|
||||||
|
83
Booklet/build.json
Normal file
83
Booklet/build.json
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"name": "Booklet",
|
||||||
|
"targets": {
|
||||||
|
"init": {
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "vfs-mkdir",
|
||||||
|
"data": [
|
||||||
|
"build",
|
||||||
|
"build/debug",
|
||||||
|
"build/release"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"coffee": {
|
||||||
|
"require": [
|
||||||
|
"coffee"
|
||||||
|
],
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "coffee-compile",
|
||||||
|
"data": {
|
||||||
|
"src": [
|
||||||
|
"coffees/main.coffee",
|
||||||
|
"coffees/common.coffee"
|
||||||
|
],
|
||||||
|
"dest": "build/debug/main.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"uglify": {
|
||||||
|
"require": [
|
||||||
|
"terser"
|
||||||
|
],
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "terser-uglify",
|
||||||
|
"data": [
|
||||||
|
"build/debug/main.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"copy": {
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "vfs-cp",
|
||||||
|
"data": {
|
||||||
|
"src": [
|
||||||
|
"assets/scheme.html",
|
||||||
|
"package.json",
|
||||||
|
"README.md",
|
||||||
|
"css/main.css"
|
||||||
|
],
|
||||||
|
"dest": "build/debug"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"require": [
|
||||||
|
"zip"
|
||||||
|
],
|
||||||
|
"depend": [
|
||||||
|
"init",
|
||||||
|
"coffee",
|
||||||
|
"uglify",
|
||||||
|
"copy"
|
||||||
|
],
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "zip-mk",
|
||||||
|
"data": {
|
||||||
|
"src": "build/debug",
|
||||||
|
"dest": "build/release/Booklet.zip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,8 @@ A back-end tool for my online document hub [https://doc.iohub.dev/antos/](https:
|
|||||||
|
|
||||||
|
|
||||||
## Change logs
|
## Change logs
|
||||||
|
### v0.2.5-a
|
||||||
|
* Use the new MDE library
|
||||||
### v0.2.3-a
|
### v0.2.3-a
|
||||||
* Chage app category name
|
* Chage app category name
|
||||||
### v0.2.1-a
|
### v0.2.1-a
|
||||||
|
File diff suppressed because one or more lines are too long
@ -7,9 +7,9 @@
|
|||||||
"author": "Xuan Sang LE",
|
"author": "Xuan Sang LE",
|
||||||
"email": "mrsang@lxsang.me"
|
"email": "mrsang@lxsang.me"
|
||||||
},
|
},
|
||||||
"version":"0.2.4-a",
|
"version":"0.2.5-a",
|
||||||
"category":"Office",
|
"category":"Office",
|
||||||
"iconclass":"bi bi-journals",
|
"iconclass":"bi bi-journals",
|
||||||
"dependencies": ["SimpleMDE@1.11.2-r","Katex@0.11.1-r"],
|
"dependencies": ["SimpleMDE@2.18.0-r","Katex@0.11.1-r"],
|
||||||
"mimes":["dir"]
|
"mimes":["dir"]
|
||||||
}
|
}
|
Binary file not shown.
@ -166,7 +166,7 @@ class Booklet extends this.OS.application.BaseApplication
|
|||||||
@previewOn = false
|
@previewOn = false
|
||||||
@editormux = false
|
@editormux = false
|
||||||
|
|
||||||
@editor = new SimpleMDE
|
@editor = new EasyMDE
|
||||||
element: markarea
|
element: markarea
|
||||||
autoDownloadFontAwesome: false
|
autoDownloadFontAwesome: false
|
||||||
autofocus: true
|
autofocus: true
|
||||||
@ -232,7 +232,7 @@ class Booklet extends this.OS.application.BaseApplication
|
|||||||
name: __("Preview"),
|
name: __("Preview"),
|
||||||
className: "fa fa-eye no-disable",
|
className: "fa fa-eye no-disable",
|
||||||
action: (e) =>
|
action: (e) =>
|
||||||
SimpleMDE.togglePreview e
|
EasyMDE.togglePreview e
|
||||||
#/console.log @select ".editor-preview editor-preview-active"
|
#/console.log @select ".editor-preview editor-preview-active"
|
||||||
renderMathInElement @find "mycontainer"
|
renderMathInElement @find "mycontainer"
|
||||||
@renderLocalElement()
|
@renderLocalElement()
|
||||||
@ -246,7 +246,7 @@ class Booklet extends this.OS.application.BaseApplication
|
|||||||
|
|
||||||
preview.innerHTML = html
|
preview.innerHTML = html
|
||||||
|
|
||||||
@on "hboxchange", (e) => @resizeContent()
|
@on "resize", (e) => @resizeContent()
|
||||||
@bindKey "ALT-N", () => @actionFile "#{@name}-New"
|
@bindKey "ALT-N", () => @actionFile "#{@name}-New"
|
||||||
@bindKey "ALT-O", () => @actionFile "#{@name}-Open"
|
@bindKey "ALT-O", () => @actionFile "#{@name}-Open"
|
||||||
@bindKey "CTRL-S", () => @actionFile "#{@name}-Save"
|
@bindKey "CTRL-S", () => @actionFile "#{@name}-Save"
|
||||||
@ -263,12 +263,12 @@ class Booklet extends this.OS.application.BaseApplication
|
|||||||
@currentToc.descFile.cache = @editor.value()
|
@currentToc.descFile.cache = @editor.value()
|
||||||
|
|
||||||
resizeContent: () ->
|
resizeContent: () ->
|
||||||
children = ($ @container).children()
|
children = ($ ".EasyMDEContainer", @container).children()
|
||||||
titlebar = (($ @scheme).find ".afx-window-top")[0]
|
titlebar = (($ @scheme).find ".afx-window-top")[0]
|
||||||
toolbar = children[1]
|
toolbar = children[0]
|
||||||
statusbar = children[4]
|
statusbar = children[3]
|
||||||
cheight = ($ @scheme).height() - ($ titlebar).height() - ($ toolbar).height() - ($ statusbar).height() - 40
|
cheight = ($ @scheme).height() - ($ titlebar).height() - ($ toolbar).height() - ($ statusbar).height() - 40
|
||||||
($ children[2]).css("height", cheight + "px")
|
($ children[1]).css("height", cheight + "px")
|
||||||
|
|
||||||
|
|
||||||
menu: () ->
|
menu: () ->
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
"author": "Xuan Sang LE",
|
"author": "Xuan Sang LE",
|
||||||
"email": "mrsang@lxsang.me"
|
"email": "mrsang@lxsang.me"
|
||||||
},
|
},
|
||||||
"version":"0.2.4-a",
|
"version":"0.2.5-a",
|
||||||
"category":"Office",
|
"category":"Office",
|
||||||
"iconclass":"bi bi-journals",
|
"iconclass":"bi bi-journals",
|
||||||
"dependencies": ["SimpleMDE@1.11.2-r","Katex@0.11.1-r"],
|
"dependencies": ["SimpleMDE@2.18.0-r","Katex@0.11.1-r"],
|
||||||
"mimes":["dir"]
|
"mimes":["dir"]
|
||||||
}
|
}
|
@ -6,6 +6,7 @@ Clipper use `html2canvas` to capture AntOS desktop or a specific window.
|
|||||||
It is able to crop the captured image before saving to a file
|
It is able to crop the captured image before saving to a file
|
||||||
|
|
||||||
## Change logs
|
## Change logs
|
||||||
|
* v0.1.4-a minor changes to adapt to new AntOS v2.0.x
|
||||||
* v0.1.3-a change app category
|
* v0.1.3-a change app category
|
||||||
* v0.1.2-a use ALT-S as global shortcut for screen capture
|
* v0.1.2-a use ALT-S as global shortcut for screen capture
|
||||||
* v0.1.1-a use CTRL-S as global shortcut for screen capture
|
* v0.1.1-a use CTRL-S as global shortcut for screen capture
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<afx-app-window apptitle="Clipper" width="500" height="400" data-id="Clipper">
|
<afx-app-window apptitle="Clipper" width="500" height="400" data-id="Clipper">
|
||||||
<afx-hbox >
|
<afx-hbox >
|
||||||
<afx-vbox>
|
<afx-vbox>
|
||||||
<div data-height="30" data-id="toolbar">
|
<div data-height="35" data-id="toolbar">
|
||||||
<afx-button data-id="btnCptScreen" text="__(Capture screen)" iconclass="fa fa-camera-retro"></afx-button>
|
<afx-button data-id="btnCptScreen" text="__(Capture screen)" iconclass="fa fa-camera-retro"></afx-button>
|
||||||
<afx-button data-id="btnCptWindow" text="__(Capture a window)" iconclass="fa fa-window-maximize"></afx-button>
|
<afx-button data-id="btnCptWindow" text="__(Capture a window)" iconclass="fa fa-window-maximize"></afx-button>
|
||||||
<afx-button data-id="btnCrop" text="__(Crop)" toggle="true" selected="false" iconclass="fa fa-crop"></afx-button>
|
<afx-button data-id="btnCrop" text="__(Crop)" toggle="true" selected="false" iconclass="fa fa-crop"></afx-button>
|
||||||
|
104
Clipper/build.json
Normal file
104
Clipper/build.json
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
{
|
||||||
|
"name": "Clipper",
|
||||||
|
"targets": {
|
||||||
|
"init": {
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "vfs-mkdir",
|
||||||
|
"data": [
|
||||||
|
"build",
|
||||||
|
"build/debug",
|
||||||
|
"build/release"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"coffee": {
|
||||||
|
"require": [
|
||||||
|
"coffee"
|
||||||
|
],
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "coffee-compile",
|
||||||
|
"data": {
|
||||||
|
"src": [
|
||||||
|
"coffees/main.coffee"
|
||||||
|
],
|
||||||
|
"dest": "build/debug/coffee-main.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cat": {
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "vfs-cat",
|
||||||
|
"data": {
|
||||||
|
"src": [
|
||||||
|
"build/debug/coffee-main.js",
|
||||||
|
"javascripts/html2canvas.js"
|
||||||
|
],
|
||||||
|
"dest": "build/debug/main.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vfs-rm",
|
||||||
|
"data": [
|
||||||
|
"build/debug/coffee-main.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"uglify": {
|
||||||
|
"require": [
|
||||||
|
"terser"
|
||||||
|
],
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "terser-uglify",
|
||||||
|
"data": [
|
||||||
|
"build/debug/main.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"copy": {
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "vfs-cp",
|
||||||
|
"data": {
|
||||||
|
"src": [
|
||||||
|
"assets/scheme.html",
|
||||||
|
"assets/bg.jpg",
|
||||||
|
"package.json",
|
||||||
|
"README.md",
|
||||||
|
"css/main.css"
|
||||||
|
],
|
||||||
|
"dest": "build/debug"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"require": [
|
||||||
|
"zip"
|
||||||
|
],
|
||||||
|
"depend": [
|
||||||
|
"init",
|
||||||
|
"coffee",
|
||||||
|
"cat",
|
||||||
|
"uglify",
|
||||||
|
"copy"
|
||||||
|
],
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"name": "zip-mk",
|
||||||
|
"data": {
|
||||||
|
"src": "build/debug",
|
||||||
|
"dest": "build/release/Clipper.zip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ Clipper use `html2canvas` to capture AntOS desktop or a specific window.
|
|||||||
It is able to crop the captured image before saving to a file
|
It is able to crop the captured image before saving to a file
|
||||||
|
|
||||||
## Change logs
|
## Change logs
|
||||||
|
* v0.1.4-a minor changes to adapt to new AntOS v2.0.x
|
||||||
* v0.1.3-a change app category
|
* v0.1.3-a change app category
|
||||||
* v0.1.2-a use ALT-S as global shortcut for screen capture
|
* v0.1.2-a use ALT-S as global shortcut for screen capture
|
||||||
* v0.1.1-a use CTRL-S as global shortcut for screen capture
|
* v0.1.1-a use CTRL-S as global shortcut for screen capture
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
afx-app-window[data-id = "Clipper"] div[data-id = "wrapper"] {
|
afx-app-window[data-id = "Clipper"] div[data-id = "wrapper"] {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background-image: url("bg.jpg");
|
background-image: url("bg.jpg");
|
||||||
|
File diff suppressed because one or more lines are too long
@ -6,7 +6,7 @@
|
|||||||
"author": "Xuan Sang LE",
|
"author": "Xuan Sang LE",
|
||||||
"email": "xsang.le@gmail.com"
|
"email": "xsang.le@gmail.com"
|
||||||
},
|
},
|
||||||
"version":"0.1.3-a",
|
"version":"0.1.4-a",
|
||||||
"category":"Utility",
|
"category":"Utility",
|
||||||
"iconclass":"fa fa-scissors",
|
"iconclass":"fa fa-scissors",
|
||||||
"mimes":["none"],
|
"mimes":["none"],
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<afx-app-window apptitle="Clipper" width="500" height="400" data-id="Clipper">
|
<afx-app-window apptitle="Clipper" width="500" height="400" data-id="Clipper">
|
||||||
<afx-hbox >
|
<afx-hbox >
|
||||||
<afx-vbox>
|
<afx-vbox>
|
||||||
<div data-height="30" data-id="toolbar">
|
<div data-height="35" data-id="toolbar">
|
||||||
<afx-button data-id="btnCptScreen" text="__(Capture screen)" iconclass="fa fa-camera-retro"></afx-button>
|
<afx-button data-id="btnCptScreen" text="__(Capture screen)" iconclass="fa fa-camera-retro"></afx-button>
|
||||||
<afx-button data-id="btnCptWindow" text="__(Capture a window)" iconclass="fa fa-window-maximize"></afx-button>
|
<afx-button data-id="btnCptWindow" text="__(Capture a window)" iconclass="fa fa-window-maximize"></afx-button>
|
||||||
<afx-button data-id="btnCrop" text="__(Crop)" toggle="true" selected="false" iconclass="fa fa-crop"></afx-button>
|
<afx-button data-id="btnCrop" text="__(Crop)" toggle="true" selected="false" iconclass="fa fa-crop"></afx-button>
|
||||||
|
Binary file not shown.
@ -6,7 +6,7 @@
|
|||||||
"author": "Xuan Sang LE",
|
"author": "Xuan Sang LE",
|
||||||
"email": "xsang.le@gmail.com"
|
"email": "xsang.le@gmail.com"
|
||||||
},
|
},
|
||||||
"version":"0.1.3-a",
|
"version":"0.1.4-a",
|
||||||
"category":"Utility",
|
"category":"Utility",
|
||||||
"iconclass":"fa fa-scissors",
|
"iconclass":"fa fa-scissors",
|
||||||
"mimes":["none"],
|
"mimes":["none"],
|
||||||
|
@ -3,4 +3,7 @@ A simple yet powerful code/text editor.
|
|||||||
CodePad is a text editor based on the ACE editor.
|
CodePad is a text editor based on the ACE editor.
|
||||||
|
|
||||||
## Change logs
|
## Change logs
|
||||||
|
- v0.1.8-a: fix resizer bug on new UI API
|
||||||
|
- v0.1.7-a: fix setting bug using new AntOS setting API
|
||||||
|
- v0.1.6-a: adapt to new AntOS v2.0.x
|
||||||
- v0.1.5-a: CodePad moved out of AntOS based system as regular AntOS package
|
- v0.1.5-a: CodePad moved out of AntOS based system as regular AntOS package
|
@ -1,34 +1,7 @@
|
|||||||
afx-app-window[data-id = "codepad"] .ace_editor {
|
afx-app-window[data-id = "codepad"] .ace_editor {
|
||||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
|
||||||
}
|
}
|
||||||
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-container
|
|
||||||
{
|
|
||||||
/*border-top: 1px solid #272822;*/
|
|
||||||
overflow: hidden;
|
|
||||||
overflow-x: auto;
|
|
||||||
font-size: 12px;
|
|
||||||
scrollbar-width: none;
|
|
||||||
/*scrollbar-color: #656565 transparent;*/
|
|
||||||
}
|
|
||||||
|
|
||||||
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-container::-webkit-scrollbar {
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-container::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-container::-webkit-scrollbar-thumb {
|
|
||||||
background-color: #656565;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-container > ul
|
|
||||||
{
|
|
||||||
width: intrinsic;
|
|
||||||
width: -moz-max-content;
|
|
||||||
width: -webkit-max-content;
|
|
||||||
width: max-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view ul afx-list-item:nth-child(even) li.selected,
|
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view ul afx-list-item:nth-child(even) li.selected,
|
||||||
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-container > ul > afx-list-item > li.selected{
|
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-container > ul > afx-list-item > li.selected{
|
||||||
@ -40,14 +13,12 @@ afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-contai
|
|||||||
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view afx-list-view i.closable:before {
|
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view afx-list-view i.closable:before {
|
||||||
color:afafaf;
|
color:afafaf;
|
||||||
}
|
}
|
||||||
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view ul afx-list-item:nth-child(even) li,
|
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view ul .afx-list-item:nth-child(even) li,
|
||||||
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-container > ul li{
|
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-container > ul li{
|
||||||
background-color:#333333;
|
background-color:#333333;
|
||||||
color:#afafaf;
|
color:#afafaf;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
padding-top: 5px;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
border-right: 1px solid #272822;
|
border-right: 1px solid #272822;
|
||||||
}
|
}
|
||||||
@ -94,48 +65,6 @@ afx-app-window[data-id = "codepad"] .afx-window-wrapper div[data-id="statctn"] a
|
|||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
afx-app-window[data-id = "cmd-win"] .afx-window-wrapper{
|
|
||||||
border-radius: 0px;
|
|
||||||
border: 0;
|
|
||||||
/*border: 1px solid #37373d;*/
|
|
||||||
background-color: transparent;
|
|
||||||
box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.65);
|
|
||||||
}
|
|
||||||
afx-app-window[data-id = "cmd-win"] .afx-window-wrapper afx-list-view ul afx-list-item:nth-child(even) li
|
|
||||||
{
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
afx-app-window[data-id = "cmd-win"] .afx-window-wrapper afx-list-view afx-list-item li{
|
|
||||||
background-color: transparent;
|
|
||||||
color:#afafaf;
|
|
||||||
}
|
|
||||||
afx-app-window[data-id = "cmd-win"] .afx-window-wrapper div.list-container > ul li:hover{
|
|
||||||
background-color: #37373d;
|
|
||||||
}
|
|
||||||
|
|
||||||
afx-app-window[data-id = "cmd-win"] .afx-window-wrapper afx-list-view ul afx-list-item:nth-child(even) li.selected,
|
|
||||||
afx-app-window[data-id = "cmd-win"] .afx-window-wrappe dafx-list-viewafx-list-view ul li.selected
|
|
||||||
{
|
|
||||||
background-color: #116cd6;
|
|
||||||
color:white;
|
|
||||||
}
|
|
||||||
afx-app-window[data-id = "cmd-win"] .afx-window-top{
|
|
||||||
height: 0;
|
|
||||||
border:0;
|
|
||||||
}
|
|
||||||
afx-app-window[data-id = "cmd-win"] input{
|
|
||||||
border: 1px solid #007acc;
|
|
||||||
border-radius: 0;
|
|
||||||
font-size: 12px;
|
|
||||||
color:#afafaf;
|
|
||||||
background-color:#272822;
|
|
||||||
padding-left: 5px;
|
|
||||||
margin: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
afx-app-window[data-id = "cmd-win"] .afx-window-content{
|
|
||||||
background-color:#272822;
|
|
||||||
}
|
|
||||||
|
|
||||||
afx-app-window[data-id = "codepad"] div[data-id="output-tab"] {
|
afx-app-window[data-id = "codepad"] div[data-id="output-tab"] {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
@ -10,17 +10,17 @@
|
|||||||
<afx-vbox>
|
<afx-vbox>
|
||||||
<afx-hbox>
|
<afx-hbox>
|
||||||
<afx-vbox data-id="left-panel">
|
<afx-vbox data-id="left-panel">
|
||||||
<afx-tab-bar closable="true" data-height="26" data-id = "left-tabbar"></afx-tab-bar>
|
<afx-tab-bar closable="true" data-height="35" data-id = "left-tabbar"></afx-tab-bar>
|
||||||
<div data-id="left-editorarea"></div>
|
<div data-id="left-editorarea"></div>
|
||||||
</afx-vbox>
|
</afx-vbox>
|
||||||
<afx-resizer data-width="3"></afx-resizer>
|
<afx-resizer data-width="3"></afx-resizer>
|
||||||
<afx-vbox data-id="right-panel">
|
<afx-vbox data-id="right-panel">
|
||||||
<afx-tab-bar closable="true" data-height="26" data-id = "right-tabbar"></afx-tab-bar>
|
<afx-tab-bar closable="true" data-height="35" data-id = "right-tabbar"></afx-tab-bar>
|
||||||
<div data-id="right-editorarea"></div>
|
<div data-id="right-editorarea"></div>
|
||||||
</afx-vbox>
|
</afx-vbox>
|
||||||
</afx-hbox>
|
</afx-hbox>
|
||||||
<afx-resizer data-height = "3" dir = "ve" attachnext = "true" ></afx-resizer>
|
<afx-resizer data-height = "3" attachnext = "true" ></afx-resizer>
|
||||||
<afx-tab-container data-id = "bottombar" data-height="150" min-height="150" tabbarheight= "22">
|
<afx-tab-container data-id = "bottombar" data-height="150" min-height="150" tabbarheight= "35">
|
||||||
<afx-hbox tabname="__(Output)" iconclass = "fa fa-file-text" class = "bottom-tab-content">
|
<afx-hbox tabname="__(Output)" iconclass = "fa fa-file-text" class = "bottom-tab-content">
|
||||||
<afx-button text = "" data-id="logger-clear" iconclass="fa fa-trash" data-width="21"></afx-button>
|
<afx-button text = "" data-id="logger-clear" iconclass="fa fa-trash" data-width="21"></afx-button>
|
||||||
<div data-id="output-tab" iconclass = "fa fa-file-text" >
|
<div data-id="output-tab" iconclass = "fa fa-file-text" >
|
||||||
|
@ -17,8 +17,10 @@
|
|||||||
"data": ["build","build/debug","build/release"]
|
"data": ["build","build/debug","build/release"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ts-import",
|
"name": "ts-antos-sdk",
|
||||||
"data": ["sdk://core/ts/core.d.ts", "sdk://core/ts/jquery.d.ts","sdk://core/ts/antos.d.ts"]
|
"data": {
|
||||||
|
"version": "2.0.x"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ts-compile",
|
"name": "ts-compile",
|
||||||
|
@ -3,4 +3,7 @@ A simple yet powerful code/text editor.
|
|||||||
CodePad is a text editor based on the ACE editor.
|
CodePad is a text editor based on the ACE editor.
|
||||||
|
|
||||||
## Change logs
|
## Change logs
|
||||||
|
- v0.1.8-a: fix resizer bug on new UI API
|
||||||
|
- v0.1.7-a: fix setting bug using new AntOS setting API
|
||||||
|
- v0.1.6-a: adapt to new AntOS v2.0.x
|
||||||
- v0.1.5-a: CodePad moved out of AntOS based system as regular AntOS package
|
- v0.1.5-a: CodePad moved out of AntOS based system as regular AntOS package
|
@ -1,34 +1,7 @@
|
|||||||
afx-app-window[data-id = "codepad"] .ace_editor {
|
afx-app-window[data-id = "codepad"] .ace_editor {
|
||||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
|
||||||
}
|
}
|
||||||
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-container
|
|
||||||
{
|
|
||||||
/*border-top: 1px solid #272822;*/
|
|
||||||
overflow: hidden;
|
|
||||||
overflow-x: auto;
|
|
||||||
font-size: 12px;
|
|
||||||
scrollbar-width: none;
|
|
||||||
/*scrollbar-color: #656565 transparent;*/
|
|
||||||
}
|
|
||||||
|
|
||||||
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-container::-webkit-scrollbar {
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-container::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-container::-webkit-scrollbar-thumb {
|
|
||||||
background-color: #656565;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-container > ul
|
|
||||||
{
|
|
||||||
width: intrinsic;
|
|
||||||
width: -moz-max-content;
|
|
||||||
width: -webkit-max-content;
|
|
||||||
width: max-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view ul afx-list-item:nth-child(even) li.selected,
|
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view ul afx-list-item:nth-child(even) li.selected,
|
||||||
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-container > ul > afx-list-item > li.selected{
|
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-container > ul > afx-list-item > li.selected{
|
||||||
@ -40,14 +13,12 @@ afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-contai
|
|||||||
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view afx-list-view i.closable:before {
|
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view afx-list-view i.closable:before {
|
||||||
color:afafaf;
|
color:afafaf;
|
||||||
}
|
}
|
||||||
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view ul afx-list-item:nth-child(even) li,
|
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view ul .afx-list-item:nth-child(even) li,
|
||||||
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-container > ul li{
|
afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-container > ul li{
|
||||||
background-color:#333333;
|
background-color:#333333;
|
||||||
color:#afafaf;
|
color:#afafaf;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
padding-top: 5px;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
border-right: 1px solid #272822;
|
border-right: 1px solid #272822;
|
||||||
}
|
}
|
||||||
@ -94,48 +65,6 @@ afx-app-window[data-id = "codepad"] .afx-window-wrapper div[data-id="statctn"] a
|
|||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
afx-app-window[data-id = "cmd-win"] .afx-window-wrapper{
|
|
||||||
border-radius: 0px;
|
|
||||||
border: 0;
|
|
||||||
/*border: 1px solid #37373d;*/
|
|
||||||
background-color: transparent;
|
|
||||||
box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.65);
|
|
||||||
}
|
|
||||||
afx-app-window[data-id = "cmd-win"] .afx-window-wrapper afx-list-view ul afx-list-item:nth-child(even) li
|
|
||||||
{
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
afx-app-window[data-id = "cmd-win"] .afx-window-wrapper afx-list-view afx-list-item li{
|
|
||||||
background-color: transparent;
|
|
||||||
color:#afafaf;
|
|
||||||
}
|
|
||||||
afx-app-window[data-id = "cmd-win"] .afx-window-wrapper div.list-container > ul li:hover{
|
|
||||||
background-color: #37373d;
|
|
||||||
}
|
|
||||||
|
|
||||||
afx-app-window[data-id = "cmd-win"] .afx-window-wrapper afx-list-view ul afx-list-item:nth-child(even) li.selected,
|
|
||||||
afx-app-window[data-id = "cmd-win"] .afx-window-wrappe dafx-list-viewafx-list-view ul li.selected
|
|
||||||
{
|
|
||||||
background-color: #116cd6;
|
|
||||||
color:white;
|
|
||||||
}
|
|
||||||
afx-app-window[data-id = "cmd-win"] .afx-window-top{
|
|
||||||
height: 0;
|
|
||||||
border:0;
|
|
||||||
}
|
|
||||||
afx-app-window[data-id = "cmd-win"] input{
|
|
||||||
border: 1px solid #007acc;
|
|
||||||
border-radius: 0;
|
|
||||||
font-size: 12px;
|
|
||||||
color:#afafaf;
|
|
||||||
background-color:#272822;
|
|
||||||
padding-left: 5px;
|
|
||||||
margin: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
afx-app-window[data-id = "cmd-win"] .afx-window-content{
|
|
||||||
background-color:#272822;
|
|
||||||
}
|
|
||||||
|
|
||||||
afx-app-window[data-id = "codepad"] div[data-id="output-tab"] {
|
afx-app-window[data-id = "codepad"] div[data-id="output-tab"] {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user