mirror of
https://github.com/antos-rde/antosdk-apps.git
synced 2024-11-08 06:28:29 +01: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"]
|
||||
},
|
||||
{
|
||||
"name": "ts-import",
|
||||
"data": ["sdk://core/ts/core.d.ts", "sdk://core/ts/jquery.d.ts","sdk://core/ts/antos.d.ts"]
|
||||
"name": "ts-antos-sdk",
|
||||
"data": {
|
||||
"version": "2.0.x"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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
|
||||
### v0.1.1-b
|
||||
* Increase default window size
|
||||
### v0.1.1-b
|
||||
* add missing css file
|
||||
|
||||
### v0.1.0-b
|
||||
|
@ -1,16 +1,8 @@
|
||||
<afx-app-window apptitle="__(About AntOS)" width="450" height="500" data-id="About">
|
||||
<afx-vbox >
|
||||
<div data-height="10"></div>
|
||||
<afx-hbox data-id="wrapper">
|
||||
<div data-width="10"></div>
|
||||
<div data-id="container"></div>
|
||||
<div data-width="10"></div>
|
||||
</afx-hbox>
|
||||
<div data-height="5"></div>
|
||||
<afx-hbox data-height="23" style="text-align: right;">
|
||||
<afx-app-window apptitle="__(About AntOS)" width="600" height="500" data-id="About">
|
||||
<afx-vbox padding="10">
|
||||
<div data-id="container"></div>
|
||||
<afx-hbox data-height="35" style="text-align: right;">
|
||||
<afx-button data-id="btnclose" text="__(Close)"></afx-button>
|
||||
<div data-width="5"></div>
|
||||
</afx-hbox>
|
||||
<div data-height="10"></div>
|
||||
</afx-vbox>
|
||||
</afx-app-window>
|
@ -5,6 +5,8 @@ It is used to show the change logs of the current AntOS version
|
||||
|
||||
## Change logs
|
||||
### v0.1.1-b
|
||||
* Increase default window size
|
||||
### v0.1.1-b
|
||||
* add missing css file
|
||||
|
||||
### v0.1.0-b
|
||||
|
@ -4,14 +4,12 @@ afx-app-window[data-id = "About"] a:hover
|
||||
{
|
||||
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-y:auto;
|
||||
}
|
||||
afx-app-window[data-id = "About"] div[data-id="container"]
|
||||
{
|
||||
text-align: justify;
|
||||
/*text-align: justify;*/
|
||||
}
|
||||
|
||||
afx-app-window[data-id = "About"] img
|
||||
|
@ -7,7 +7,7 @@
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "xsang.le@gmail.com"
|
||||
},
|
||||
"version":"0.1.1-b",
|
||||
"version":"0.1.2-b",
|
||||
"category":"Utility",
|
||||
"iconclass":"fa fa-question-circle",
|
||||
"mimes":["none"],
|
||||
|
@ -1,16 +1,8 @@
|
||||
<afx-app-window apptitle="__(About AntOS)" width="450" height="500" data-id="About">
|
||||
<afx-vbox >
|
||||
<div data-height="10"></div>
|
||||
<afx-hbox data-id="wrapper">
|
||||
<div data-width="10"></div>
|
||||
<div data-id="container"></div>
|
||||
<div data-width="10"></div>
|
||||
</afx-hbox>
|
||||
<div data-height="5"></div>
|
||||
<afx-hbox data-height="23" style="text-align: right;">
|
||||
<afx-app-window apptitle="__(About AntOS)" width="600" height="500" data-id="About">
|
||||
<afx-vbox padding="10">
|
||||
<div data-id="container"></div>
|
||||
<afx-hbox data-height="35" style="text-align: right;">
|
||||
<afx-button data-id="btnclose" text="__(Close)"></afx-button>
|
||||
<div data-width="5"></div>
|
||||
</afx-hbox>
|
||||
<div data-height="10"></div>
|
||||
</afx-vbox>
|
||||
</afx-app-window>
|
Binary file not shown.
@ -4,14 +4,12 @@ afx-app-window[data-id = "About"] a:hover
|
||||
{
|
||||
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-y:auto;
|
||||
}
|
||||
afx-app-window[data-id = "About"] div[data-id="container"]
|
||||
{
|
||||
text-align: justify;
|
||||
/*text-align: justify;*/
|
||||
}
|
||||
|
||||
afx-app-window[data-id = "About"] img
|
||||
|
@ -7,7 +7,7 @@
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "xsang.le@gmail.com"
|
||||
},
|
||||
"version":"0.1.1-b",
|
||||
"version":"0.1.2-b",
|
||||
"category":"Utility",
|
||||
"iconclass":"fa fa-question-circle",
|
||||
"mimes":["none"],
|
||||
|
@ -5,4 +5,7 @@ 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).
|
||||
|
||||
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
|
||||
|
||||
## Change logs
|
||||
### v0.0.8-b
|
||||
* Increase default window size
|
||||
|
||||
### v0.0.6-a
|
||||
* 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
|
||||
|
||||
## Change logs
|
||||
### v0.0.8-b
|
||||
* Increase default window size
|
||||
|
||||
### v0.0.6-a
|
||||
* Fix process type identification bug
|
||||
|
@ -7,7 +7,7 @@
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "xsang.le@gmail.com"
|
||||
},
|
||||
"version":"0.0.7-b",
|
||||
"version":"0.0.8-b",
|
||||
"category":"System",
|
||||
"iconclass":"fa fa-heartbeat",
|
||||
"mimes":["none"]
|
||||
|
@ -1,11 +1,6 @@
|
||||
<afx-app-window data-id = "am-window" apptitle="" width="400" height="300">
|
||||
<afx-hbox>
|
||||
<div data-width="7"></div>
|
||||
<afx-vbox>
|
||||
<div data-height="7"></div>
|
||||
<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-vbox>
|
||||
<div data-width="7"></div>
|
||||
</afx-hbox>
|
||||
<afx-app-window data-id = "am-window" apptitle="" width="500" height="400">
|
||||
<afx-vbox padding="7">
|
||||
<afx-grid-view data-id = "mygrid"></afx-grid-view>
|
||||
<afx-button data-height="35" data-id = "btkill" text = "__(Kill process)" iconclass="fa fa-times"></afx-button>
|
||||
</afx-vbox>
|
||||
</afx-app-window>
|
Binary file not shown.
@ -7,7 +7,7 @@
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "xsang.le@gmail.com"
|
||||
},
|
||||
"version":"0.0.7-b",
|
||||
"version":"0.0.8-b",
|
||||
"category":"System",
|
||||
"iconclass":"fa fa-heartbeat",
|
||||
"mimes":["none"]
|
||||
|
@ -1,11 +1,6 @@
|
||||
<afx-app-window data-id = "am-window" apptitle="" width="400" height="300">
|
||||
<afx-hbox>
|
||||
<div data-width="7"></div>
|
||||
<afx-vbox>
|
||||
<div data-height="7"></div>
|
||||
<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-vbox>
|
||||
<div data-width="7"></div>
|
||||
</afx-hbox>
|
||||
<afx-app-window data-id = "am-window" apptitle="" width="500" height="400">
|
||||
<afx-vbox padding="7">
|
||||
<afx-grid-view data-id = "mygrid"></afx-grid-view>
|
||||
<afx-button data-height="35" data-id = "btkill" text = "__(Kill process)" iconclass="fa fa-times"></afx-button>
|
||||
</afx-vbox>
|
||||
</afx-app-window>
|
@ -5,6 +5,9 @@ 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
|
||||
|
@ -2,7 +2,7 @@
|
||||
<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="30">
|
||||
<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>
|
||||
@ -15,7 +15,7 @@
|
||||
<!--extension tab-->
|
||||
<afx-hbox data-height="100%" iconclass="bi bi-puzzle" >
|
||||
<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-vbox>
|
||||
</afx-hbox>
|
||||
@ -26,17 +26,17 @@
|
||||
<afx-vbox data-id="editor-main-container">
|
||||
<afx-hbox>
|
||||
<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>
|
||||
</afx-vbox>
|
||||
<afx-resizer data-width="3"></afx-resizer>
|
||||
<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>
|
||||
</afx-vbox>
|
||||
</afx-hbox>
|
||||
<afx-resizer data-height = "3" dir = "ve" attachnext = "true" ></afx-resizer>
|
||||
<afx-tab-container data-id = "bottombar" data-height="150" min-height="150" tabbarheight= "22">
|
||||
<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" >
|
||||
|
@ -27,8 +27,10 @@
|
||||
"require":["ts"],
|
||||
"jobs": [
|
||||
{
|
||||
"name": "ts-import",
|
||||
"data": ["sdk://core/ts/core.d.ts", "sdk://core/ts/jquery.d.ts","sdk://core/ts/antos.d.ts"]
|
||||
"name": "ts-antos-sdk",
|
||||
"data": {
|
||||
"version": "2.0.x"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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;
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
padding-right: 20px;
|
||||
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;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "mrsang@iohub.dev"
|
||||
},
|
||||
"version": "0.2.3-b",
|
||||
"version": "0.2.6-b",
|
||||
"category": "Development",
|
||||
"iconclass": "bi bi-journal-code",
|
||||
"mimes": [
|
||||
|
@ -59,8 +59,7 @@ namespace OS {
|
||||
});
|
||||
}
|
||||
|
||||
protected _wr(t: string, d: any): Promise<any> {
|
||||
this.cache = d;
|
||||
protected _wr(t: string): Promise<any> {
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
resolve({
|
||||
|
@ -561,7 +561,7 @@ namespace OS {
|
||||
this.loadExtensionMetaData();
|
||||
this.toggleSideBar();
|
||||
this.toggleSplitMode();
|
||||
this.applyAllSetting();
|
||||
//this.applyAllSetting();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -718,7 +718,7 @@ namespace OS {
|
||||
|
||||
showOutput(toggle: boolean = false): void {
|
||||
if (toggle)
|
||||
this.showBottomBar(true);
|
||||
this.setting.showBottomBar = true;
|
||||
this.bottombar.selectedIndex = 0;
|
||||
}
|
||||
|
||||
@ -749,7 +749,6 @@ namespace OS {
|
||||
* @memberof Antedit
|
||||
*/
|
||||
public showBottomBar(v: boolean): void {
|
||||
this.setting.showBottomBar = v;
|
||||
if (v) {
|
||||
$(this.bottombar).show();
|
||||
}
|
||||
@ -765,7 +764,7 @@ namespace OS {
|
||||
* @memberof Antedit
|
||||
*/
|
||||
private toggleBottomBar(): void {
|
||||
this.showBottomBar(!this.setting.showBottomBar);
|
||||
this.setting.showBottomBar = !this.setting.showBottomBar;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -817,7 +816,7 @@ namespace OS {
|
||||
dataid: "recent",
|
||||
nodes: recent,
|
||||
onchildselect: (
|
||||
e: GUI.TagEventType<GUI.tag.MenuEventData>,
|
||||
e: GUI.TagEventType<GUI.tag.StackMenuEventData>,
|
||||
r: Antedit
|
||||
) => {
|
||||
const handle = e.data.item.data.text.asFileHandle();
|
||||
@ -849,7 +848,7 @@ namespace OS {
|
||||
},
|
||||
],
|
||||
onchildselect: (
|
||||
e: GUI.TagEventType<GUI.tag.MenuEventData>,
|
||||
e: GUI.TagEventType<GUI.tag.StackMenuEventData>,
|
||||
r: Antedit
|
||||
) => {
|
||||
return this.menuAction(e.data.item.data.dataid, r);
|
||||
@ -866,9 +865,9 @@ namespace OS {
|
||||
* @memberof Antedit
|
||||
*/
|
||||
private ctxFileMenuHandle(
|
||||
e: GUI.TagEventType<GUI.tag.MenuEventData>
|
||||
e: GUI.TagEventType<GUI.tag.StackMenuEventData>
|
||||
): void {
|
||||
const el = e.data.item as GUI.tag.MenuEntryTag;
|
||||
const el = e.data.item;
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
@ -1150,7 +1149,7 @@ namespace OS {
|
||||
}
|
||||
],
|
||||
onchildselect: (
|
||||
e: GUI.TagEventType<GUI.tag.MenuEventData>,
|
||||
e: GUI.TagEventType<GUI.tag.StackMenuEventData>,
|
||||
r: EditorFileHandle
|
||||
) => {
|
||||
switch (e.data.item.data.dataid) {
|
||||
|
Binary file not shown.
@ -17,8 +17,10 @@
|
||||
"data": ["build","build/debug","build/release"]
|
||||
},
|
||||
{
|
||||
"name": "ts-import",
|
||||
"data": ["sdk://core/ts/core.d.ts", "sdk://core/ts/jquery.d.ts","sdk://core/ts/antos.d.ts"]
|
||||
"name": "ts-antos-sdk",
|
||||
"data": {
|
||||
"version": "2.0.x"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ts-compile",
|
||||
|
Binary file not shown.
@ -9,7 +9,10 @@ Small application for zip file manager
|
||||
|
||||
## 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
|
||||
|
||||
### 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-tree-view data-id="filetree"></afx-tree-view>
|
||||
<div data-height="5"></div>
|
||||
<afx-hbox data-height="30">
|
||||
<div data-width="10"></div>
|
||||
<afx-hbox data-height="55" padding="10">
|
||||
<div style="text-align: left;">
|
||||
<afx-button iconclass="fa fa-plus-circle" data-id="btaradd"></afx-button>
|
||||
<afx-button iconclass="fa fa-minus-circle" data-id="btardel"></afx-button>
|
||||
@ -11,7 +9,6 @@
|
||||
<div style="text-align: right;">
|
||||
<afx-button text="__(Extract)" data-id="btarxtract"></afx-button>
|
||||
</div>
|
||||
<div data-width="10"></div>
|
||||
</afx-hbox>
|
||||
</afx-vbox>
|
||||
</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
|
||||
|
||||
### v0.0.2-a
|
||||
### v0.0.4-a
|
||||
* Fix file dialog bug when extract zip content
|
||||
|
||||
### v0.0.3-a
|
||||
* Change category to utility
|
||||
|
||||
### v0.0.2-a
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
afx-app-window[data-id="Archive"] afx-tree-view .afx-tree-view-folder-close:before{
|
||||
content: "\f07b";
|
||||
font-family: "FontAwesome";
|
||||
|
File diff suppressed because one or more lines are too long
@ -7,7 +7,7 @@
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "mrsang@lxsang.me"
|
||||
},
|
||||
"version":"0.0.3-a",
|
||||
"version":"0.0.4-a",
|
||||
"category":"Utility",
|
||||
"iconclass":"fa fa-archive",
|
||||
"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-tree-view data-id="filetree"></afx-tree-view>
|
||||
<div data-height="5"></div>
|
||||
<afx-hbox data-height="30">
|
||||
<div data-width="10"></div>
|
||||
<afx-hbox data-height="55" padding="10">
|
||||
<div style="text-align: left;">
|
||||
<afx-button iconclass="fa fa-plus-circle" data-id="btaradd"></afx-button>
|
||||
<afx-button iconclass="fa fa-minus-circle" data-id="btardel"></afx-button>
|
||||
@ -11,7 +9,6 @@
|
||||
<div style="text-align: right;">
|
||||
<afx-button text="__(Extract)" data-id="btarxtract"></afx-button>
|
||||
</div>
|
||||
<div data-width="10"></div>
|
||||
</afx-hbox>
|
||||
</afx-vbox>
|
||||
</afx-app-window>
|
Binary file not shown.
@ -20,7 +20,7 @@ class Archive extends this.OS.application.BaseApplication
|
||||
item = @filetree.selectedItem
|
||||
return @notify __("Please select file/folder to extract") unless item
|
||||
treedata = item.data
|
||||
@openDialog "FileDialog", { title: __("Select a folder"), mimes: ["dir"] }
|
||||
@openDialog "FileDialog", { title: __("Select a folder"), type: "dir" }
|
||||
.then (d) =>
|
||||
@xtract(treedata, d.file.path)
|
||||
.then () => @notify __("extract successful: {0}", treedata.path)
|
||||
|
@ -7,7 +7,7 @@
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "mrsang@lxsang.me"
|
||||
},
|
||||
"version":"0.0.3-a",
|
||||
"version":"0.0.4-a",
|
||||
"category":"Utility",
|
||||
"iconclass":"fa fa-archive",
|
||||
"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(`![](${this._api.handle.shared}/${r.result})`);
|
||||
}).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
|
||||
### v0.2.5-a
|
||||
* Use the new MDE library
|
||||
### v0.2.3-a
|
||||
* Chage app category name
|
||||
### 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
|
||||
### v0.2.5-a
|
||||
* Use the new MDE library
|
||||
### v0.2.3-a
|
||||
* Chage app category name
|
||||
### v0.2.1-a
|
||||
|
File diff suppressed because one or more lines are too long
@ -7,9 +7,9 @@
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "mrsang@lxsang.me"
|
||||
},
|
||||
"version":"0.2.4-a",
|
||||
"version":"0.2.5-a",
|
||||
"category":"Office",
|
||||
"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"]
|
||||
}
|
Binary file not shown.
@ -166,7 +166,7 @@ class Booklet extends this.OS.application.BaseApplication
|
||||
@previewOn = false
|
||||
@editormux = false
|
||||
|
||||
@editor = new SimpleMDE
|
||||
@editor = new EasyMDE
|
||||
element: markarea
|
||||
autoDownloadFontAwesome: false
|
||||
autofocus: true
|
||||
@ -232,7 +232,7 @@ class Booklet extends this.OS.application.BaseApplication
|
||||
name: __("Preview"),
|
||||
className: "fa fa-eye no-disable",
|
||||
action: (e) =>
|
||||
SimpleMDE.togglePreview e
|
||||
EasyMDE.togglePreview e
|
||||
#/console.log @select ".editor-preview editor-preview-active"
|
||||
renderMathInElement @find "mycontainer"
|
||||
@renderLocalElement()
|
||||
@ -246,7 +246,7 @@ class Booklet extends this.OS.application.BaseApplication
|
||||
|
||||
preview.innerHTML = html
|
||||
|
||||
@on "hboxchange", (e) => @resizeContent()
|
||||
@on "resize", (e) => @resizeContent()
|
||||
@bindKey "ALT-N", () => @actionFile "#{@name}-New"
|
||||
@bindKey "ALT-O", () => @actionFile "#{@name}-Open"
|
||||
@bindKey "CTRL-S", () => @actionFile "#{@name}-Save"
|
||||
@ -263,12 +263,12 @@ class Booklet extends this.OS.application.BaseApplication
|
||||
@currentToc.descFile.cache = @editor.value()
|
||||
|
||||
resizeContent: () ->
|
||||
children = ($ @container).children()
|
||||
children = ($ ".EasyMDEContainer", @container).children()
|
||||
titlebar = (($ @scheme).find ".afx-window-top")[0]
|
||||
toolbar = children[1]
|
||||
statusbar = children[4]
|
||||
toolbar = children[0]
|
||||
statusbar = children[3]
|
||||
cheight = ($ @scheme).height() - ($ titlebar).height() - ($ toolbar).height() - ($ statusbar).height() - 40
|
||||
($ children[2]).css("height", cheight + "px")
|
||||
($ children[1]).css("height", cheight + "px")
|
||||
|
||||
|
||||
menu: () ->
|
||||
|
@ -7,9 +7,9 @@
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "mrsang@lxsang.me"
|
||||
},
|
||||
"version":"0.2.4-a",
|
||||
"version":"0.2.5-a",
|
||||
"category":"Office",
|
||||
"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"]
|
||||
}
|
@ -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
|
||||
|
||||
## 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.2-a use ALT-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-hbox >
|
||||
<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="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>
|
||||
|
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
|
||||
|
||||
## 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.2-a use ALT-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"] {
|
||||
overflow: auto;
|
||||
background-image: url("bg.jpg");
|
||||
|
File diff suppressed because one or more lines are too long
@ -6,7 +6,7 @@
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "xsang.le@gmail.com"
|
||||
},
|
||||
"version":"0.1.3-a",
|
||||
"version":"0.1.4-a",
|
||||
"category":"Utility",
|
||||
"iconclass":"fa fa-scissors",
|
||||
"mimes":["none"],
|
||||
|
@ -1,7 +1,7 @@
|
||||
<afx-app-window apptitle="Clipper" width="500" height="400" data-id="Clipper">
|
||||
<afx-hbox >
|
||||
<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="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>
|
||||
|
Binary file not shown.
@ -6,7 +6,7 @@
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "xsang.le@gmail.com"
|
||||
},
|
||||
"version":"0.1.3-a",
|
||||
"version":"0.1.4-a",
|
||||
"category":"Utility",
|
||||
"iconclass":"fa fa-scissors",
|
||||
"mimes":["none"],
|
||||
|
@ -3,4 +3,7 @@ A simple yet powerful code/text editor.
|
||||
CodePad is a text editor based on the ACE editor.
|
||||
|
||||
## 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
|
@ -1,34 +1,7 @@
|
||||
afx-app-window[data-id = "codepad"] .ace_editor {
|
||||
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 > 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 {
|
||||
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{
|
||||
background-color:#333333;
|
||||
color:#afafaf;
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
padding-right: 20px;
|
||||
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;
|
||||
}
|
||||
|
||||
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"] {
|
||||
overflow-y: auto;
|
||||
|
@ -10,17 +10,17 @@
|
||||
<afx-vbox>
|
||||
<afx-hbox>
|
||||
<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>
|
||||
</afx-vbox>
|
||||
<afx-resizer data-width="3"></afx-resizer>
|
||||
<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>
|
||||
</afx-vbox>
|
||||
</afx-hbox>
|
||||
<afx-resizer data-height = "3" dir = "ve" attachnext = "true" ></afx-resizer>
|
||||
<afx-tab-container data-id = "bottombar" data-height="150" min-height="150" tabbarheight= "22">
|
||||
<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" >
|
||||
|
@ -17,8 +17,10 @@
|
||||
"data": ["build","build/debug","build/release"]
|
||||
},
|
||||
{
|
||||
"name": "ts-import",
|
||||
"data": ["sdk://core/ts/core.d.ts", "sdk://core/ts/jquery.d.ts","sdk://core/ts/antos.d.ts"]
|
||||
"name": "ts-antos-sdk",
|
||||
"data": {
|
||||
"version": "2.0.x"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ts-compile",
|
||||
|
@ -3,4 +3,7 @@ A simple yet powerful code/text editor.
|
||||
CodePad is a text editor based on the ACE editor.
|
||||
|
||||
## 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
|
@ -1,34 +1,7 @@
|
||||
afx-app-window[data-id = "codepad"] .ace_editor {
|
||||
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 > 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 {
|
||||
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{
|
||||
background-color:#333333;
|
||||
color:#afafaf;
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
padding-right: 20px;
|
||||
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;
|
||||
}
|
||||
|
||||
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"] {
|
||||
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…
Reference in New Issue
Block a user