mirror of
https://github.com/antos-rde/antosdk-apps.git
synced 2024-12-25 03:38:21 +01:00
Blogger: migrate code to typescript,use SQLiteDB as database access API
This commit is contained in:
parent
c8ddd5ec6e
commit
5dec0a2b56
@ -6,6 +6,7 @@ Blackend for my blog at https://blog.iohub.dev
|
|||||||
## Change logs
|
## Change logs
|
||||||
|
|
||||||
### v0.2.x-a
|
### v0.2.x-a
|
||||||
|
* Patch 10: Migrate code to typescript, use SQLiteDB lib for database access
|
||||||
* Patch 9: Update to use the new MDE library
|
* Patch 9: Update to use the new MDE library
|
||||||
* Patch 8: Support for antOS 2.0.x
|
* Patch 8: Support for antOS 2.0.x
|
||||||
* Patch 7: Fix sendmail API security bug
|
* Patch 7: Fix sendmail API security bug
|
||||||
|
@ -6,6 +6,7 @@ Blackend for my blog at https://blog.iohub.dev
|
|||||||
## Change logs
|
## Change logs
|
||||||
|
|
||||||
### v0.2.x-a
|
### v0.2.x-a
|
||||||
|
* Patch 10: Migrate code to typescript, use SQLiteDB lib for database access
|
||||||
* Patch 9: Update to use the new MDE library
|
* Patch 9: Update to use the new MDE library
|
||||||
* Patch 8: Support for antOS 2.0.x
|
* Patch 8: Support for antOS 2.0.x
|
||||||
* Patch 7: Fix sendmail API security bug
|
* Patch 7: Fix sendmail API security bug
|
||||||
|
File diff suppressed because one or more lines are too long
@ -6,7 +6,7 @@
|
|||||||
"author": "Xuan Sang LE",
|
"author": "Xuan Sang LE",
|
||||||
"email": "xsang.le@gmail.com"
|
"email": "xsang.le@gmail.com"
|
||||||
},
|
},
|
||||||
"version": "0.2.9-a",
|
"version": "0.2.10-a",
|
||||||
"category": "Internet",
|
"category": "Internet",
|
||||||
"iconclass": "fa fa-book",
|
"iconclass": "fa fa-book",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
@ -36,7 +36,6 @@
|
|||||||
"No email selected": "No email selected",
|
"No email selected": "No email selected",
|
||||||
"Unable to send mail to: {0}": "Unable to send mail to: {0}",
|
"Unable to send mail to: {0}": "Unable to send mail to: {0}",
|
||||||
"Error sending mail: {0}": "Error sending mail: {0}",
|
"Error sending mail: {0}": "Error sending mail: {0}",
|
||||||
"Cannot fetch subscribers data: {0}": "Cannot fetch subscribers data: {0}",
|
|
||||||
"Open/create new database": "Open/create new database",
|
"Open/create new database": "Open/create new database",
|
||||||
"Unable to init database file: {0}": "Unable to init database file: {0}",
|
"Unable to init database file: {0}": "Unable to init database file: {0}",
|
||||||
"Select image file": "Select image file",
|
"Select image file": "Select image file",
|
||||||
@ -65,28 +64,20 @@
|
|||||||
"No post selected": "No post selected",
|
"No post selected": "No post selected",
|
||||||
"Emails sent": "Emails sent",
|
"Emails sent": "Emails sent",
|
||||||
"Error sending mails: {0}": "Error sending mails: {0}",
|
"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",
|
"Cannot fetch the entry content": "Cannot fetch the entry content",
|
||||||
"Delete a post": "Delete a post",
|
"Delete a post": "Delete a post",
|
||||||
"Do you really want to delete this post ?": "Do you really want to delete this 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",
|
"Cannot fetch user data": "Cannot fetch user data",
|
||||||
"Full name must be entered": "Full name must be entered",
|
"Full name must be entered": "Full name must be entered",
|
||||||
"User data updated": "User data updated",
|
"User data updated": "User data updated",
|
||||||
"Cannot save user data": "Cannot save user data",
|
"Cannot save user data: {0}": "Cannot save user data: {0}",
|
||||||
"Unable to load categories": "Unable to load categories",
|
"Unable to load categories": "Unable to load categories",
|
||||||
"Found {0} sections": "Found {0} sections",
|
"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 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",
|
"Please enter tags": "Please enter tags",
|
||||||
"Cannot save blog: {0}": "Cannot save blog: {0}",
|
"Cannot save blog: {0}": "Cannot save blog: {0}",
|
||||||
"Cannot add new category": "Cannot add new category",
|
"No more record to load": "No more record to load",
|
||||||
"Unable to fetch categories": "Unable to fetch categories",
|
|
||||||
"Cannot Edit category": "Cannot Edit category",
|
|
||||||
"Cannot save section: {0}": "Cannot save section: {0}",
|
|
||||||
"Cannot move section": "Cannot move section",
|
|
||||||
"Cannot delete the category: {0} [{1}]": "Cannot delete the category: {0} [{1}]",
|
|
||||||
"Cannot delete all content of: {0} [{1}]": "Cannot delete all content of: {0} [{1}]",
|
|
||||||
"No post found: {0}": "No post found: {0}",
|
|
||||||
"Created: {0}": "Created: {0}",
|
|
||||||
"Updated: {0}": "Updated: {0}",
|
|
||||||
"Full name": "Full name",
|
"Full name": "Full name",
|
||||||
"Address": "Address",
|
"Address": "Address",
|
||||||
"Phone": "Phone",
|
"Phone": "Phone",
|
||||||
@ -95,7 +86,10 @@
|
|||||||
"Photo": "Photo",
|
"Photo": "Photo",
|
||||||
"Short biblio": "Short biblio",
|
"Short biblio": "Short biblio",
|
||||||
"Categories": "Categories",
|
"Categories": "Categories",
|
||||||
"Tags": "Tags"
|
"Load more": "Load more",
|
||||||
|
"Tags": "Tags",
|
||||||
|
"Created: {0}": "Created: {0}",
|
||||||
|
"Updated: {0}": "Updated: {0}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
<afx-app-window data-id = "blogger-win" apptitle="Blogger" width="650" height="500">
|
<afx-app-window data-id = "blogger-win" apptitle="Blogger" width="650" height="500">
|
||||||
<afx-hbox >
|
<afx-hbox padding="5">
|
||||||
<afx-tab-container data-id = "tabcontainer" dir = "row" tabbarwidth= "40">
|
<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-hbox data-id="user-container" data-height="100%" iconclass="fa fa-user-circle">
|
||||||
@ -65,7 +65,10 @@
|
|||||||
|
|
||||||
|
|
||||||
<afx-hbox data-id = "blog-container" data-height="100%" iconclass="fa fa-book">
|
<afx-hbox data-id = "blog-container" data-height="100%" iconclass="fa fa-book">
|
||||||
<afx-list-view data-id = "blog-list" min-width="100" data-width="200"></afx-list-view>
|
<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-resizer data-width = "3"></afx-resizer>
|
||||||
<afx-vbox>
|
<afx-vbox>
|
||||||
<div data-id = "editor-container">
|
<div data-id = "editor-container">
|
||||||
|
Binary file not shown.
@ -1,30 +0,0 @@
|
|||||||
<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>
|
|
@ -1,189 +0,0 @@
|
|||||||
# 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/.
|
|
||||||
class BloggerCategoryDialog extends this.OS.GUI.BasicDialog
|
|
||||||
constructor: () ->
|
|
||||||
super "BloggerCategoryDialog", BloggerCategoryDialog.scheme
|
|
||||||
|
|
||||||
main: () ->
|
|
||||||
super.main()
|
|
||||||
@tree = @find "tree"
|
|
||||||
@txtinput = @find "txtinput"
|
|
||||||
|
|
||||||
(@find "bt-ok").onbtclick = (e) =>
|
|
||||||
sel = @tree.selectedItem
|
|
||||||
return @notify __("Please select a parent category") unless sel
|
|
||||||
seldata = sel.data
|
|
||||||
val = @txtinput.value
|
|
||||||
return @notify __("Please enter category name") if val is "" and not @data.selonly
|
|
||||||
return @notify __("Parent can not be the category itself") if @data.cat and @data.cat.id is seldata.id
|
|
||||||
@handle { p: seldata, value: val } if @handle
|
|
||||||
@quit()
|
|
||||||
|
|
||||||
(@find "bt-cancel").onbtclick = (e) =>
|
|
||||||
@quit()
|
|
||||||
if @data and @data.tree
|
|
||||||
if @data and @data.cat
|
|
||||||
@txtinput.value = @data.cat.name
|
|
||||||
if @data.cat.pid is "0"
|
|
||||||
seldata = @data.tree
|
|
||||||
else
|
|
||||||
seldata = @findDataByID @data.cat.pid, @data.tree.nodes
|
|
||||||
seldata.selected = true if seldata
|
|
||||||
@tree.data = @data.tree
|
|
||||||
@tree.expandAll()
|
|
||||||
# TODO set selected category name
|
|
||||||
|
|
||||||
findDataByID: (id, list) ->
|
|
||||||
for data in list
|
|
||||||
return data if data.id is id
|
|
||||||
if data.nodes
|
|
||||||
@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
|
|
||||||
|
|
||||||
class BloggerCVSectionDiaglog extends this.OS.GUI.BasicDialog
|
|
||||||
constructor: (parent) ->
|
|
||||||
file = "#{parent.meta().path}/cvsection.html".asFileHandle()
|
|
||||||
super "BloggerCVSectionDiaglog", file
|
|
||||||
|
|
||||||
main: () ->
|
|
||||||
super.main()
|
|
||||||
@editor = new EasyMDE
|
|
||||||
autoDownloadFontAwesome: false
|
|
||||||
element: @find "contentarea"
|
|
||||||
status: false
|
|
||||||
toolbar: false
|
|
||||||
($ (@select '[class = "CodeMirror-scroll"]')[0]).css "min-height", "50px"
|
|
||||||
($ (@select '[class="CodeMirror cm-s-paper CodeMirror-wrap"]')[0]).css "min-height", "50px"
|
|
||||||
inputs = @select "[input-class='user-input']"
|
|
||||||
(($ v).val @data.section[v.name] for v in inputs ) if @data and @data.section
|
|
||||||
@editor.value @data.section.content if @data and @data.section
|
|
||||||
(@find "section-publish").swon = (if @data and @data.section and Number(@data.section.publish) then true else false)
|
|
||||||
(@find "bt-cv-sec-save").onbtclick = (e) =>
|
|
||||||
data = {}
|
|
||||||
data[v.name] = ($ v).val() for v in inputs
|
|
||||||
data.content = @editor.value()
|
|
||||||
return @notify __("Title or content must not be blank") if data.title is "" and data.content is ""
|
|
||||||
#return @notify "Content must not be blank" if data.content is ""
|
|
||||||
data.id = @data.section.id if @data and @data.section
|
|
||||||
val = (@find "section-publish").swon
|
|
||||||
if val is true
|
|
||||||
data.publish = 1
|
|
||||||
else
|
|
||||||
data.publish = 0
|
|
||||||
@handle data if @handle
|
|
||||||
@quit()
|
|
||||||
|
|
||||||
@on "resize", () => @resizeContent()
|
|
||||||
@resizeContent()
|
|
||||||
|
|
||||||
resizeContent: () ->
|
|
||||||
container = @find "editor-container"
|
|
||||||
children = ($ ".EasyMDEContainer", container).children()
|
|
||||||
cheight = ($ container).height() - 30
|
|
||||||
($ children[0]).css("height", cheight + "px")
|
|
||||||
|
|
||||||
|
|
||||||
# this dialog is for send mail
|
|
||||||
class BloggerSendmailDiaglog extends this.OS.GUI.BasicDialog
|
|
||||||
constructor: (parent) ->
|
|
||||||
file = "#{parent.meta().path}/sendmail.html".asFileHandle()
|
|
||||||
super "BloggerSendmailDiaglog", file
|
|
||||||
|
|
||||||
main: () ->
|
|
||||||
super.main()
|
|
||||||
@subdb = new @.parent._api.DB("subscribers")
|
|
||||||
@maillinglist = @find "email-list"
|
|
||||||
title = (new RegExp "^#+(.*)\n", "g").exec @data.content
|
|
||||||
(@find "mail-title").value = title[1]
|
|
||||||
content = (@data.content.substring 0, 500) + "..."
|
|
||||||
(@find "contentarea").value = BloggerSendmailDiaglog.template.format @data.id, content
|
|
||||||
|
|
||||||
@subdb.find {}
|
|
||||||
.then (d) =>
|
|
||||||
for v in d
|
|
||||||
v.text = v.name
|
|
||||||
v.switch = true
|
|
||||||
v.checked = true
|
|
||||||
@maillinglist. items = d
|
|
||||||
.catch (e) =>
|
|
||||||
@error __("Cannot fetch subscribers data: {0}", e.toString()), e
|
|
||||||
|
|
||||||
(@find "bt-sendmail").onbtclick = (e) =>
|
|
||||||
items = @maillinglist.items
|
|
||||||
emails = []
|
|
||||||
for v in items
|
|
||||||
if v.checked is true
|
|
||||||
console.log v.email
|
|
||||||
emails.push v.email
|
|
||||||
|
|
||||||
return @notify __("No email selected") if emails.length is 0
|
|
||||||
# send the email
|
|
||||||
data =
|
|
||||||
path: "#{@parent.path()}/sendmail.lua",
|
|
||||||
parameters:
|
|
||||||
to: emails,
|
|
||||||
title: (@find "mail-title").value,
|
|
||||||
content: (@find "contentarea").value
|
|
||||||
@_api.apigateway data, false
|
|
||||||
.then (d) =>
|
|
||||||
return @notify __("Unable to send mail to: {0}", d.result.join(",")) if d.error
|
|
||||||
@quit()
|
|
||||||
.catch (e) =>
|
|
||||||
console.log e
|
|
||||||
@error __("Error sending mail: {0}", e.toString()), e
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
BloggerSendmailDiaglog.template = """
|
|
||||||
Hello,
|
|
||||||
|
|
||||||
Xuan Sang LE has just published a new post on his blog: https://blog.lxsang.me/post/id/{0}
|
|
||||||
|
|
||||||
==========
|
|
||||||
{1}
|
|
||||||
==========
|
|
||||||
|
|
||||||
|
|
||||||
Read the full article via:
|
|
||||||
https://blog.lxsang.me/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
|
|
||||||
"""
|
|
278
Blogger/dialogs.ts
Normal file
278
Blogger/dialogs.ts
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
// 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>\s
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 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.ListViewTag;
|
||||||
|
// 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.ListViewTag;
|
||||||
|
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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.maillinglist.data = mlist;
|
||||||
|
|
||||||
|
return (this.find("bt-sendmail") as OS.GUI.tag.ButtonTag).onbtclick = (e: any) => {
|
||||||
|
const items = this.maillinglist.data;
|
||||||
|
const emails = [];
|
||||||
|
for (let v of items) {
|
||||||
|
if (v.checked === true) {
|
||||||
|
console.log(v.email);
|
||||||
|
emails.push(v.email);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emails.length === 0) { return this.notify(__("No email selected")); }
|
||||||
|
// send the email
|
||||||
|
const data = {
|
||||||
|
path: `${this.meta().path}/sendmail.lua`,
|
||||||
|
parameters: {
|
||||||
|
to: emails,
|
||||||
|
title: (this.find("mail-title") as HTMLInputElement).value,
|
||||||
|
content: (this.find("contentarea") as HTMLTextAreaElement).value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return this._api.apigateway(data, false)
|
||||||
|
.then((d: { error: any; result: { join: (arg0: string) => any; }; }) => {
|
||||||
|
if (d.error) { return this.notify(__("Unable to send mail to: {0}", d.result.join(","))); }
|
||||||
|
return this.quit();
|
||||||
|
}).catch((e) => {
|
||||||
|
console.log(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="500" height="400" resizable = "false">
|
||||||
|
<afx-hbox>
|
||||||
|
<afx-menu data-width="150" data-id="email-list"></afx-menu>
|
||||||
|
<afx-resizer data-width="3"></afx-resizer>
|
||||||
|
<div data-width="5"></div>
|
||||||
|
<afx-vbox >
|
||||||
|
<div data-height="5"></div>
|
||||||
|
<afx-label data-height="20" text = "__(Title)"></afx-label>
|
||||||
|
<input type = "text" data-height="20" name="title" data-id = "mail-title"></input>
|
||||||
|
<afx-label data-height = "20" text = "Content" ></afx-label>
|
||||||
|
<textarea name="content" data-id = "contentarea" ></textarea>
|
||||||
|
<div data-height="5"></div>
|
||||||
|
<afx-hbox data-height = "30">
|
||||||
|
<div></div>
|
||||||
|
<afx-button iconclass = "fa fa-paper-plane" data-id = "bt-sendmail" data-width="60" text = "__(Send)"></afx-button>
|
||||||
|
</afx-hbox>
|
||||||
|
</afx-vbox>
|
||||||
|
<div data-width="5"></div>
|
||||||
|
</afx-hbox>
|
||||||
|
</afx-app-window>`;
|
||||||
|
|
||||||
|
|
||||||
|
BloggerSendmailDiaglog.template = `\
|
||||||
|
Hello,
|
||||||
|
|
||||||
|
Xuan Sang 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\
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,506 +0,0 @@
|
|||||||
# 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/.
|
|
||||||
class Blogger extends this.OS.application.BaseApplication
|
|
||||||
constructor: (args) ->
|
|
||||||
super "Blogger", args
|
|
||||||
|
|
||||||
|
|
||||||
main: () ->
|
|
||||||
@user = {}
|
|
||||||
@cvlist = @find "cv-list"
|
|
||||||
@cvlist.ontreeselect = (d) =>
|
|
||||||
return unless d
|
|
||||||
data = d.data.item.data
|
|
||||||
@CVSectionByCID Number(data.id)
|
|
||||||
|
|
||||||
@inputtags = @.find "input-tags"
|
|
||||||
@bloglist = @find "blog-list"
|
|
||||||
@seclist = @find "cv-sec-list"
|
|
||||||
|
|
||||||
el = @find("photo")
|
|
||||||
$(el)
|
|
||||||
.click (e) =>
|
|
||||||
@openDialog("FileDialog", {
|
|
||||||
title: __("Select image file"),
|
|
||||||
mimes: ["image/.*"]
|
|
||||||
})
|
|
||||||
.then (d) =>
|
|
||||||
el.value = d.file.path
|
|
||||||
.catch (e) => @error __("Unable to get file"), e
|
|
||||||
|
|
||||||
@userdb = new @_api.DB("user")
|
|
||||||
@cvcatdb = new @_api.DB("cv_cat")
|
|
||||||
@cvsecdb = new @_api.DB("cv_sections")
|
|
||||||
@blogdb = new @_api.DB("blogs")
|
|
||||||
|
|
||||||
|
|
||||||
@tabcontainer = @find "tabcontainer"
|
|
||||||
@tabcontainer.ontabselect = (e) =>
|
|
||||||
@fetchData e.data.container.aid
|
|
||||||
|
|
||||||
(@find "bt-user-save").onbtclick = (e) =>
|
|
||||||
@saveUser()
|
|
||||||
|
|
||||||
(@find "cv-cat-add").onbtclick = (e) =>
|
|
||||||
fn = (tree) =>
|
|
||||||
@openDialog(new BloggerCategoryDialog(), {
|
|
||||||
title: __("Add category"),
|
|
||||||
tree: tree
|
|
||||||
}).then (d) =>
|
|
||||||
c =
|
|
||||||
name: d.value,
|
|
||||||
pid: d.p.id,
|
|
||||||
publish: 1
|
|
||||||
@cvcatdb.save c
|
|
||||||
.then (r) =>
|
|
||||||
@refreshCVCat()
|
|
||||||
.catch (e) => @error __("Cannot add new category"), e
|
|
||||||
.catch (e) => @error e.toString(), e
|
|
||||||
@fetchCVCat()
|
|
||||||
.then (tree) => fn(tree)
|
|
||||||
.catch (e) =>
|
|
||||||
data =
|
|
||||||
text: "Porfolio",
|
|
||||||
id:"0",
|
|
||||||
nodes: []
|
|
||||||
fn(data)
|
|
||||||
@error __("Unable to fetch categories"), e
|
|
||||||
|
|
||||||
(@find "cv-cat-edit").onbtclick = (e) =>
|
|
||||||
sel = @cvlist.selectedItem
|
|
||||||
return unless sel
|
|
||||||
cat = sel.data
|
|
||||||
return unless cat
|
|
||||||
@fetchCVCat().then (tree) =>
|
|
||||||
@openDialog(new BloggerCategoryDialog(), {
|
|
||||||
title: __("Edit category"),
|
|
||||||
tree: tree, cat: cat
|
|
||||||
}).then (d) =>
|
|
||||||
c =
|
|
||||||
id: cat.id,
|
|
||||||
publish: cat.publish,
|
|
||||||
pid: d.p.id,
|
|
||||||
name: d.value
|
|
||||||
|
|
||||||
@cvcatdb.save c
|
|
||||||
.then (r) =>
|
|
||||||
@refreshCVCat()
|
|
||||||
.catch (e) =>
|
|
||||||
@error __("Cannot Edit category"), e
|
|
||||||
.catch (e) => @error __("Unable to fetch categories"), e
|
|
||||||
|
|
||||||
(@find "cv-cat-del").onbtclick = (e) =>
|
|
||||||
sel = @cvlist.selectedItem
|
|
||||||
return unless sel
|
|
||||||
cat = sel.data
|
|
||||||
return unless cat
|
|
||||||
@openDialog("YesNoDialog", {
|
|
||||||
title: __("Delete category") ,
|
|
||||||
iconclass: "fa fa-question-circle",
|
|
||||||
text: __("Do you really want to delete: {0}?", cat.name)
|
|
||||||
}).then (d) =>
|
|
||||||
return unless d
|
|
||||||
@deleteCVCat cat
|
|
||||||
.catch (e) => @error e.toString(), e
|
|
||||||
|
|
||||||
(@find "cv-sec-add").onbtclick = (e) =>
|
|
||||||
sel = @cvlist.selectedItem
|
|
||||||
return unless sel
|
|
||||||
cat = sel.data
|
|
||||||
return @notify __("Please select a category") unless cat and cat.id isnt "0"
|
|
||||||
@openDialog(new BloggerCVSectionDiaglog(@), {
|
|
||||||
title: __("New section entry for {0}", cat.name)
|
|
||||||
}).then (d) =>
|
|
||||||
d.cid = Number cat.id
|
|
||||||
d.start = Number d.start
|
|
||||||
d.end = Number d.end
|
|
||||||
# d.publish = 1
|
|
||||||
@cvsecdb.save d
|
|
||||||
.then (r) =>
|
|
||||||
@CVSectionByCID Number(cat.id)
|
|
||||||
.catch (e) => @error __("Cannot save section: {0}", e.toString()), e
|
|
||||||
|
|
||||||
(@find "cv-sec-move").onbtclick = (e) =>
|
|
||||||
sel = (@find "cv-sec-list").selectedItem
|
|
||||||
return @notify __("Please select a section to move") unless sel
|
|
||||||
sec = sel.data
|
|
||||||
|
|
||||||
@fetchCVCat().then (tree) =>
|
|
||||||
@openDialog(new BloggerCategoryDialog(),{
|
|
||||||
title: __("Move to"),
|
|
||||||
tree: tree,
|
|
||||||
selonly: true
|
|
||||||
}).then (d) =>
|
|
||||||
c =
|
|
||||||
id: sec.id,
|
|
||||||
cid: d.p.id
|
|
||||||
|
|
||||||
@cvsecdb.save c
|
|
||||||
.then (r) =>
|
|
||||||
@CVSectionByCID(sec.cid)
|
|
||||||
(@find "cv-sec-list").unselect()
|
|
||||||
.catch (e) => @error __("Cannot move section"), e
|
|
||||||
|
|
||||||
(@find "cv-sec-edit").onbtclick = (e) =>
|
|
||||||
sel = (@find "cv-sec-list").selectedItem
|
|
||||||
return @notify __("Please select a section to edit") unless sel
|
|
||||||
sec = sel.data
|
|
||||||
@openDialog(new BloggerCVSectionDiaglog(@), {
|
|
||||||
title: __("Modify section entry"),
|
|
||||||
section: sec
|
|
||||||
}).then (d) =>
|
|
||||||
d.cid = Number sec.cid
|
|
||||||
d.start = Number d.start
|
|
||||||
d.end = Number d.end
|
|
||||||
#d.publish = Number sec.publish
|
|
||||||
@cvsecdb.save d
|
|
||||||
.then (r) =>
|
|
||||||
@CVSectionByCID Number(sec.cid)
|
|
||||||
.catch (e) => return @error __("Cannot save section: {0}", e.toString()), e
|
|
||||||
|
|
||||||
@seclist.onitemclose = (e) =>
|
|
||||||
return unless e
|
|
||||||
data = e.data.item.data
|
|
||||||
@openDialog("YesNoDialog", {
|
|
||||||
iconclass: "fa fa-question-circle",
|
|
||||||
text: __("Do you really want to delete: {0}?", data.title)
|
|
||||||
}).then (b) =>
|
|
||||||
return unless b
|
|
||||||
@cvsecdb.delete data.id
|
|
||||||
.then (r) =>
|
|
||||||
@seclist.delete e.data.item
|
|
||||||
.catch (e) => @error __("Cannot delete the section: {0}", e.toString()), e
|
|
||||||
return false
|
|
||||||
|
|
||||||
@editor = new EasyMDE
|
|
||||||
element: @find "markarea"
|
|
||||||
autoDownloadFontAwesome: false
|
|
||||||
autofocus: true
|
|
||||||
tabSize: 4
|
|
||||||
indentWithTabs: true
|
|
||||||
toolbar: [
|
|
||||||
{
|
|
||||||
name: __("New"),
|
|
||||||
className: "fa fa-file",
|
|
||||||
action: (e) =>
|
|
||||||
@bloglist.unselect()
|
|
||||||
@clearEditor()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: __("Save"),
|
|
||||||
className: "fa fa-save",
|
|
||||||
action: (e) =>
|
|
||||||
@saveBlog()
|
|
||||||
}
|
|
||||||
, "|", "bold", "italic", "heading", "|", "quote", "code",
|
|
||||||
"unordered-list", "ordered-list", "|", "link",
|
|
||||||
"image", "table", "horizontal-rule",
|
|
||||||
{
|
|
||||||
name: "image",
|
|
||||||
className: "fa fa-file-image-o",
|
|
||||||
action: (e) =>
|
|
||||||
@openDialog("FileDialog", {
|
|
||||||
title: __("Select image file"),
|
|
||||||
mimes: ["image/.*"]
|
|
||||||
}).then (d) =>
|
|
||||||
d.file.path.asFileHandle().publish()
|
|
||||||
.then (r) =>
|
|
||||||
doc = @editor.codemirror.getDoc()
|
|
||||||
doc.replaceSelection "![](#{@_api.handle.shared}/#{r.result})"
|
|
||||||
.catch (e) => @error __("Cannot export file for embedding to text"), e
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name:"Youtube",
|
|
||||||
className: "fa fa-youtube",
|
|
||||||
action: (e) =>
|
|
||||||
doc = @editor.codemirror.getDoc()
|
|
||||||
doc.replaceSelection "[[youtube:]]"
|
|
||||||
}
|
|
||||||
"|",
|
|
||||||
{
|
|
||||||
name: __("Preview"),
|
|
||||||
className: "fa fa-eye no-disable",
|
|
||||||
action: (e) =>
|
|
||||||
@previewOn = !@previewOn
|
|
||||||
EasyMDE.togglePreview e
|
|
||||||
#/console.log @select ".editor-preview editor-preview-active"
|
|
||||||
renderMathInElement @find "editor-container"
|
|
||||||
},
|
|
||||||
"|",
|
|
||||||
{
|
|
||||||
name: __("Send mail"),
|
|
||||||
className: "fa fa-paper-plane",
|
|
||||||
action: (e) =>
|
|
||||||
sel = @bloglist.selectedItem
|
|
||||||
return @error __("No post selected") unless sel
|
|
||||||
data = sel.data
|
|
||||||
@openDialog(new BloggerSendmailDiaglog(@), {
|
|
||||||
title: __("Send mail"),
|
|
||||||
content: @editor.value(),
|
|
||||||
id: data.id
|
|
||||||
})
|
|
||||||
.then (d) ->
|
|
||||||
console.log "Email sent"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
@bloglist.onlistselect = (e) =>
|
|
||||||
el = @bloglist.selectedItem
|
|
||||||
return unless el
|
|
||||||
sel = el.data
|
|
||||||
return unless sel
|
|
||||||
@blogdb.get Number(sel.id)
|
|
||||||
.then (r) =>
|
|
||||||
@editor.value r.content
|
|
||||||
@inputtags.value = r.tags
|
|
||||||
(@find "blog-publish").swon = if Number(r.publish) then true else false
|
|
||||||
.catch (e) =>
|
|
||||||
@error __("Cannot fetch the entry content"), e
|
|
||||||
|
|
||||||
@bloglist.onitemclose = (e) =>
|
|
||||||
return unless e
|
|
||||||
el = e.data.item
|
|
||||||
data = el.data
|
|
||||||
@openDialog("YesNoDialog", {
|
|
||||||
title: __("Delete a post"),
|
|
||||||
iconclass: "fa fa-question-circle",
|
|
||||||
text: __("Do you really want to delete this post ?")
|
|
||||||
}).then (b) =>
|
|
||||||
return unless b
|
|
||||||
@blogdb.delete data.id
|
|
||||||
.then (r) =>
|
|
||||||
@bloglist.delete el
|
|
||||||
@bloglist.unselect()
|
|
||||||
@clearEditor()
|
|
||||||
return false
|
|
||||||
|
|
||||||
|
|
||||||
@bindKey "CTRL-S", () =>
|
|
||||||
sel = @tabcontainer.selectedTab
|
|
||||||
return unless sel and sel.container.aid is "blog-container"
|
|
||||||
@saveBlog()
|
|
||||||
@on "resize", () =>
|
|
||||||
@resizeContent()
|
|
||||||
|
|
||||||
@resizeContent()
|
|
||||||
@loadBlogs()
|
|
||||||
# @fetchData 0
|
|
||||||
# USER TAB
|
|
||||||
fetchData: (idx) ->
|
|
||||||
switch idx
|
|
||||||
when "user-container" #user info
|
|
||||||
|
|
||||||
@userdb.get null
|
|
||||||
.then (d) =>
|
|
||||||
@user = d[0]
|
|
||||||
inputs = @select "[input-class='user-input']"
|
|
||||||
($ v).val @user[v.name] for v in inputs
|
|
||||||
.catch (e) => @error __("Cannot fetch user data"), e
|
|
||||||
when "cv-container" # category
|
|
||||||
@refreshCVCat()
|
|
||||||
else
|
|
||||||
@loadBlogs()
|
|
||||||
|
|
||||||
saveUser:() ->
|
|
||||||
inputs = @select "[input-class='user-input']"
|
|
||||||
@user[v.name] = ($ v).val() for v in inputs
|
|
||||||
return @notify __("Full name must be entered") if not @user.fullname or @user.fullname is ""
|
|
||||||
#console.log @user
|
|
||||||
@userdb.save @user
|
|
||||||
.then (r) =>
|
|
||||||
return @notify __("User data updated")
|
|
||||||
.catch (e) => return @error __("Cannot save user data"), e
|
|
||||||
|
|
||||||
|
|
||||||
# PORFOLIO TAB
|
|
||||||
refreshCVCat: () ->
|
|
||||||
@fetchCVCat().then (data) =>
|
|
||||||
@cvlist.data = data
|
|
||||||
@cvlist.expandAll()
|
|
||||||
.catch (e) => @error __("Unable to load categories"), e
|
|
||||||
|
|
||||||
fetchCVCat: () ->
|
|
||||||
new Promise (resolve, reject) =>
|
|
||||||
data =
|
|
||||||
text: "Porfolio",
|
|
||||||
id:"0",
|
|
||||||
nodes: []
|
|
||||||
cnd =
|
|
||||||
order:
|
|
||||||
name: "ASC"
|
|
||||||
@cvcatdb.find cnd
|
|
||||||
.then (d) =>
|
|
||||||
@catListToTree d, data, "0"
|
|
||||||
resolve data
|
|
||||||
.catch (e) -> reject __e e
|
|
||||||
#it = (@cvlist.find "pid", "2")[0]
|
|
||||||
#@cvlist.set "selectedItem", it
|
|
||||||
|
|
||||||
catListToTree: (table, data, id) ->
|
|
||||||
result = (v for v in table when v.pid is id)
|
|
||||||
return data.nodes = null if result.length is 0
|
|
||||||
for v in result
|
|
||||||
v.nodes = []
|
|
||||||
v.text = v.name
|
|
||||||
@catListToTree table, v, v.id
|
|
||||||
#v.nodes = null if v.nodes.length is 0
|
|
||||||
data.nodes.push v
|
|
||||||
|
|
||||||
deleteCVCat: (cat) ->
|
|
||||||
me = @
|
|
||||||
ids = []
|
|
||||||
func = (c) ->
|
|
||||||
ids.push c.id
|
|
||||||
func(v) for v in c.nodes if c.nodes
|
|
||||||
func(cat)
|
|
||||||
|
|
||||||
cond = ({ "=": { cid: v } } for v in ids)
|
|
||||||
# delete all content
|
|
||||||
@cvsecdb.delete({ "or": cond }).then (r) =>
|
|
||||||
cond = ({ "=": { id: v } } for v in ids)
|
|
||||||
@cvcatdb.delete({ "or": cond }).then (re) =>
|
|
||||||
@refreshCVCat()
|
|
||||||
@seclist.data=[]
|
|
||||||
.catch (e) =>
|
|
||||||
@error __("Cannot delete the category: {0} [{1}]", cat.name, e.toString()), e
|
|
||||||
.catch (e) =>
|
|
||||||
@error __("Cannot delete all content of: {0} [{1}]", cat.name, e.toString()), e
|
|
||||||
|
|
||||||
CVSectionByCID: (cid) ->
|
|
||||||
cond =
|
|
||||||
exp:
|
|
||||||
"=":
|
|
||||||
cid: cid
|
|
||||||
order:
|
|
||||||
start: "DESC"
|
|
||||||
@cvsecdb.find(cond).then (d) =>
|
|
||||||
items = []
|
|
||||||
(@find "cv-sec-status").text = __("Found {0} sections", d.length)
|
|
||||||
for v in d
|
|
||||||
v.closable = true
|
|
||||||
v.tag = "afx-blogger-cvsection-item"
|
|
||||||
v.start = Number(v.start)
|
|
||||||
v.end = Number(v.end)
|
|
||||||
v.start = undefined if v.start < 1000
|
|
||||||
v.end = undefined if v.end < 1000
|
|
||||||
items.push v
|
|
||||||
@seclist.data = items
|
|
||||||
.catch (e) => @error e.toString(), e
|
|
||||||
|
|
||||||
# blog
|
|
||||||
saveBlog: () ->
|
|
||||||
sel = undefined
|
|
||||||
selel = @bloglist.selectedItem
|
|
||||||
sel = selel.data if selel
|
|
||||||
tags = @inputtags.value
|
|
||||||
content = @editor.value()
|
|
||||||
title = (new RegExp "^#+(.*)\n", "g").exec content
|
|
||||||
return @notify __("Please insert a title in the text: beginning with heading") unless title and title.length is 2
|
|
||||||
return @notify __("Please enter tags") if tags is ""
|
|
||||||
d = new Date()
|
|
||||||
data =
|
|
||||||
content: content
|
|
||||||
title: title[1].trim()
|
|
||||||
tags: tags
|
|
||||||
ctime: if sel then sel.ctime else d.timestamp()
|
|
||||||
ctimestr: if sel then sel.ctimestr else d.toString()
|
|
||||||
utime: d.timestamp()
|
|
||||||
utimestr: d.toString()
|
|
||||||
rendered: @process(@editor.options.previewRender(content))
|
|
||||||
publish: if (@find "blog-publish").swon then 1 else 0
|
|
||||||
data.id = sel.id if sel
|
|
||||||
#save the data
|
|
||||||
@blogdb.save data
|
|
||||||
.then (r) =>
|
|
||||||
@loadBlogs()
|
|
||||||
.catch (e) => @error __("Cannot save blog: {0}", e.toString()), e
|
|
||||||
|
|
||||||
process: (text) ->
|
|
||||||
# find video tag and rendered it
|
|
||||||
embed = (id) ->
|
|
||||||
return """
|
|
||||||
<iframe
|
|
||||||
class = "embeded-video"
|
|
||||||
width="560" height="315"
|
|
||||||
src="https://www.youtube.com/embed/#{id}"
|
|
||||||
frameborder="0" allow="encrypted-media" allowfullscreen
|
|
||||||
></iframe>
|
|
||||||
"""
|
|
||||||
re = /\[\[youtube:([^\]]*)\]\]/g
|
|
||||||
replace = []
|
|
||||||
while (found = re.exec text) isnt null
|
|
||||||
replace.push found
|
|
||||||
return text unless replace.length > 0
|
|
||||||
ret = ""
|
|
||||||
begin = 0
|
|
||||||
for it in 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
|
|
||||||
|
|
||||||
clearEditor:() ->
|
|
||||||
@.editor.value ""
|
|
||||||
@.inputtags.value = ""
|
|
||||||
(@.find "blog-publish").swon = false
|
|
||||||
# load blog
|
|
||||||
loadBlogs: () ->
|
|
||||||
selidx = -1
|
|
||||||
el = @bloglist.selectedItem
|
|
||||||
selidx = $(el).index()
|
|
||||||
cond =
|
|
||||||
order:
|
|
||||||
ctime: "DESC"
|
|
||||||
fields: [
|
|
||||||
"id",
|
|
||||||
"title",
|
|
||||||
"ctimestr",
|
|
||||||
"ctime",
|
|
||||||
"utime",
|
|
||||||
"utimestr"
|
|
||||||
]
|
|
||||||
@blogdb.find cond
|
|
||||||
.then (r) =>
|
|
||||||
v.tag = "afx-blogger-post-item" for v in r
|
|
||||||
@bloglist.data = r
|
|
||||||
if selidx isnt -1
|
|
||||||
@bloglist.selected = selidx
|
|
||||||
else
|
|
||||||
@clearEditor()
|
|
||||||
@bloglist.selected = -1
|
|
||||||
.catch (e) => @error __("No post found: {0}", e.toString()), e
|
|
||||||
|
|
||||||
resizeContent: () ->
|
|
||||||
container = @find "editor-container"
|
|
||||||
children = ($ ".EasyMDEContainer", container).children()
|
|
||||||
titlebar = (($ @scheme).find ".afx-window-top")[0]
|
|
||||||
toolbar = children[0]
|
|
||||||
statusbar = children[3]
|
|
||||||
cheight = ($ @scheme).height() - ($ titlebar).height() - ($ toolbar).height() - ($ statusbar).height() - 90
|
|
||||||
($ 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",
|
|
||||||
]
|
|
||||||
this.OS.register "Blogger", Blogger
|
|
893
Blogger/main.ts
Normal file
893
Blogger/main.ts
Normal file
@ -0,0 +1,893 @@
|
|||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
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 = 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",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@
|
|||||||
"author": "Xuan Sang LE",
|
"author": "Xuan Sang LE",
|
||||||
"email": "xsang.le@gmail.com"
|
"email": "xsang.le@gmail.com"
|
||||||
},
|
},
|
||||||
"version": "0.2.9-a",
|
"version": "0.2.10-a",
|
||||||
"category": "Internet",
|
"category": "Internet",
|
||||||
"iconclass": "fa fa-book",
|
"iconclass": "fa fa-book",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
@ -36,7 +36,6 @@
|
|||||||
"No email selected": "No email selected",
|
"No email selected": "No email selected",
|
||||||
"Unable to send mail to: {0}": "Unable to send mail to: {0}",
|
"Unable to send mail to: {0}": "Unable to send mail to: {0}",
|
||||||
"Error sending mail: {0}": "Error sending mail: {0}",
|
"Error sending mail: {0}": "Error sending mail: {0}",
|
||||||
"Cannot fetch subscribers data: {0}": "Cannot fetch subscribers data: {0}",
|
|
||||||
"Open/create new database": "Open/create new database",
|
"Open/create new database": "Open/create new database",
|
||||||
"Unable to init database file: {0}": "Unable to init database file: {0}",
|
"Unable to init database file: {0}": "Unable to init database file: {0}",
|
||||||
"Select image file": "Select image file",
|
"Select image file": "Select image file",
|
||||||
@ -65,28 +64,20 @@
|
|||||||
"No post selected": "No post selected",
|
"No post selected": "No post selected",
|
||||||
"Emails sent": "Emails sent",
|
"Emails sent": "Emails sent",
|
||||||
"Error sending mails: {0}": "Error sending mails: {0}",
|
"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",
|
"Cannot fetch the entry content": "Cannot fetch the entry content",
|
||||||
"Delete a post": "Delete a post",
|
"Delete a post": "Delete a post",
|
||||||
"Do you really want to delete this post ?": "Do you really want to delete this 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",
|
"Cannot fetch user data": "Cannot fetch user data",
|
||||||
"Full name must be entered": "Full name must be entered",
|
"Full name must be entered": "Full name must be entered",
|
||||||
"User data updated": "User data updated",
|
"User data updated": "User data updated",
|
||||||
"Cannot save user data": "Cannot save user data",
|
"Cannot save user data: {0}": "Cannot save user data: {0}",
|
||||||
"Unable to load categories": "Unable to load categories",
|
"Unable to load categories": "Unable to load categories",
|
||||||
"Found {0} sections": "Found {0} sections",
|
"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 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",
|
"Please enter tags": "Please enter tags",
|
||||||
"Cannot save blog: {0}": "Cannot save blog: {0}",
|
"Cannot save blog: {0}": "Cannot save blog: {0}",
|
||||||
"Cannot add new category": "Cannot add new category",
|
"No more record to load": "No more record to load",
|
||||||
"Unable to fetch categories": "Unable to fetch categories",
|
|
||||||
"Cannot Edit category": "Cannot Edit category",
|
|
||||||
"Cannot save section: {0}": "Cannot save section: {0}",
|
|
||||||
"Cannot move section": "Cannot move section",
|
|
||||||
"Cannot delete the category: {0} [{1}]": "Cannot delete the category: {0} [{1}]",
|
|
||||||
"Cannot delete all content of: {0} [{1}]": "Cannot delete all content of: {0} [{1}]",
|
|
||||||
"No post found: {0}": "No post found: {0}",
|
|
||||||
"Created: {0}": "Created: {0}",
|
|
||||||
"Updated: {0}": "Updated: {0}",
|
|
||||||
"Full name": "Full name",
|
"Full name": "Full name",
|
||||||
"Address": "Address",
|
"Address": "Address",
|
||||||
"Phone": "Phone",
|
"Phone": "Phone",
|
||||||
@ -95,7 +86,10 @@
|
|||||||
"Photo": "Photo",
|
"Photo": "Photo",
|
||||||
"Short biblio": "Short biblio",
|
"Short biblio": "Short biblio",
|
||||||
"Categories": "Categories",
|
"Categories": "Categories",
|
||||||
"Tags": "Tags"
|
"Load more": "Load more",
|
||||||
|
"Tags": "Tags",
|
||||||
|
"Created: {0}": "Created: {0}",
|
||||||
|
"Updated: {0}": "Updated: {0}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
<afx-app-window data-id = "blogger-win" apptitle="Blogger" width="650" height="500">
|
<afx-app-window data-id = "blogger-win" apptitle="Blogger" width="650" height="500">
|
||||||
<afx-hbox >
|
<afx-hbox padding="5">
|
||||||
<afx-tab-container data-id = "tabcontainer" dir = "row" tabbarwidth= "40">
|
<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-hbox data-id="user-container" data-height="100%" iconclass="fa fa-user-circle">
|
||||||
@ -65,7 +65,10 @@
|
|||||||
|
|
||||||
|
|
||||||
<afx-hbox data-id = "blog-container" data-height="100%" iconclass="fa fa-book">
|
<afx-hbox data-id = "blog-container" data-height="100%" iconclass="fa fa-book">
|
||||||
<afx-list-view data-id = "blog-list" min-width="100" data-width="200"></afx-list-view>
|
<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-resizer data-width = "3"></afx-resizer>
|
||||||
<afx-vbox>
|
<afx-vbox>
|
||||||
<div data-id = "editor-container">
|
<div data-id = "editor-container">
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
<afx-app-window data-id = "blogger-send-mail-win" apptitle="Send mail" width="500" height="400" resizable = "false">
|
|
||||||
<afx-hbox>
|
|
||||||
<afx-menu data-width="150" data-id="email-list"></afx-menu>
|
|
||||||
<afx-resizer data-width="3"></afx-resizer>
|
|
||||||
<div data-width="5"></div>
|
|
||||||
<afx-vbox >
|
|
||||||
<div data-height="5"></div>
|
|
||||||
<afx-label data-height="20" text = "__(Title)"></afx-label>
|
|
||||||
<input type = "text" data-height="20" name="title" data-id = "mail-title"></input>
|
|
||||||
<afx-label data-height = "20" text = "Content" ></afx-label>
|
|
||||||
<textarea name="content" data-id = "contentarea" ></textarea>
|
|
||||||
<div data-height="5"></div>
|
|
||||||
<afx-hbox data-height = "30">
|
|
||||||
<div></div>
|
|
||||||
<afx-button iconclass = "fa fa-paper-plane" data-id = "bt-sendmail" data-width="60" text = "__(Send)"></afx-button>
|
|
||||||
</afx-hbox>
|
|
||||||
</afx-vbox>
|
|
||||||
<div data-width="5"></div>
|
|
||||||
</afx-hbox>
|
|
||||||
</afx-app-window>
|
|
@ -1,63 +0,0 @@
|
|||||||
Ant = this
|
|
||||||
|
|
||||||
class CVSectionListItemTag extends this.OS.GUI.tag.ListViewItemTag
|
|
||||||
constructor: () ->
|
|
||||||
super()
|
|
||||||
|
|
||||||
ondatachange: () ->
|
|
||||||
return unless @data
|
|
||||||
v = @data
|
|
||||||
nativel = ["content", "start", "end" ]
|
|
||||||
@closable = v.closable
|
|
||||||
for k, el of @refs
|
|
||||||
if v[k] and v[k] isnt ""
|
|
||||||
if nativel.includes k
|
|
||||||
$(el).text v[k]
|
|
||||||
else
|
|
||||||
el.text = v[k]
|
|
||||||
|
|
||||||
reload: () ->
|
|
||||||
|
|
||||||
init:() ->
|
|
||||||
|
|
||||||
|
|
||||||
itemlayout: () ->
|
|
||||||
{ 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" }
|
|
||||||
] }
|
|
||||||
|
|
||||||
this.OS.GUI.tag.define "afx-blogger-cvsection-item", CVSectionListItemTag
|
|
||||||
|
|
||||||
|
|
||||||
class BlogPostListItemTag extends this.OS.GUI.tag.ListViewItemTag
|
|
||||||
constructor: () ->
|
|
||||||
super()
|
|
||||||
|
|
||||||
ondatachange: (v) ->
|
|
||||||
return unless @data
|
|
||||||
v = @data
|
|
||||||
v.closable = true
|
|
||||||
@closable = v.closable
|
|
||||||
@refs.title.text = v.title
|
|
||||||
@refs.ctimestr.text = __("Created: {0}", v.ctimestr)
|
|
||||||
@refs.utimestr.text = __("Updated: {0}", v.utimestr)
|
|
||||||
|
|
||||||
reload: () ->
|
|
||||||
|
|
||||||
init:() ->
|
|
||||||
|
|
||||||
itemlayout: () ->
|
|
||||||
{ 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" },
|
|
||||||
] }
|
|
||||||
|
|
||||||
this.OS.GUI.tag.define "afx-blogger-post-item", BlogPostListItemTag
|
|
104
Blogger/tags.ts
Normal file
104
Blogger/tags.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
|
||||||
|
interface Array<T> {
|
||||||
|
/**
|
||||||
|
* Check if the array includes an element
|
||||||
|
*
|
||||||
|
* @param {T} element to check
|
||||||
|
* @returns {boolean}
|
||||||
|
* @memberof Array
|
||||||
|
*/
|
||||||
|
includes(el: T): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace OS {
|
||||||
|
export namespace application {
|
||||||
|
export namespace blogger {
|
||||||
|
|
||||||
|
class CVSectionListItemTag extends OS.GUI.tag.ListViewItemTag {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
ondatachange() {
|
||||||
|
if (!this.data) { return; }
|
||||||
|
const v = this.data;
|
||||||
|
const nativel = ["content", "start", "end"];
|
||||||
|
this.closable = v.closable;
|
||||||
|
return (() => {
|
||||||
|
const result = [];
|
||||||
|
for (let k in this.refs) {
|
||||||
|
const el = this.refs[k];
|
||||||
|
if (v[k] && (v[k] !== "")) {
|
||||||
|
if (nativel.includes(k)) {
|
||||||
|
result.push($(el).text(v[k]));
|
||||||
|
} else {
|
||||||
|
result.push((el as OS.GUI.tag.LabelTag).text = v[k]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
reload() { }
|
||||||
|
|
||||||
|
init() { }
|
||||||
|
|
||||||
|
|
||||||
|
itemlayout() {
|
||||||
|
return {
|
||||||
|
el: "div", children: [
|
||||||
|
{ el: "afx-label", ref: "title", class: "afx-cv-sec-title" },
|
||||||
|
{ el: "afx-label", ref: "subtitle", class: "afx-cv-sec-subtitle" },
|
||||||
|
{ el: "p", ref: "content", class: "afx-cv-sec-content" },
|
||||||
|
{
|
||||||
|
el: "p", class: "afx-cv-sec-period", children: [
|
||||||
|
{ el: "i", ref: "start" },
|
||||||
|
{ el: "i", ref: "end", class: "period-end" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ el: "afx-label", ref: "location", class: "afx-cv-sec-loc" }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OS.GUI.tag.define("afx-blogger-cvsection-item", CVSectionListItemTag);
|
||||||
|
|
||||||
|
|
||||||
|
class BlogPostListItemTag extends OS.GUI.tag.ListViewItemTag {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
ondatachange() {
|
||||||
|
if (!this.data) { return; }
|
||||||
|
const v = this.data;
|
||||||
|
v.closable = true;
|
||||||
|
this.closable = v.closable;
|
||||||
|
(this.refs.title as OS.GUI.tag.LabelTag).text = v.title;
|
||||||
|
(this.refs.ctimestr as OS.GUI.tag.LabelTag).text = __("Created: {0}", v.ctimestr);
|
||||||
|
(this.refs.utimestr as OS.GUI.tag.LabelTag).text = __("Updated: {0}", v.utimestr);
|
||||||
|
}
|
||||||
|
|
||||||
|
reload() { }
|
||||||
|
|
||||||
|
init() { }
|
||||||
|
|
||||||
|
itemlayout() {
|
||||||
|
return {
|
||||||
|
el: "div", children: [
|
||||||
|
{ el: "afx-label", ref: "title", class: "afx-blogpost-title" },
|
||||||
|
{ el: "afx-label", ref: "ctimestr", class: "blog-dates" },
|
||||||
|
{ el: "afx-label", ref: "utimestr", class: "blog-dates" },
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OS.GUI.tag.define("afx-blogger-post-item", BlogPostListItemTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -406,15 +406,27 @@ namespace OS {
|
|||||||
filter.table_name = this._table_name;
|
filter.table_name = this._table_name;
|
||||||
if(this._id)
|
if(this._id)
|
||||||
{
|
{
|
||||||
filter.where = { id: this._id};
|
filter.where = {};
|
||||||
|
filter.where[this.info.schema.pk] = this._id;
|
||||||
}
|
}
|
||||||
let data = await this._handle.select(filter);
|
let data: GenericObject<any>[] = await this._handle.select(filter);
|
||||||
if(this._id)
|
if(this._id)
|
||||||
{
|
{
|
||||||
this.cache = data[0];
|
this.cache = data[0];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
for(let row of data)
|
||||||
|
{
|
||||||
|
if(row[this.info.schema.pk])
|
||||||
|
{
|
||||||
|
Object.defineProperty(row, '$vfs', {
|
||||||
|
value: `${this.path}@${row[this.info.schema.pk]}`.asFileHandle(),
|
||||||
|
enumerable: false,
|
||||||
|
configurable: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
this.cache = data;
|
this.cache = data;
|
||||||
}
|
}
|
||||||
resolve(this.cache)
|
resolve(this.cache)
|
||||||
@ -444,7 +456,7 @@ namespace OS {
|
|||||||
await this.onready();
|
await this.onready();
|
||||||
if(this._id && this._table_name)
|
if(this._id && this._table_name)
|
||||||
{
|
{
|
||||||
this.cache.id = this._id;
|
this.cache[this.info.schema.pk] = this._id;
|
||||||
const ret = await this._handle.update(this._table_name, this.cache, this.info.schema.pk);
|
const ret = await this._handle.update(this._table_name, this.cache, this.info.schema.pk);
|
||||||
resolve({result:ret, error: false});
|
resolve({result:ret, error: false});
|
||||||
return
|
return
|
||||||
@ -504,7 +516,8 @@ namespace OS {
|
|||||||
filter.table_name = this._table_name;
|
filter.table_name = this._table_name;
|
||||||
if(this._id)
|
if(this._id)
|
||||||
{
|
{
|
||||||
filter.where = { id: this._id};
|
filter.where = {};
|
||||||
|
filter.where[this.info.schema.pk] = this._id;
|
||||||
}
|
}
|
||||||
let ret = await this._handle.delete_records(filter);
|
let ret = await this._handle.delete_records(filter);
|
||||||
resolve({result: ret, error: false});
|
resolve({result: ret, error: false});
|
||||||
|
@ -1 +1 @@
|
|||||||
var OS;!function(e){let t;!function(e){class t{constructor(e){t.REGISTY||(t.REGISTY={}),this.db_file=e.asFileHandle(),t.REGISTY[this.db_file.path]?this.db_file=t.REGISTY[this.db_file.path]:t.REGISTY[this.db_file.path]=this.db_file}pwd(){return"pkg://SQLiteDB/".asFileHandle()}fileinfo(){return this.db_file.info}init(){return new Promise(async(e,t)=>{try{if(this.db_file.ready)return e(!0);let t={action:"init",args:{db_source:this.db_file.path}},a=await this.call(t);if(a=await this.db_file.onready(),!this.db_file||!this.db_file.ready||"file"!==this.db_file.info.type)throw __("DB file meta-data is invalid: {0}",this.db_file.path).__();e(!0)}catch(e){t(__e(e))}})}call(t){return new Promise(async(a,i)=>{t.args.db_source=this.db_file.path;let s={path:this.pwd().path+"/api/api.lua",parameters:t},r=await e.apigateway(s,!1);r.error?i(e.throwe(__("SQLiteDB server call error: {0}",r.error))):a(r.result)})}request(e){return new Promise(async(t,a)=>{try{this.db_file.ready||await this.init(),t(await this.call(e))}catch(e){a(__e(e))}})}select(e){let t={action:"select",args:{filter:e}};return this.request(t)}delete_records(e){let t={action:"delete_records",args:{filter:e}};return this.request(t)}drop_table(e){let t={action:"drop_table",args:{table_name:e}};return this.request(t)}list_tables(){return this.request({action:"list_table",args:{}})}create_table(e,t){let a={action:"create_table",args:{table_name:e,scheme:t}};return this.request(a)}get_table_scheme(e){let t={action:"table_scheme",args:{table_name:e}};return this.request(t)}insert(e,t,a){let i={action:"insert",args:{table_name:e,record:t,pk:a}};return this.request(i)}update(e,t,a){let i={action:"update",args:{table_name:e,record:t,pk:a}};return this.request(i)}last_insert_id(){return this.request({action:"last_insert_id",args:{}})}}let a;!function(e){class a extends e.BaseFileHandle{setPath(e){let a=e.split("@");if(super.setPath(a[0]),a.length>3)throw new Error(__("Invalid file path").__());this.path=e,this._table_name=a[1],this._id=a[2]?parseInt(a[2]):void 0,this._handle=new t("home://"+this.genealogy.join("/"))}meta(){return new Promise(async(e,t)=>{try{await this._handle.init();let t={result:{file:this._handle.fileinfo(),schema:void 0},error:!1};if(this._table_name){const e=await this._handle.get_table_scheme(this._table_name);if(0==e.length)t.result.schema=void 0;else{t.result.schema={fields:[],types:{},pk:void 0},t.result.schema.fields=e.map(e=>e.name);for(let a of e)t.result.schema.types[a.name]=a.type,a.pk&&(t.result.schema.pk=a.name)}}return e(t)}catch(e){return t(__e(e))}})}_rd(e){return new Promise(async(t,a)=>{try{if(this._table_name&&!this.info.schema)throw new Error(__("Table `{0}` does not exists in database: {1}",this._table_name,this.path).__());if(this._table_name){let a=e;a&&!this._id||(a={}),a.table_name=this._table_name,this._id&&(a.where={id:this._id});let i=await this._handle.select(a);this._id?this.cache=i[0]:this.cache=i,t(this.cache)}else{let e=await this._handle.list_tables();const a={};for(let t of e)a[t.name]=`${this.path}@${t.name}`.asFileHandle();this.cache=a,t(a)}}catch(e){return a(__e(e))}})}_wr(e){return new Promise(async(t,a)=>{try{if(!this.cache)throw new Error(__("No data to submit to remote database, please check the `cache` field").__());if(await this.onready(),this._id&&this._table_name)return this.cache.id=this._id,void t({result:await this._handle.update(this._table_name,this.cache,this.info.schema.pk),error:!1});if(this._table_name)return void t({result:await this._handle.insert(this._table_name,this.cache,this.info.schema.pk),error:!1});t({result:await this._handle.create_table(e,this.cache),error:!1})}catch(e){return a(__e(e))}})}_rm(e){return new Promise(async(t,a)=>{try{if(this._table_name&&!this.info.schema)throw new Error(__("Table `{0}` does not exists in database: {1}",this._table_name,this.path).__());if(this._table_name){let a=e;a&&!this._id||(a={}),a.table_name=this._table_name,this._id&&(a.where={id:this._id}),t({result:await this._handle.delete_records(a),error:!1})}else{let a=e;if(!a)throw new Error(__("No table specified for dropping").__());t({result:await this._handle.drop_table(a),error:!1})}}catch(e){return a(__e(e))}})}}e.register("^sqlite$",a)}(a=e.VFS||(e.VFS={}))}(t=e.API||(e.API={}))}(OS||(OS={}));
|
var OS;!function(e){let t;!function(e){class t{constructor(e){t.REGISTY||(t.REGISTY={}),this.db_file=e.asFileHandle(),t.REGISTY[this.db_file.path]?this.db_file=t.REGISTY[this.db_file.path]:t.REGISTY[this.db_file.path]=this.db_file}pwd(){return"pkg://SQLiteDB/".asFileHandle()}fileinfo(){return this.db_file.info}init(){return new Promise(async(e,t)=>{try{if(this.db_file.ready)return e(!0);let t={action:"init",args:{db_source:this.db_file.path}},a=await this.call(t);if(a=await this.db_file.onready(),!this.db_file||!this.db_file.ready||"file"!==this.db_file.info.type)throw __("DB file meta-data is invalid: {0}",this.db_file.path).__();e(!0)}catch(e){t(__e(e))}})}call(t){return new Promise(async(a,i)=>{t.args.db_source=this.db_file.path;let s={path:this.pwd().path+"/api/api.lua",parameters:t},r=await e.apigateway(s,!1);r.error?i(e.throwe(__("SQLiteDB server call error: {0}",r.error))):a(r.result)})}request(e){return new Promise(async(t,a)=>{try{this.db_file.ready||await this.init(),t(await this.call(e))}catch(e){a(__e(e))}})}select(e){let t={action:"select",args:{filter:e}};return this.request(t)}delete_records(e){let t={action:"delete_records",args:{filter:e}};return this.request(t)}drop_table(e){let t={action:"drop_table",args:{table_name:e}};return this.request(t)}list_tables(){return this.request({action:"list_table",args:{}})}create_table(e,t){let a={action:"create_table",args:{table_name:e,scheme:t}};return this.request(a)}get_table_scheme(e){let t={action:"table_scheme",args:{table_name:e}};return this.request(t)}insert(e,t,a){let i={action:"insert",args:{table_name:e,record:t,pk:a}};return this.request(i)}update(e,t,a){let i={action:"update",args:{table_name:e,record:t,pk:a}};return this.request(i)}last_insert_id(){return this.request({action:"last_insert_id",args:{}})}}let a;!function(e){class a extends e.BaseFileHandle{setPath(e){let a=e.split("@");if(super.setPath(a[0]),a.length>3)throw new Error(__("Invalid file path").__());this.path=e,this._table_name=a[1],this._id=a[2]?parseInt(a[2]):void 0,this._handle=new t("home://"+this.genealogy.join("/"))}meta(){return new Promise(async(e,t)=>{try{await this._handle.init();let t={result:{file:this._handle.fileinfo(),schema:void 0},error:!1};if(this._table_name){const e=await this._handle.get_table_scheme(this._table_name);if(0==e.length)t.result.schema=void 0;else{t.result.schema={fields:[],types:{},pk:void 0},t.result.schema.fields=e.map(e=>e.name);for(let a of e)t.result.schema.types[a.name]=a.type,a.pk&&(t.result.schema.pk=a.name)}}return e(t)}catch(e){return t(__e(e))}})}_rd(e){return new Promise(async(t,a)=>{try{if(this._table_name&&!this.info.schema)throw new Error(__("Table `{0}` does not exists in database: {1}",this._table_name,this.path).__());if(this._table_name){let a=e;a&&!this._id||(a={}),a.table_name=this._table_name,this._id&&(a.where={},a.where[this.info.schema.pk]=this._id);let i=await this._handle.select(a);if(this._id)this.cache=i[0];else{for(let e of i)e[this.info.schema.pk]&&Object.defineProperty(e,"$vfs",{value:`${this.path}@${e[this.info.schema.pk]}`.asFileHandle(),enumerable:!1,configurable:!1});this.cache=i}t(this.cache)}else{let e=await this._handle.list_tables();const a={};for(let t of e)a[t.name]=`${this.path}@${t.name}`.asFileHandle();this.cache=a,t(a)}}catch(e){return a(__e(e))}})}_wr(e){return new Promise(async(t,a)=>{try{if(!this.cache)throw new Error(__("No data to submit to remote database, please check the `cache` field").__());if(await this.onready(),this._id&&this._table_name)return this.cache[this.info.schema.pk]=this._id,void t({result:await this._handle.update(this._table_name,this.cache,this.info.schema.pk),error:!1});if(this._table_name)return void t({result:await this._handle.insert(this._table_name,this.cache,this.info.schema.pk),error:!1});t({result:await this._handle.create_table(e,this.cache),error:!1})}catch(e){return a(__e(e))}})}_rm(e){return new Promise(async(t,a)=>{try{if(this._table_name&&!this.info.schema)throw new Error(__("Table `{0}` does not exists in database: {1}",this._table_name,this.path).__());if(this._table_name){let a=e;a&&!this._id||(a={}),a.table_name=this._table_name,this._id&&(a.where={},a.where[this.info.schema.pk]=this._id),t({result:await this._handle.delete_records(a),error:!1})}else{let a=e;if(!a)throw new Error(__("No table specified for dropping").__());t({result:await this._handle.drop_table(a),error:!1})}}catch(e){return a(__e(e))}})}}e.register("^sqlite$",a)}(a=e.VFS||(e.VFS={}))}(t=e.API||(e.API={}))}(OS||(OS={}));
|
File diff suppressed because one or more lines are too long
@ -16,6 +16,7 @@
|
|||||||
<afx-grid-view data-id="tb-browser"></afx-grid-view>
|
<afx-grid-view data-id="tb-browser"></afx-grid-view>
|
||||||
<div data-height="5"></div>
|
<div data-height="5"></div>
|
||||||
<afx-hbox data-height="35">
|
<afx-hbox data-height="35">
|
||||||
|
<afx-button iconclass_end="bi bi-arrow-clockwise" data-id="bt-refresh" data-width="content"></afx-button>
|
||||||
<afx-button iconclass_end="bi bi-chevron-double-right" data-id="bt-load-next" data-width="content"></afx-button>
|
<afx-button iconclass_end="bi bi-chevron-double-right" data-id="bt-load-next" data-width="content"></afx-button>
|
||||||
<div></div>
|
<div></div>
|
||||||
<afx-button iconclass="bi bi-plus-lg" data-id="btn-add-record" data-width="content"></afx-button>
|
<afx-button iconclass="bi bi-plus-lg" data-id="btn-add-record" data-width="content"></afx-button>
|
||||||
|
Binary file not shown.
@ -235,6 +235,23 @@ namespace OS {
|
|||||||
this.error(__("Error reading table: {0}", e.toString()),e);
|
this.error(__("Error reading table: {0}", e.toString()),e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(this.find("bt-refresh") as GUI.tag.ButtonTag).onbtclick = async (e) => {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.last_max_id = 0;
|
||||||
|
const handle: API.VFS.BaseFileHandle = this.tbl_list.selectedItem.data.handle;
|
||||||
|
const records = await handle.read({fields:["COUNT(*)"]});
|
||||||
|
this.n_records = records[0]["COUNT(*)"];
|
||||||
|
this.grid_table.rows = [];
|
||||||
|
await this.load_table();
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
this.error(__("Error reload table: {0}", e.toString()),e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.tbl_list.onlistselect = async (_) => {
|
this.tbl_list.onlistselect = async (_) => {
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -406,7 +423,7 @@ namespace OS {
|
|||||||
for(let v in headers)
|
for(let v in headers)
|
||||||
{
|
{
|
||||||
let text:string = e[headers[v].text];
|
let text:string = e[headers[v].text];
|
||||||
if(text.length > 100)
|
if(text && text.length > 100)
|
||||||
{
|
{
|
||||||
text = text.substring(0,100);
|
text = text.substring(0,100);
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
<afx-grid-view data-id="tb-browser"></afx-grid-view>
|
<afx-grid-view data-id="tb-browser"></afx-grid-view>
|
||||||
<div data-height="5"></div>
|
<div data-height="5"></div>
|
||||||
<afx-hbox data-height="35">
|
<afx-hbox data-height="35">
|
||||||
|
<afx-button iconclass_end="bi bi-arrow-clockwise" data-id="bt-refresh" data-width="content"></afx-button>
|
||||||
<afx-button iconclass_end="bi bi-chevron-double-right" data-id="bt-load-next" data-width="content"></afx-button>
|
<afx-button iconclass_end="bi bi-chevron-double-right" data-id="bt-load-next" data-width="content"></afx-button>
|
||||||
<div></div>
|
<div></div>
|
||||||
<afx-button iconclass="bi bi-plus-lg" data-id="btn-add-record" data-width="content"></afx-button>
|
<afx-button iconclass="bi bi-plus-lg" data-id="btn-add-record" data-width="content"></afx-button>
|
||||||
|
@ -95,8 +95,8 @@
|
|||||||
"description": "https://raw.githubusercontent.com/lxsang/antosdk-apps/2.0.x/Blogger/README.md",
|
"description": "https://raw.githubusercontent.com/lxsang/antosdk-apps/2.0.x/Blogger/README.md",
|
||||||
"category": "Internet",
|
"category": "Internet",
|
||||||
"author": "Xuan Sang LE",
|
"author": "Xuan Sang LE",
|
||||||
"version": "0.2.9-a",
|
"version": "0.2.10-a",
|
||||||
"dependencies": ["SimpleMDE@2.18.0-r","Katex@0.11.1-r"],"mimes":["none"],
|
"dependencies": ["SimpleMDE@2.18.0-r","Katex@0.11.1-r","SQLiteDB@0.1.0-a"],"mimes":["none"],
|
||||||
"download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/2.0.x/Blogger/build/release/Blogger.zip"
|
"download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/2.0.x/Blogger/build/release/Blogger.zip"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -409,6 +409,16 @@
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/2.0.x/SimpleMDE/build/release/SimpleMDE.zip"
|
"download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/2.0.x/SimpleMDE/build/release/SimpleMDE.zip"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pkgname": "SQLiteDB",
|
||||||
|
"name": "SQLite3 Browser",
|
||||||
|
"description": "https://raw.githubusercontent.com/lxsang/antosdk-apps/2.0.x/SQLiteDB/README.md",
|
||||||
|
"category": "Library",
|
||||||
|
"author": "Dany LE",
|
||||||
|
"version": "0.1.0-a",
|
||||||
|
"dependencies": [],
|
||||||
|
"download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/2.0.x/SQLiteDB/build/release/SQLiteDB.zip"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pkgname": "SystemControl",
|
"pkgname": "SystemControl",
|
||||||
"name": "System monitoring",
|
"name": "System monitoring",
|
||||||
|
Loading…
Reference in New Issue
Block a user