Merge branch 'origin/2.0.x' to master
All checks were successful
gitea-sync/antosdk-apps/pipeline/head This commit looks good

This commit is contained in:
DanyLE 2024-04-29 11:43:45 +02:00
commit 23b64bef4a
883 changed files with 18113 additions and 146658 deletions

View File

@ -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.

View File

@ -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

View File

@ -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>
<afx-app-window apptitle="__(About AntOS)" width="600" height="500" data-id="About">
<afx-vbox padding="10">
<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-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>

View File

@ -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

View File

@ -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

View File

@ -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"],

View File

@ -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>
<afx-app-window apptitle="__(About AntOS)" width="600" height="500" data-id="About">
<afx-vbox padding="10">
<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-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.

View File

@ -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

View File

@ -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"],

View File

@ -6,3 +6,6 @@ It's built on top of google-diff-match-patch library. That lib handles the hard
Github page: [https://github.com/ace-diff/ace-diff](https://github.com/ace-diff/ace-diff).
The ACE diff depends on the ACECore package.
## Change logs
- v0.1.1-a: add dependencies

Binary file not shown.

View File

@ -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

View 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"
}
}
]
}
}
}

View File

@ -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

View File

@ -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"]

View File

@ -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-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="30" data-id = "btkill" text = "__(Kill process)" iconclass="fa fa-times"></afx-button>
<afx-button data-height="35" data-id = "btkill" text = "__(Kill process)" iconclass="fa fa-times"></afx-button>
</afx-vbox>
<div data-width="7"></div>
</afx-hbox>
</afx-app-window>

View File

@ -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"]

View File

@ -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-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="30" data-id = "btkill" text = "__(Kill process)" iconclass="fa fa-times"></afx-button>
<afx-button data-height="35" data-id = "btkill" text = "__(Kill process)" iconclass="fa fa-times"></afx-button>
</afx-vbox>
<div data-width="7"></div>
</afx-hbox>
</afx-app-window>

View File

@ -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

View File

@ -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" >

View File

@ -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",

View 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

View 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);

View 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"
}
]
}
}

View 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"
}
]
}
]

View 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;
}

File diff suppressed because one or more lines are too long

View 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"
}
}
}

View 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.

View File

@ -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;
}

View File

@ -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": [

View File

@ -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({

View File

@ -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.

View File

@ -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",

View File

@ -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

View File

@ -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
View 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"
}
}
]
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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"],

View File

@ -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.

View File

@ -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)

View File

@ -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
View 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

View 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
View 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

View 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
View 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
View 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
View 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"
}
}
]
}
}
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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;
}

File diff suppressed because one or more lines are too long

View 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}"
}
}
}

View 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>

Binary file not shown.

282
Blogger/dialogs.ts Normal file
View 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
View 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
View 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
View 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
View 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
View 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);
}
}
}

View File

@ -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
View 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"
}
}
]
}
}
}

View File

@ -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

View File

@ -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.

View File

@ -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: () ->

View File

@ -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"]
}

View File

@ -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

View File

@ -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
View 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"
}
}
]
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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"],

View File

@ -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.

View File

@ -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"],

View File

@ -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

View File

@ -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;

View File

@ -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" >

View File

@ -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",

View File

@ -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

View File

@ -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