From 5dec0a2b56303eed83e14c230f935eb04ccefba4 Mon Sep 17 00:00:00 2001 From: DanyLE Date: Thu, 23 Feb 2023 16:59:33 +0100 Subject: [PATCH] Blogger: migrate code to typescript,use SQLiteDB as database access API --- Blogger/README.md | 1 + Blogger/build/debug/README.md | 1 + Blogger/build/debug/main.js | 1181 +-------------------------- Blogger/build/debug/package.json | 22 +- Blogger/build/debug/scheme.html | 7 +- Blogger/build/release/Blogger.zip | Bin 9376 -> 10239 bytes Blogger/cvsection.html | 30 - Blogger/dialogs.coffee | 189 ----- Blogger/dialogs.ts | 278 +++++++ Blogger/main.coffee | 506 ------------ Blogger/main.ts | 893 ++++++++++++++++++++ Blogger/package.json | 22 +- Blogger/scheme.html | 7 +- Blogger/sendmail.html | 20 - Blogger/tags.coffee | 63 -- Blogger/tags.ts | 104 +++ SQLiteDB/LibSQLite.ts | 21 +- SQLiteDB/build/debug/libsqlite.js | 2 +- SQLiteDB/build/debug/main.js | 2 +- SQLiteDB/build/debug/scheme.html | 1 + SQLiteDB/build/release/SQLiteDB.zip | Bin 7178 -> 7298 bytes SQLiteDB/main.ts | 19 +- SQLiteDB/scheme.html | 1 + packages.json | 14 +- 24 files changed, 1355 insertions(+), 2029 deletions(-) delete mode 100644 Blogger/cvsection.html delete mode 100644 Blogger/dialogs.coffee create mode 100644 Blogger/dialogs.ts delete mode 100644 Blogger/main.coffee create mode 100644 Blogger/main.ts delete mode 100644 Blogger/sendmail.html delete mode 100644 Blogger/tags.coffee create mode 100644 Blogger/tags.ts diff --git a/Blogger/README.md b/Blogger/README.md index 8b57be7..b6f783d 100644 --- a/Blogger/README.md +++ b/Blogger/README.md @@ -6,6 +6,7 @@ Blackend for my blog at https://blog.iohub.dev ## Change logs ### 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 8: Support for antOS 2.0.x * Patch 7: Fix sendmail API security bug diff --git a/Blogger/build/debug/README.md b/Blogger/build/debug/README.md index 8b57be7..b6f783d 100644 --- a/Blogger/build/debug/README.md +++ b/Blogger/build/debug/README.md @@ -6,6 +6,7 @@ Blackend for my blog at https://blog.iohub.dev ## Change logs ### 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 8: Support for antOS 2.0.x * Patch 7: Fix sendmail API security bug diff --git a/Blogger/build/debug/main.js b/Blogger/build/debug/main.js index 719622c..59d4cd8 100644 --- a/Blogger/build/debug/main.js +++ b/Blogger/build/debug/main.js @@ -1,1180 +1 @@ - -// Copyright 2017-2018 Xuan Sang LE -// 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/. -var OS; -(function (OS) { - let application; - (function (application) { - class Blogger extends application.BaseApplication { - constructor(args) { - super("Blogger", args); - this.previewOn = false; - } - 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); - } - } - 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); - } - } - 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); - } - } - 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); - } - } - 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); - } - } - 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); - } - } - 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(); - await this.loadBlogs(); - } - catch (e) { - this.error(__("Unable to init database file: {0}", e.toString()), e); - this.dbhandle = undefined; - } - } - main() { - this.user = {}; - this.cvlist = this.find("cv-list"); - this.cvlist.ontreeselect = (d) => { - if (!d) { - return; - } - const { data } = d.data.item; - return this.CVSectionByCID(Number(data.id)); - }; - this.inputtags = this.find("input-tags"); - this.bloglist = this.find("blog-list"); - this.seclist = this.find("cv-sec-list"); - let el = this.find("photo"); - $(el) - .on("click", async (e) => { - 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"); - this.tabcontainer.ontabselect = (e) => { - return this.fetchData(e.data.container.aid); - }; - this.find("bt-user-save").onbtclick = (e) => { - return this.saveUser(); - }; - this.find("cv-cat-add").onbtclick = async (e) => { - try { - const tree = await this.fetchCVCat(); - const d = await this.openDialog(new application.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); - } - await this.refreshCVCat(); - } - catch (e) { - this.error(__("cv-cat-add: {0}", e.toString()), e); - } - }; - this.find("cv-cat-edit").onbtclick = async (e) => { - 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 application.blogger.BloggerCategoryDialog(), { - title: __("Edit category"), - tree, cat - }); - this.cvcatdb.cache = { - id: cat.id, - publish: cat.publish, - pid: d.p.id, - name: d.value - }; - const r = await this.cvcatdb.write(undefined); - if (r.error) { - throw new Error(r.error); - } - await this.refreshCVCat(); - } - catch (e) { - this.error(__("cv-cat-edit: {0}", e.toString()), e); - } - }; - this.find("cv-cat-del").onbtclick = async (e) => { - 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").onbtclick = async (e) => { - 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 application.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); - } - await this.CVSectionByCID(Number(cat.id)); - } - catch (e) { - this.error(__("cv-sec-add: {0}", e.toString()), e); - } - }; - this.find("cv-sec-move").onbtclick = async (e) => { - try { - const sel = this.seclist.selectedItem; - if (!sel) { - return this.toast(__("Please select a section to move")); - } - const sec = sel.data; - const tree = await this.fetchCVCat(); - const d = await this.openDialog(new application.blogger.BloggerCategoryDialog(), { - title: __("Move to"), - tree, - selonly: true - }); - this.cvsecdb.cache = { - id: sec.id, - cid: d.p.id - }; - const r = await this.cvsecdb.write(undefined); - if (r.error) { - throw new Error(r.error); - } - await this.CVSectionByCID(sec.cid); - this.seclist.unselect(); - } - catch (e) { - this.error(__("cv-sec-move: {0}", e.toString()), e); - } - }; - this.find("cv-sec-edit").onbtclick = async (e) => { - 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 application.blogger.BloggerCVSectionDiaglog(), { - title: __("Modify section entry"), - section: sec - }); - d.cid = Number(sec.cid); - d.start = Number(d.start); - d.end = Number(d.end); - this.cvsecdb.cache = d; - //d.publish = Number sec.publish - const r = await this.cvsecdb.write(undefined); - if (r.error) { - throw new Error(r.error); - } - 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) => { - if (!b) { - return; - } - try { - const r = await this.cvsecdb.remove({ - where: { - id: data.id - } - }); - if (r.error) { - throw new Error(r.error); - } - 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) => { - this.bloglist.unselect(); - return this.clearEditor(); - } - }, - { - name: __("Save"), - className: "fa fa-save", - action: (e) => { - 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) => { - const doc = this.editor.codemirror.getDoc(); - return doc.replaceSelection(`![](${this._api.handle.shared}/${r.result})`); - }).catch((e) => this.error(__("Cannot export file for embedding to text"), e)); - }); - } - }, - { - name: "Youtube", - className: "fa fa-youtube", - action: (e) => { - const doc = this.editor.codemirror.getDoc(); - return doc.replaceSelection("[[youtube:]]"); - } - }, - "|", - { - name: __("Preview"), - className: "fa fa-eye no-disable", - action: (e) => { - 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) => { - 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 application.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 = (e) => { - const el = this.bloglist.selectedItem; - if (!el) { - return; - } - const sel = el.data; - if (!sel) { - return; - } - return this.blogdb.read({ - where: { - id: Number(sel.id) - } - }) - .then((r) => { - this.editor.value(r.content); - this.inputtags.value = r.tags; - return this.find("blog-publish").swon = Number(r.publish) ? true : false; - }).catch((e) => { - return this.error(__("Cannot fetch the entry content"), e); - }); - }; - 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) => { - if (!b) { - return; - } - const r = await this.blogdb.remove({ - where: { - id: Number(data.id) - } - }); - if (r.error) { - throw new Error(r.error); - } - 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.aid !== "blog-container")) { - return; - } - return this.saveBlog(); - }); - this.on("resize", () => { - return this.resizeContent(); - }); - this.resizeContent(); - return this.init_db(); - } - // @fetchData 0 - // USER TAB - fetchData(idx) { - 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.name])); - }).catch((e) => this.error(__("Cannot fetch user data"), e)); - case "cv-container": // category - return this.refreshCVCat(); - default: - return this.loadBlogs(); - } - } - async saveUser() { - try { - const inputs = this.select("[input-class='user-input']"); - for (let v of inputs) { - this.user[v.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); - } - 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 - refreshCVCat() { - return this.fetchCVCat().then((data) => { - this.cvlist.data = data; - return this.cvlist.expandAll(); - }).catch((e) => this.error(__("Unable to load categories"), e)); - } - 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 - catListToTree(table, data, id) { - let v; - const result = table.filter((e) => { - 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); - } - } - deleteCVCat(cat) { - return new Promise(async (resolve, reject) => { - try { - let v; - const ids = []; - var func = function (c) { - 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); - } - r = await this.cvcatdb.remove({ - where: { - $or: { - id: ids - } - } - }); - if (r.error) { - throw new Error(r.error); - } - await this.refreshCVCat(); - this.seclist.data = []; - } - catch (e) { - reject(__e(e)); - } - }); - } - CVSectionByCID(cid) { - 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").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 - 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 = { - 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").swon ? 1 : 0 - }; - if (sel) { - data.id = sel.id; - } - //save the data - this.blogdb.cache = data; - const r = await this.blogdb.write(undefined); - if (r.error) { - throw new Error(r.error); - } - await this.loadBlogs(); - } - catch (e) { - this.error(__("Cannot save blog: {0}", e.toString()), e); - } - } - process(text) { - // find video tag and rendered it - let found; - const embed = (id) => `\ -\ -`; - 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; - } - clearEditor() { - this.editor.value(""); - this.inputtags.value = ""; - return this.find("blog-publish").swon = false; - } - // load blog - loadBlogs() { - return new Promise(async (ok, reject) => { - try { - let selidx = -1; - const el = this.bloglist.selectedItem; - selidx = $(el).index(); - const filter = { - order: ["ctime$desc"], - fields: [ - "id", - "title", - "ctimestr", - "ctime", - "utime", - "utimestr" - ] - }; - const r = await this.blogdb.read(filter); - for (let v of r) { - v.tag = "afx-blogger-post-item"; - } - this.bloglist.data = r; - if (selidx !== -1) { - return this.bloglist.selected = selidx; - } - else { - this.clearEditor(); - return this.bloglist.selected = -1; - } - } - catch (e) { - reject(__e(e)); - } - }); - } - 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"); - } - } - application.Blogger = Blogger; - 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", - ]; - })(application = OS.application || (OS.application = {})); -})(OS || (OS = {})); - -// Copyright 2017-2018 Xuan Sang LE -// 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/. -var OS; -(function (OS) { - let application; - (function (application) { - let blogger; - (function (blogger) { - class BloggerCategoryDialog extends OS.GUI.BasicDialog { - constructor() { - super("BloggerCategoryDialog", BloggerCategoryDialog.scheme); - } - main() { - super.main(); - this.tree = this.find("tree"); - this.txtinput = this.find("txtinput"); - this.find("bt-ok").onbtclick = (e) => { - 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").onbtclick = (e) => { - return this.quit(); - }; - if (this.data && this.data.tree) { - if (this.data && this.data.cat) { - let seldata; - 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, list) { - for (let data of list) { - if (data.id === id) { - return data; - } - if (data.nodes) { - this.findDataByID(id, data.nodes); - } - } - return undefined; - } - } - blogger.BloggerCategoryDialog = BloggerCategoryDialog; - BloggerCategoryDialog.scheme = `\ - - - - - - - -
- - -
-
-
-
\s - `; - // This dialog is use for cv section editing - class BloggerCVSectionDiaglog extends OS.GUI.BasicDialog { - 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.name]); - } - } - if (this.data && this.data.section) { - this.editor.value(this.data.section.content); - } - this.find("section-publish").swon = (this.data && this.data.section && Number(this.data.section.publish) ? true : false); - this.find("bt-cv-sec-save").onbtclick = (e) => { - const data = {}; - for (let v of inputs) { - data[v.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").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"); - } - } - blogger.BloggerCVSectionDiaglog = BloggerCVSectionDiaglog; - BloggerCVSectionDiaglog.scheme = `\ - - - - - - - - - - - - - - - - - - - - - -
- -
-
- - -
-
-
`; - // this dialog is for send mail - class BloggerSendmailDiaglog extends OS.GUI.BasicDialog { - constructor() { - super("BloggerSendmailDiaglog"); - } - main() { - super.main(); - this.maillinglist = this.find("email-list"); - const title = (new RegExp("^#+(.*)\n", "g")).exec(this.data.content); - this.find("mail-title").value = title[1]; - const content = (this.data.content.substring(0, 500)) + "..."; - this.find("contentarea").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").onbtclick = (e) => { - 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").value, - content: this.find("contentarea").value - } - }; - return this._api.apigateway(data, false) - .then((d) => { - 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); - }); - }; - } - } - blogger.BloggerSendmailDiaglog = BloggerSendmailDiaglog; - BloggerSendmailDiaglog.scheme = `\ - - - - -
- -
- - - - -
- -
- -
-
-
-
-
`; - 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\ -`; - })(blogger = application.blogger || (application.blogger = {})); - })(application = OS.application || (OS.application = {})); -})(OS || (OS = {})); - -var OS; -(function (OS) { - let application; - (function (application) { - let blogger; - (function (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.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.text = v.title; - this.refs.ctimestr.text = __("Created: {0}", v.ctimestr); - this.refs.utimestr.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); - })(blogger = application.blogger || (application.blogger = {})); - })(application = OS.application || (OS.application = {})); -})(OS || (OS = {})); +var OS;!function(t){let e;!function(t){class e extends t.BaseApplication{constructor(t){super("Blogger",t),this.previewOn=!1}async init_db(){try{const e=await this.openDialog("FileDialog",{title:__("Open/create new database"),file:"Untitled.db"});var t=e.file.path.asFileHandle();"file"===e.file.type&&(t=t.parent());const i=`${t.path}/${e.name}`.asFileHandle();this.dbhandle=("sqlite://"+i.genealogy.join("/")).asFileHandle();const a=await this.dbhandle.read();if(!a.user){this.dbhandle.cache={address:"TEXT",Phone:"TEXT",shortbiblio:"TEXT",fullname:"TEXT",email:"TEXT",url:"TEXT",photo:"TEXT"};const t=await this.dbhandle.write("user");if(t.error)throw new Error(t.error)}if(!a.cv_cat){this.dbhandle.cache={publish:"NUMERIC",name:"TEXT",pid:"NUMERIC"};const t=await this.dbhandle.write("cv_cat");if(t.error)throw new Error(t.error)}if(!a.cv_sections){this.dbhandle.cache={title:"TEXT",start:"NUMERIC",location:"TEXT",end:"NUMERIC",content:"TEXT",subtitle:"TEXT",publish:"NUMERIC",cid:"NUMERIC"};const t=await this.dbhandle.write("cv_sections");if(t.error)throw new Error(t.error)}if(!a.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 t=await this.dbhandle.write("blogs");if(t.error)throw new Error(t.error)}if(!a.st_similarity){this.dbhandle.cache={pid:"NUMERIC",sid:"NUMERIC",score:"NUMERIC"};const t=await this.dbhandle.write("st_similarity");if(t.error)throw new Error(t.error)}if(!a.subscribers){this.dbhandle.cache={name:"TEXT",email:"TEXT"};const t=await this.dbhandle.write("subscribers");if(t.error)throw new Error(t.error)}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(t){this.error(__("Unable to init database file: {0}",t.toString()),t),this.dbhandle=void 0}}menu(){return[{text:"__(Open/Create database)",onmenuselect:t=>{this.init_db()}}]}main(){this.user={},this.cvlist=this.find("cv-list"),this.cvlist.ontreeselect=t=>{if(!t)return;const{data:e}=t.data.item;return this.CVSectionByCID(Number(e.id))},this.inputtags=this.find("input-tags"),this.bloglist=this.find("blog-list"),this.seclist=this.find("cv-sec-list");let e=this.find("photo");return $(e).on("click",async t=>{try{const t=await this.openDialog("FileDialog",{title:__("Select image file"),mimes:["image/.*"]});return e.value=t.file.path}catch(t){return this.error(__("Unable to get file"),t)}}),this.tabcontainer=this.find("tabcontainer"),this.tabcontainer.ontabselect=t=>this.fetchData(t.data.container.aid),this.find("bt-user-save").onbtclick=t=>this.saveUser(),this.find("blog-load-more").onbtclick=t=>{this.loadBlogs()},this.find("cv-cat-add").onbtclick=async e=>{try{const e=await this.fetchCVCat(),i=await this.openDialog(new t.blogger.BloggerCategoryDialog,{title:__("Add category"),tree:e});this.cvcatdb.cache={name:i.value,pid:i.p.id,publish:1};const a=await this.cvcatdb.write(void 0);if(a.error)throw new Error(a.error);await this.refreshCVCat()}catch(e){this.error(__("cv-cat-add: {0}",e.toString()),e)}},this.find("cv-cat-edit").onbtclick=async e=>{try{const e=this.cvlist.selectedItem;if(!e)return;const i=e.data;if(!i)return;const a=await this.fetchCVCat(),s=await this.openDialog(new t.blogger.BloggerCategoryDialog,{title:__("Edit category"),tree:a,cat:i}),n=i.$vfs;n.cache={id:i.id,publish:i.publish,pid:s.p.id,name:s.value};const r=await n.write(void 0);if(r.error)throw new Error(r.error);await this.refreshCVCat()}catch(e){this.error(__("cv-cat-edit: {0}",e.toString()),e)}},this.find("cv-cat-del").onbtclick=async t=>{try{const t=this.cvlist.selectedItem;if(!t)return;const e=t.data;if(!e)return;if(!await this.openDialog("YesNoDialog",{title:__("Delete category"),iconclass:"fa fa-question-circle",text:__("Do you really want to delete: {0}?",e.name)}))return;await this.deleteCVCat(e)}catch(t){this.error(__("cv-cat-del: {0}",t.toString()),t)}},this.find("cv-sec-add").onbtclick=async e=>{try{const e=this.cvlist.selectedItem;if(!e)return;const i=e.data;if(!i||"0"===i.id)return this.toast(__("Please select a category"));const a=await this.openDialog(new t.blogger.BloggerCVSectionDiaglog,{title:__("New section entry for {0}",i.name)});a.cid=Number(i.id),a.start=Number(a.start),a.end=Number(a.end),this.cvsecdb.cache=a;const s=await this.cvsecdb.write(void 0);if(s.error)throw new Error(s.error);await this.CVSectionByCID(Number(i.id))}catch(e){this.error(__("cv-sec-add: {0}",e.toString()),e)}},this.find("cv-sec-move").onbtclick=async e=>{try{const e=this.seclist.selectedItem;if(!e)return this.toast(__("Please select a section to move"));const i=e.data,a=i.$vfs;console.log(a);const s=await this.fetchCVCat(),n=await this.openDialog(new t.blogger.BloggerCategoryDialog,{title:__("Move to"),tree:s,selonly:!0});a.cache={id:i.id,cid:n.p.id};const r=await a.write(void 0);if(r.error)throw new Error(r.error);await this.CVSectionByCID(i.cid),this.seclist.unselect()}catch(e){this.error(__("cv-sec-move: {0}",e.toString()),e)}},this.find("cv-sec-edit").onbtclick=async e=>{try{const e=this.seclist.selectedItem;if(!e)return this.toast(__("Please select a section to edit"));const i=e.data,a=await this.openDialog(new t.blogger.BloggerCVSectionDiaglog,{title:__("Modify section entry"),section:i});a.cid=Number(i.cid),a.start=Number(a.start),a.end=Number(a.end);const s=i.$vfs;s.cache=a;const n=await s.write(void 0);if(n.error)throw new Error(n.error);await this.CVSectionByCID(Number(i.cid))}catch(e){this.error(__("cv-sec-edit: {0}",e.toString()),e)}},this.seclist.onitemclose=t=>{if(!t)return;const e=t.data.item.data;return this.openDialog("YesNoDialog",{iconclass:"fa fa-question-circle",text:__("Do you really want to delete: {0}?",e.title)}).then(async i=>{if(i)try{const i=await this.cvsecdb.remove({where:{id:e.id}});if(i.error)throw new Error(i.error);return this.seclist.delete(t.data.item)}catch(t){return this.error(__("Cannot delete the section: {0}",t.toString()),t)}}),!1},this.editor=new EasyMDE({element:this.find("markarea"),autoDownloadFontAwesome:!1,autofocus:!0,tabSize:4,indentWithTabs:!0,toolbar:[{name:__("New"),className:"fa fa-file",action:t=>(this.bloglist.unselect(),this.clearEditor())},{name:__("Save"),className:"fa fa-save",action:t=>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:t=>this.openDialog("FileDialog",{title:__("Select image file"),mimes:["image/.*"]}).then(t=>t.file.path.asFileHandle().publish().then(t=>this.editor.codemirror.getDoc().replaceSelection(`![](${this._api.handle.shared}/${t.result})`)).catch(t=>this.error(__("Cannot export file for embedding to text"),t)))},{name:"Youtube",className:"fa fa-youtube",action:t=>this.editor.codemirror.getDoc().replaceSelection("[[youtube:]]")},"|",{name:__("Preview"),className:"fa fa-eye no-disable",action:t=>{this.previewOn=!this.previewOn,EasyMDE.togglePreview(t),renderMathInElement(this.find("editor-container"))}},"|",{name:__("Send mail"),className:"fa fa-paper-plane",action:async e=>{try{const e=await this.subdb.read(),i=this.bloglist.selectedItem;if(!i)return this.error(__("No post selected"));const a=i.data;await this.openDialog(new t.blogger.BloggerSendmailDiaglog,{title:__("Send mail"),content:this.editor.value(),mails:e,id:a.id}),this.toast(__("Emails sent"))}catch(e){this.error(__("Error sending mails: {0}",e.toString()),e)}}}]}),this.bloglist.onlistselect=async t=>{const e=this.bloglist.selectedItem;if(!e)return;const i=e.data;if(i)try{const t=await this.blogdb.read({where:{id:Number(i.id)}});if(!t||0==t.length)throw new Error(__("No record found for ID {}",i.id).__());const e=t[0];return this.editor.value(e.content),this.inputtags.value=e.tags,this.find("blog-publish").swon=!!Number(e.publish)}catch(t){return this.error(__("Cannot fetch the entry content"),t)}},this.bloglist.onitemclose=t=>{if(!t)return;const e=t.data.item,i=e.data;return this.openDialog("YesNoDialog",{title:__("Delete a post"),iconclass:"fa fa-question-circle",text:__("Do you really want to delete this post ?")}).then(async t=>{if(!t)return;const a=await this.blogdb.remove({where:{id:Number(i.id)}});if(a.error)throw new Error(a.error);return this.bloglist.delete(e),this.bloglist.unselect(),this.clearEditor()}),!1},this.bindKey("CTRL-S",()=>{const t=this.tabcontainer.selectedTab;if(t&&"blog-container"===t.container.aid)return this.saveBlog()}),this.on("resize",()=>this.resizeContent()),this.resizeContent(),this.init_db()}fetchData(t){switch(t){case"user-container":return this.userdb.read().then(t=>{if(t&&0!=t.length)return this.user=t[0],this.select("[input-class='user-input']").map((t,e)=>$(e).val(this.user[e.name]))}).catch(t=>this.error(__("Cannot fetch user data"),t));case"cv-container":return this.refreshCVCat();default:return this.last_ctime=0,this.bloglist.data=[],this.loadBlogs()}}async saveUser(){try{const t=this.select("[input-class='user-input']");for(let e of t)this.user[e.name]=$(e).val();if(!this.user.fullname||""===this.user.fullname)return this.toast(__("Full name must be entered"));let e=this.userdb;this.user&&this.user.id&&(e=`${this.userdb.path}@${this.user.id}`.asFileHandle()),e.cache=this.user;const i=await e.write(void 0);if(i.error)throw new Error(i.error);this.user.id||(this.user.id=i.result),this.toast(__("User data updated"))}catch(t){this.error(__("Cannot save user data: {0}",t.toString()),t)}}refreshCVCat(){return this.fetchCVCat().then(t=>(this.cvlist.data=t,this.cvlist.expandAll())).catch(t=>this.error(__("Unable to load categories"),t))}fetchCVCat(){return new Promise(async(t,e)=>{try{const e={text:"Porfolio",id:0,nodes:[]},i={order:["name$asc"]},a=await this.cvcatdb.read(i);this.catListToTree(a,e,0),t(e)}catch(t){e(__e(t))}})}catListToTree(t,e,i){const a=t.filter(t=>t.pid==i);if(0===a.length)return e.nodes=null;for(let i of a)i.nodes=[],i.text=i.name,this.catListToTree(t,i,i.id),e.nodes.push(i)}deleteCVCat(t){return new Promise(async(e,i)=>{try{const e=[];var a=function(t){e.push(t.id),t.nodes&&t.nodes.map(t=>a(t))};a(t);let i=await this.cvsecdb.remove({where:{$or:{cid:e}}});if(i.error)throw new Error(i.error);if(i=await this.cvcatdb.remove({where:{$or:{id:e}}}),i.error)throw new Error(i.error);await this.refreshCVCat(),this.seclist.data=[]}catch(t){i(__e(t))}})}CVSectionByCID(t){return new Promise(async(e,i)=>{try{const e=await this.cvsecdb.read({where:{cid:t},order:["start$desc"]}),i=[];this.find("cv-sec-status").text=__("Found {0} sections",e.length);for(let t of e)t.closable=!0,t.tag="afx-blogger-cvsection-item",t.start=Number(t.start),t.end=Number(t.end),t.start<1e3&&(t.start=void 0),t.end<1e3&&(t.end=void 0),i.push(t);this.seclist.data=i}catch(t){i(__e(t))}})}async saveBlog(){try{let t=void 0;const e=this.bloglist.selectedItem;e&&(t=e.data);const i=this.inputtags.value,a=this.editor.value(),s=new RegExp("^#+(.*)\n","g").exec(a);if(!s||2!==s.length)return this.toast(__("Please insert a title in the text: beginning with heading"));if(""===i)return this.toast(__("Please enter tags"));const n=new Date,r={content:a,title:s[1].trim(),tags:i,ctime:t?t.ctime:n.timestamp(),ctimestr:t?t.ctimestr:n.toString(),utime:n.timestamp(),utimestr:n.toString(),rendered:this.process(this.editor.options.previewRender(a)),publish:this.find("blog-publish").swon?1:0};let o=this.blogdb;t&&(r.id=t.id,o=t.$vfs),o.cache=r;const l=await o.write(void 0);if(l.error)throw new Error(l.error);t?e.data=r:(this.last_ctime=0,this.bloglist.data=[],await this.loadBlogs())}catch(t){this.error(__("Cannot save blog: {0}",t.toString()),t)}}process(t){let e;const i=/\[\[youtube:([^\]]*)\]\]/g,a=[];for(;null!==(e=i.exec(t));)a.push(e);if(!(a.length>0))return t;let s="",n=0;for(let e of a)s+=t.substring(n,e.index),s+=``,n=e.index+e[0].length;return s+=t.substring(n,t.length),s}clearEditor(){return this.editor.value(""),this.inputtags.value="",this.find("blog-publish").swon=!1}loadBlogs(){return new Promise(async(t,e)=>{try{const t={order:["ctime$desc"],fields:["id","title","ctimestr","ctime","utime","utimestr"],limit:10};this.last_ctime&&(t.where={ctime$lt:this.last_ctime});const e=await this.blogdb.read(t);if(0==e.length)return void this.toast(__("No more record to load"));this.last_ctime=e[e.length-1].ctime;for(let t of e)t.tag="afx-blogger-post-item",this.bloglist.push(t);return this.clearEditor(),this.bloglist.selected=-1}catch(t){e(__e(t))}})}resizeContent(){const t=this.find("editor-container"),e=$(".EasyMDEContainer",t).children(),i=$(this.scheme).find(".afx-window-top")[0],a=e[0],s=e[3],n=$(this.scheme).height()-$(i).height()-$(a).height()-$(s).height()-90;return $(e[1]).css("height",n+"px")}}t.Blogger=e,e.singleton=!0,e.dependencies=["pkg://SimpleMDE/main.js","pkg://SimpleMDE/main.css","pkg://Katex/main.js","pkg://Katex/main.css","pkg://SQLiteDB/libsqlite.js"]}(e=t.application||(t.application={}))}(OS||(OS={})),function(t){let e;!function(e){let i;!function(e){class i extends t.GUI.BasicDialog{constructor(){super("BloggerCategoryDialog",i.scheme)}main(){if(super.main(),this.tree=this.find("tree"),this.txtinput=this.find("txtinput"),this.find("bt-ok").onbtclick=t=>{const e=this.tree.selectedItem;if(!e)return this.notify(__("Please select a parent category"));const i=e.data,a=this.txtinput.value;return""!==a||this.data.selonly?this.data.cat&&this.data.cat.id===i.id?this.notify(__("Parent can not be the category itself")):(this.handle&&this.handle({p:i,value:a}),this.quit()):this.notify(__("Please enter category name"))},this.find("bt-cancel").onbtclick=t=>this.quit(),this.data&&this.data.tree){if(this.data&&this.data.cat){let t;this.txtinput.value=this.data.cat.name,t="0"===this.data.cat.pid?this.data.tree:this.findDataByID(this.data.cat.pid,this.data.tree.nodes),t&&(t.selected=!0)}return this.tree.data=this.data.tree,this.tree.expandAll()}}findDataByID(t,e){for(let i of e){if(i.id===t)return i;i.nodes&&this.findDataByID(t,i.nodes)}}}e.BloggerCategoryDialog=i,i.scheme='\n \n \n \n \n \n \n
\n \n \n
\n
\n
\n
s\n ';class a extends t.GUI.BasicDialog{constructor(){super("BloggerCVSectionDiaglog")}main(){super.main(),this.editor=new EasyMDE({autoDownloadFontAwesome:!1,element:this.find("contentarea"),status:!1,toolbar:!1}),$(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 t=this.select("[input-class='user-input']");if(this.data&&this.data.section)for(let e of t)$(e).val(this.data.section[e.name]);return this.data&&this.data.section&&this.editor.value(this.data.section.content),this.find("section-publish").swon=!!(this.data&&this.data.section&&Number(this.data.section.publish)),this.find("bt-cv-sec-save").onbtclick=e=>{const i={};for(let e of t)i[e.name]=$(e).val();if(i.content=this.editor.value(),""===i.title&&""===i.content)return this.notify(__("Title or content must not be blank"));this.data&&this.data.section&&(i.id=this.data.section.id);const a=this.find("section-publish").swon;return i.publish=!0===a?1:0,this.handle&&this.handle(i),this.quit()},this.on("resize",()=>this.resizeContent()),this.resizeContent()}resizeContent(){const t=this.find("editor-container"),e=$(".EasyMDEContainer",t).children(),i=$(t).height()-30;return $(e[0]).css("height",i+"px")}}e.BloggerCVSectionDiaglog=a,a.scheme='\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n
\n
\n \n \n
\n
\n
';class s extends t.GUI.BasicDialog{constructor(){super("BloggerSendmailDiaglog")}main(){super.main(),this.maillinglist=this.find("email-list");const t=new RegExp("^#+(.*)\n","g").exec(this.data.content);this.find("mail-title").value=t[1];const e=this.data.content.substring(0,500)+"...";this.find("contentarea").value=s.template.format(this.data.id,e);const i=this.data.mails.map(t=>({text:t.name,email:t.email,switch:!0,checked:!0}));return this.maillinglist.data=i,this.find("bt-sendmail").onbtclick=t=>{const e=this.maillinglist.data,i=[];for(let t of e)!0===t.checked&&(console.log(t.email),i.push(t.email));if(0===i.length)return this.notify(__("No email selected"));const a={path:this.meta().path+"/sendmail.lua",parameters:{to:i,title:this.find("mail-title").value,content:this.find("contentarea").value}};return this._api.apigateway(a,!1).then(t=>t.error?this.notify(__("Unable to send mail to: {0}",t.result.join(","))):this.quit()).catch(t=>(console.log(t),this.error(__("Error sending mail: {0}",t.toString()),t)))}}}e.BloggerSendmailDiaglog=s,s.scheme='\n \n \n \n
\n \n
\n \n \n \n \n
\n \n
\n \n
\n
\n
\n
\n
',s.template="Hello,\n\nXuan Sang LE has just published a new post on his blog: https://blog.iohub.dev/post/id/{0}\n\n==========\n{1}\n==========\n\n\nRead the full article via:\nhttps://blog.iohub.dev/post/id/{0}\n\nYou receive this email because you have been subscribed to his blog.\n\nHave a nice day,\n\nSent from Blogger, an AntOS application"}(i=e.blogger||(e.blogger={}))}(e=t.application||(t.application={}))}(OS||(OS={})),function(t){let e;!function(e){let i;!function(e){class i extends t.GUI.tag.ListViewItemTag{constructor(){super()}ondatachange(){if(!this.data)return;const t=this.data,e=["content","start","end"];return this.closable=t.closable,(()=>{const i=[];for(let a in this.refs){const s=this.refs[a];t[a]&&""!==t[a]?e.includes(a)?i.push($(s).text(t[a])):i.push(s.text=t[a]):i.push(void 0)}return i})()}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"}]}}}t.GUI.tag.define("afx-blogger-cvsection-item",i);class a extends t.GUI.tag.ListViewItemTag{constructor(){super()}ondatachange(){if(!this.data)return;const t=this.data;t.closable=!0,this.closable=t.closable,this.refs.title.text=t.title,this.refs.ctimestr.text=__("Created: {0}",t.ctimestr),this.refs.utimestr.text=__("Updated: {0}",t.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"}]}}}t.GUI.tag.define("afx-blogger-post-item",a)}(i=e.blogger||(e.blogger={}))}(e=t.application||(t.application={}))}(OS||(OS={})); \ No newline at end of file diff --git a/Blogger/build/debug/package.json b/Blogger/build/debug/package.json index 0fcd2be..67e3f7b 100644 --- a/Blogger/build/debug/package.json +++ b/Blogger/build/debug/package.json @@ -6,7 +6,7 @@ "author": "Xuan Sang LE", "email": "xsang.le@gmail.com" }, - "version": "0.2.9-a", + "version": "0.2.10-a", "category": "Internet", "iconclass": "fa fa-book", "dependencies": [ @@ -36,7 +36,6 @@ "No email selected": "No email selected", "Unable to send mail to: {0}": "Unable to send mail to: {0}", "Error sending mail: {0}": "Error sending mail: {0}", - "Cannot fetch subscribers data: {0}": "Cannot fetch subscribers data: {0}", "Open/create new database": "Open/create new database", "Unable to init database file: {0}": "Unable to init database file: {0}", "Select image file": "Select image file", @@ -65,28 +64,20 @@ "No post selected": "No post selected", "Emails sent": "Emails sent", "Error sending mails: {0}": "Error sending mails: {0}", + "No record found for ID {}": "No record found for ID {}", "Cannot fetch the entry content": "Cannot fetch the entry content", "Delete a post": "Delete a post", "Do you really want to delete this post ?": "Do you really want to delete this post ?", "Cannot fetch user data": "Cannot fetch user data", "Full name must be entered": "Full name must be entered", "User data updated": "User data updated", - "Cannot save user data": "Cannot save user data", + "Cannot save user data: {0}": "Cannot save user data: {0}", "Unable to load categories": "Unable to load categories", "Found {0} sections": "Found {0} sections", "Please insert a title in the text: beginning with heading": "Please insert a title in the text: beginning with heading", "Please enter tags": "Please enter tags", "Cannot save blog: {0}": "Cannot save blog: {0}", - "Cannot add new category": "Cannot add new category", - "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}", + "No more record to load": "No more record to load", "Full name": "Full name", "Address": "Address", "Phone": "Phone", @@ -95,7 +86,10 @@ "Photo": "Photo", "Short biblio": "Short biblio", "Categories": "Categories", - "Tags": "Tags" + "Load more": "Load more", + "Tags": "Tags", + "Created: {0}": "Created: {0}", + "Updated: {0}": "Updated: {0}" } } } \ No newline at end of file diff --git a/Blogger/build/debug/scheme.html b/Blogger/build/debug/scheme.html index cf8ed01..e10b98d 100644 --- a/Blogger/build/debug/scheme.html +++ b/Blogger/build/debug/scheme.html @@ -1,5 +1,5 @@ - + @@ -65,7 +65,10 @@ - + + + +
diff --git a/Blogger/build/release/Blogger.zip b/Blogger/build/release/Blogger.zip index 4ab506855e8e0fd9d9309bb990ab31260faf4a7d..ea7d5ea4fe51192f27776fec6e027ae4c17664d1 100644 GIT binary patch delta 9305 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 diff --git a/Blogger/cvsection.html b/Blogger/cvsection.html deleted file mode 100644 index 51d6928..0000000 --- a/Blogger/cvsection.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - -
- -
-
- - -
-
-
\ No newline at end of file diff --git a/Blogger/dialogs.coffee b/Blogger/dialogs.coffee deleted file mode 100644 index 630d9de..0000000 --- a/Blogger/dialogs.coffee +++ /dev/null @@ -1,189 +0,0 @@ -# Copyright 2017-2018 Xuan Sang LE - -# 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 = """ - - - - - - - -
- - -
-
-
-
-""" - -# 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 -""" \ No newline at end of file diff --git a/Blogger/dialogs.ts b/Blogger/dialogs.ts new file mode 100644 index 0000000..8453425 --- /dev/null +++ b/Blogger/dialogs.ts @@ -0,0 +1,278 @@ +// Copyright 2017-2018 Xuan Sang LE + +// 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; + 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[]) { + for (let data of list) { + if (data.id === id) { return data; } + if (data.nodes) { + this.findDataByID(id, data.nodes); + } + } + return undefined; + } + } + + BloggerCategoryDialog.scheme = `\ + + + + + + + +
+ + +
+
+
+
\s + `; + + // This dialog is use for cv section editing + + export class BloggerCVSectionDiaglog extends OS.GUI.BasicDialog { + private editor: GenericObject; + 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 = {}; + 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 = `\ + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + +
+
+
`; + + // 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 = `\ + + + + +
+ +
+ + + + +
+ +
+ +
+
+
+
+
`; + + + 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\ +`; + } + } +} \ No newline at end of file diff --git a/Blogger/main.coffee b/Blogger/main.coffee deleted file mode 100644 index 9925b49..0000000 --- a/Blogger/main.coffee +++ /dev/null @@ -1,506 +0,0 @@ -# Copyright 2017-2018 Xuan Sang LE - -# 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 """ - - """ - 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 \ No newline at end of file diff --git a/Blogger/main.ts b/Blogger/main.ts new file mode 100644 index 0000000..955e477 --- /dev/null +++ b/Blogger/main.ts @@ -0,0 +1,893 @@ +// Copyright 2017-2018 Xuan Sang LE + +// 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; + 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; + 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[], data: GenericObject, 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): Promise { + return new Promise(async (resolve, reject) => { + try { + let v: any; + const ids = []; + var func = function (c: GenericObject) { + 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 { + 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 = { + 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) => `\ +\ +`; + 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 { + return new Promise( async (ok, reject)=> { + try { + const filter: GenericObject = { + 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", + ]; + } +} diff --git a/Blogger/package.json b/Blogger/package.json index 0fcd2be..67e3f7b 100644 --- a/Blogger/package.json +++ b/Blogger/package.json @@ -6,7 +6,7 @@ "author": "Xuan Sang LE", "email": "xsang.le@gmail.com" }, - "version": "0.2.9-a", + "version": "0.2.10-a", "category": "Internet", "iconclass": "fa fa-book", "dependencies": [ @@ -36,7 +36,6 @@ "No email selected": "No email selected", "Unable to send mail to: {0}": "Unable to send mail to: {0}", "Error sending mail: {0}": "Error sending mail: {0}", - "Cannot fetch subscribers data: {0}": "Cannot fetch subscribers data: {0}", "Open/create new database": "Open/create new database", "Unable to init database file: {0}": "Unable to init database file: {0}", "Select image file": "Select image file", @@ -65,28 +64,20 @@ "No post selected": "No post selected", "Emails sent": "Emails sent", "Error sending mails: {0}": "Error sending mails: {0}", + "No record found for ID {}": "No record found for ID {}", "Cannot fetch the entry content": "Cannot fetch the entry content", "Delete a post": "Delete a post", "Do you really want to delete this post ?": "Do you really want to delete this post ?", "Cannot fetch user data": "Cannot fetch user data", "Full name must be entered": "Full name must be entered", "User data updated": "User data updated", - "Cannot save user data": "Cannot save user data", + "Cannot save user data: {0}": "Cannot save user data: {0}", "Unable to load categories": "Unable to load categories", "Found {0} sections": "Found {0} sections", "Please insert a title in the text: beginning with heading": "Please insert a title in the text: beginning with heading", "Please enter tags": "Please enter tags", "Cannot save blog: {0}": "Cannot save blog: {0}", - "Cannot add new category": "Cannot add new category", - "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}", + "No more record to load": "No more record to load", "Full name": "Full name", "Address": "Address", "Phone": "Phone", @@ -95,7 +86,10 @@ "Photo": "Photo", "Short biblio": "Short biblio", "Categories": "Categories", - "Tags": "Tags" + "Load more": "Load more", + "Tags": "Tags", + "Created: {0}": "Created: {0}", + "Updated: {0}": "Updated: {0}" } } } \ No newline at end of file diff --git a/Blogger/scheme.html b/Blogger/scheme.html index cf8ed01..e10b98d 100644 --- a/Blogger/scheme.html +++ b/Blogger/scheme.html @@ -1,5 +1,5 @@ - + @@ -65,7 +65,10 @@ - + + + +
diff --git a/Blogger/sendmail.html b/Blogger/sendmail.html deleted file mode 100644 index 799709a..0000000 --- a/Blogger/sendmail.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - -
- -
- - - - -
- -
- -
-
-
-
-
\ No newline at end of file diff --git a/Blogger/tags.coffee b/Blogger/tags.coffee deleted file mode 100644 index b751498..0000000 --- a/Blogger/tags.coffee +++ /dev/null @@ -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 \ No newline at end of file diff --git a/Blogger/tags.ts b/Blogger/tags.ts new file mode 100644 index 0000000..f4a7a06 --- /dev/null +++ b/Blogger/tags.ts @@ -0,0 +1,104 @@ + +interface Array { + /** + * 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); + } + } +} \ No newline at end of file diff --git a/SQLiteDB/LibSQLite.ts b/SQLiteDB/LibSQLite.ts index c2d4ed5..aea192c 100644 --- a/SQLiteDB/LibSQLite.ts +++ b/SQLiteDB/LibSQLite.ts @@ -406,15 +406,27 @@ namespace OS { filter.table_name = this._table_name; 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[] = await this._handle.select(filter); if(this._id) { this.cache = data[0]; } 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; } resolve(this.cache) @@ -444,7 +456,7 @@ namespace OS { await this.onready(); 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); resolve({result:ret, error: false}); return @@ -504,7 +516,8 @@ namespace OS { filter.table_name = this._table_name; 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); resolve({result: ret, error: false}); diff --git a/SQLiteDB/build/debug/libsqlite.js b/SQLiteDB/build/debug/libsqlite.js index 7cc3853..c3fc375 100644 --- a/SQLiteDB/build/debug/libsqlite.js +++ b/SQLiteDB/build/debug/libsqlite.js @@ -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={})); \ No newline at end of file +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={})); \ No newline at end of file diff --git a/SQLiteDB/build/debug/main.js b/SQLiteDB/build/debug/main.js index f9a242b..0718b43 100644 --- a/SQLiteDB/build/debug/main.js +++ b/SQLiteDB/build/debug/main.js @@ -1 +1 @@ -var OS;!function(t){let e;!function(e){class i extends e.BaseApplication{constructor(t){super("SQLiteDBBrowser",t)}menu(){return[{text:"__(File)",nodes:[{text:"__(New database)",dataid:"new",shortcut:"A-N"},{text:"__(Open database)",dataid:"open",shortcut:"A-O"}],onchildselect:t=>{switch(t.data.item.data.dataid){case"new":return this.newFile();case"open":return this.openFile()}}}]}list_tables(){this.filehandle.read().then(t=>{const e=[];for(let i in t)e.push({text:i,name:i,handle:t[i]});this.tbl_list.data=e,e.length>0&&(this.tbl_list.selected=e.length-1)})}async openFile(){try{let t;t=this.args&&this.args.length>0?this.args[0].path.asFileHandle():(await this.openDialog("FileDialog",{title:__("Open file"),mimes:this.meta().mimes})).file.path.asFileHandle(),this.filehandle=("sqlite://"+t.genealogy.join("/")).asFileHandle(),await this.filehandle.onready(),this.list_tables()}catch(t){this.error(__("Unable to open database file: {0}",t.toString()),t)}}async newFile(){try{const e=await this.openDialog("FileDialog",{title:__("Save as"),file:"Untitled.db"});var t=e.file.path.asFileHandle();"file"===e.file.type&&(t=t.parent());const i=`${t.path}/${e.name}`.asFileHandle();this.filehandle=("sqlite://"+i.genealogy.join("/")).asFileHandle(),await this.filehandle.onready(),this.list_tables()}catch(t){this.error(__("Unable to init database file: {0}",t.toString()),t)}}main(){this.filehandle=void 0,this.tbl_list=this.find("tbl-list"),this.grid_table=this.find("tb-browser"),this.grid_scheme=this.find("sch-browser"),this.grid_table.resizable=!0,this.grid_scheme.resizable=!0,this.grid_scheme.header=[{text:__("Field name")},{text:__("Field type")}],this.btn_loadmore=this.find("bt-load-next"),this.container=this.find("container"),this.bindKey("ALT-N",()=>this.newFile()),this.bindKey("ALT-O",()=>this.openFile()),this.container.ontabselect=t=>{if(0==this.container.selectedIndex){if(!this.tbl_list.selectedItem)return;const t=this.tbl_list.selectedItem.data.handle.info.schema;if(!t)return;const e=[];for(let i in t.types)e.push([{text:i},{text:t.types[i]}]);this.grid_scheme.rows=e}},this.find("bt-rm-table").onbtclick=async t=>{try{if(!this.filehandle)return this.notify(__("Please open a database file"));if(null==this.tbl_list.selectedItem)return;const t=this.tbl_list.selectedItem.data.name;await this.openDialog("YesNoDialog",{title:__("Confirm delete?"),text:__("Do you realy want to delete table: {0}",t)})&&(await this.filehandle.remove(t),this.list_tables())}catch(t){this.error(__("Unable to execute action table delete: {0}",t.toString()),t)}},this.find("bt-add-table").onbtclick=async t=>{try{if(!this.filehandle)return this.notify(__("Please open a database file"));const t=await this.openDialog(new a,{title:__("Create new table")});this.filehandle.cache=t.schema,await this.filehandle.write(t.name),this.list_tables()}catch(t){this.error(__("Unable to create table: {0}",t.toString()),t)}},this.find("btn-edit-record").onbtclick=async t=>{this.edit_record()},this.find("btn-add-record").onbtclick=async t=>{this.add_record()},this.find("btn-delete-record").onbtclick=async t=>{this.remove_record()},this.btn_loadmore.onbtclick=async t=>{try{await this.load_table()}catch(t){this.error(__("Error reading table: {0}",t.toString()),t)}},this.tbl_list.onlistselect=async t=>{try{if(!this.tbl_list.selectedItem)return;const t=this.tbl_list.selectedItem.data.handle;await t.onready(),this.last_max_id=0;const e=t.info.schema.fields.map(t=>({text:t}));this.grid_table.header=e,this.grid_table.rows=[];const i=await t.read({fields:["COUNT(*)"]});this.n_records=i[0]["COUNT(*)"],this.btn_loadmore.text="0/"+this.n_records,await this.load_table(),this.container.selectedIndex=1}catch(t){this.error(__("Error reading table: {0}",t.toString()),t)}},this.grid_table.oncelldbclick=async t=>{this.edit_record()},this.openFile()}async add_record(){try{const t=this.tbl_list.selectedItem;if(!t)return;const e=t.data.handle,i=t.data.handle.info.schema,a={};for(let t in i.types)["INTEGER","REAL","NUMERIC"].includes(i.types[t])?a[t]=0:a[t]="";console.log(a);const n=await this.openDialog(new s,{title:__("New record"),schema:i,record:a});e.cache=n,await e.write(void 0),this.n_records+=1,await this.load_table()}catch(t){this.error(__("Error edit/view record: {0}",t.toString()),t)}}async remove_record(){try{const t=this.grid_table.selectedCell,e=this.grid_table.selectedRow,i=this.tbl_list.selectedItem;if(!t||!i)return;const a=t.data.record[i.data.handle.info.schema.pk];if(!await this.openDialog("YesNoDialog",{title:__("Delete record"),text:__("Do you realy want to delete record {0}",a)}))return;const s=`${i.data.handle.path}@${a}`.asFileHandle();await s.remove(),this.n_records--,this.grid_table.delete(e),this.btn_loadmore.text=`${this.grid_table.rows.length}/${this.n_records}`}catch(t){this.error(__("Error deleting record: {0}",t.toString()),t)}}async edit_record(){try{const t=this.grid_table.selectedCell,e=this.grid_table.selectedRow,i=this.tbl_list.selectedItem;if(!t||!i)return;const a=await this.openDialog(new s,{title:__("View/edit record"),schema:i.data.handle.info.schema,record:t.data.record}),n=t.data.record[i.data.handle.info.schema.pk],l=`${i.data.handle.path}@${n}`.asFileHandle();l.cache=a,await l.write(void 0);const d=[];for(let t of l.info.schema.fields){let e=a[t];e.length>100&&(e=e.substring(0,100)),d.push({text:e,record:a})}e.data=d}catch(t){this.error(__("Error edit/view record: {0}",t.toString()),t)}}async load_table(){if(this.grid_table.rows&&this.grid_table.rows.length>=this.n_records)return;if(!this.tbl_list.selectedItem)return;const t=this.tbl_list.selectedItem.data.handle;await t.onready();const e=t.info.schema.fields.map(t=>({text:t})),i={where:{},limit:10};i.where[t.info.schema.pk+"$gt"]=this.last_max_id;const a=await t.read(i);if(a&&a.length>0){for(let t of a){const i=[];t.id&&t.id>this.last_max_id&&(this.last_max_id=t.id);for(let a in e){let s=t[e[a].text];s.length>100&&(s=s.substring(0,100)),i.push({text:s,record:t})}this.grid_table.push(i,!1)}this.grid_table.scroll_to_bottom()}this.btn_loadmore.text=`${this.grid_table.rows.length}/${this.n_records}`}}e.SQLiteDBBrowser=i,i.dependencies=["pkg://SQLiteDB/libsqlite.js"];class a extends t.GUI.BasicDialog{constructor(){super("NewTableDialog")}main(){super.main(),this.container=this.find("container"),this.find("btnCancel").onbtclick=t=>this.quit(),this.find("btnAdd").onbtclick=t=>this.addField(),$(this.container).css("overflow-y","auto"),this.addField(),this.find("btnOk").onbtclick=t=>{const e=this.find("txt-tblname");if(!e.value||""==e.value)return this.notify(__("Please enter table name"));const i=e.value,a=$("input",this.container),s=$("afx-list-view",this.container);if(0==a.length)return this.notify(__("Please define table fields"));let n={};for(let t=0;t").css("display","flex").css("flex-direction","row").appendTo(this.container);$("").attr("type","text").css("flex","1").appendTo(t);let e=$("").css("flex","1").appendTo(t)[0];e.uify(this.observable),e.dropdown=!0,e.data=[{text:"TEXT"},{text:"INTEGER"},{text:"REAL"},{text:"NUMERIC"}],e.selected=0;const i=$("");i[0].uify(void 0),i[0].iconclass="fa fa-minus",i.on("click",()=>{t.remove()}).appendTo(t)}}a.scheme='\n \n \n \n
\n \n \n
\n \n \n
\n
\n
\n
';class s extends t.GUI.BasicDialog{constructor(){super("RecordEditDialog")}main(){if(super.main(),this.container=this.find("container"),this.find("btnCancel").onbtclick=t=>this.quit(),$(this.container).css("overflow-y","auto"),!this.data||!this.data.schema)throw new Error(__("No data provided for dialog").__());for(let t in this.data.schema.types){const e=$("").appendTo(this.container)[0];e.uify(this.observable),e.label=t,t==this.data.schema.pk&&(e.disable=!0),"TEXT"==this.data.schema.types[t]&&(e.verbose=!0,$(e).css("height","100px")),null!=this.data.record[t]&&(e.value=this.data.record[t])}this.find("btnOk").onbtclick=t=>{const e=$("afx-input",this.container),i={};for(let t of e)i[t.label.__()]=t.value;this.handle&&this.handle(i),this.quit()}}}s.scheme='\n \n
\n \n
\n
\n \n \n
\n
\n
\n
'}(e=t.application||(t.application={}))}(OS||(OS={})); \ No newline at end of file +var OS;!function(t){let e;!function(e){class i extends e.BaseApplication{constructor(t){super("SQLiteDBBrowser",t)}menu(){return[{text:"__(File)",nodes:[{text:"__(New database)",dataid:"new",shortcut:"A-N"},{text:"__(Open database)",dataid:"open",shortcut:"A-O"}],onchildselect:t=>{switch(t.data.item.data.dataid){case"new":return this.newFile();case"open":return this.openFile()}}}]}list_tables(){this.filehandle.read().then(t=>{const e=[];for(let i in t)e.push({text:i,name:i,handle:t[i]});this.tbl_list.data=e,e.length>0&&(this.tbl_list.selected=e.length-1)})}async openFile(){try{let t;t=this.args&&this.args.length>0?this.args[0].path.asFileHandle():(await this.openDialog("FileDialog",{title:__("Open file"),mimes:this.meta().mimes})).file.path.asFileHandle(),this.filehandle=("sqlite://"+t.genealogy.join("/")).asFileHandle(),await this.filehandle.onready(),this.list_tables()}catch(t){this.error(__("Unable to open database file: {0}",t.toString()),t)}}async newFile(){try{const e=await this.openDialog("FileDialog",{title:__("Save as"),file:"Untitled.db"});var t=e.file.path.asFileHandle();"file"===e.file.type&&(t=t.parent());const i=`${t.path}/${e.name}`.asFileHandle();this.filehandle=("sqlite://"+i.genealogy.join("/")).asFileHandle(),await this.filehandle.onready(),this.list_tables()}catch(t){this.error(__("Unable to init database file: {0}",t.toString()),t)}}main(){this.filehandle=void 0,this.tbl_list=this.find("tbl-list"),this.grid_table=this.find("tb-browser"),this.grid_scheme=this.find("sch-browser"),this.grid_table.resizable=!0,this.grid_scheme.resizable=!0,this.grid_scheme.header=[{text:__("Field name")},{text:__("Field type")}],this.btn_loadmore=this.find("bt-load-next"),this.container=this.find("container"),this.bindKey("ALT-N",()=>this.newFile()),this.bindKey("ALT-O",()=>this.openFile()),this.container.ontabselect=t=>{if(0==this.container.selectedIndex){if(!this.tbl_list.selectedItem)return;const t=this.tbl_list.selectedItem.data.handle.info.schema;if(!t)return;const e=[];for(let i in t.types)e.push([{text:i},{text:t.types[i]}]);this.grid_scheme.rows=e}},this.find("bt-rm-table").onbtclick=async t=>{try{if(!this.filehandle)return this.notify(__("Please open a database file"));if(null==this.tbl_list.selectedItem)return;const t=this.tbl_list.selectedItem.data.name;await this.openDialog("YesNoDialog",{title:__("Confirm delete?"),text:__("Do you realy want to delete table: {0}",t)})&&(await this.filehandle.remove(t),this.list_tables())}catch(t){this.error(__("Unable to execute action table delete: {0}",t.toString()),t)}},this.find("bt-add-table").onbtclick=async t=>{try{if(!this.filehandle)return this.notify(__("Please open a database file"));const t=await this.openDialog(new a,{title:__("Create new table")});this.filehandle.cache=t.schema,await this.filehandle.write(t.name),this.list_tables()}catch(t){this.error(__("Unable to create table: {0}",t.toString()),t)}},this.find("btn-edit-record").onbtclick=async t=>{this.edit_record()},this.find("btn-add-record").onbtclick=async t=>{this.add_record()},this.find("btn-delete-record").onbtclick=async t=>{this.remove_record()},this.btn_loadmore.onbtclick=async t=>{try{await this.load_table()}catch(t){this.error(__("Error reading table: {0}",t.toString()),t)}},this.find("bt-refresh").onbtclick=async t=>{try{this.last_max_id=0;const t=this.tbl_list.selectedItem.data.handle,e=await t.read({fields:["COUNT(*)"]});this.n_records=e[0]["COUNT(*)"],this.grid_table.rows=[],await this.load_table()}catch(t){this.error(__("Error reload table: {0}",t.toString()),t)}},this.tbl_list.onlistselect=async t=>{try{if(!this.tbl_list.selectedItem)return;const t=this.tbl_list.selectedItem.data.handle;await t.onready(),this.last_max_id=0;const e=t.info.schema.fields.map(t=>({text:t}));this.grid_table.header=e,this.grid_table.rows=[];const i=await t.read({fields:["COUNT(*)"]});this.n_records=i[0]["COUNT(*)"],this.btn_loadmore.text="0/"+this.n_records,await this.load_table(),this.container.selectedIndex=1}catch(t){this.error(__("Error reading table: {0}",t.toString()),t)}},this.grid_table.oncelldbclick=async t=>{this.edit_record()},this.openFile()}async add_record(){try{const t=this.tbl_list.selectedItem;if(!t)return;const e=t.data.handle,i=t.data.handle.info.schema,a={};for(let t in i.types)["INTEGER","REAL","NUMERIC"].includes(i.types[t])?a[t]=0:a[t]="";console.log(a);const n=await this.openDialog(new s,{title:__("New record"),schema:i,record:a});e.cache=n,await e.write(void 0),this.n_records+=1,await this.load_table()}catch(t){this.error(__("Error edit/view record: {0}",t.toString()),t)}}async remove_record(){try{const t=this.grid_table.selectedCell,e=this.grid_table.selectedRow,i=this.tbl_list.selectedItem;if(!t||!i)return;const a=t.data.record[i.data.handle.info.schema.pk];if(!await this.openDialog("YesNoDialog",{title:__("Delete record"),text:__("Do you realy want to delete record {0}",a)}))return;const s=`${i.data.handle.path}@${a}`.asFileHandle();await s.remove(),this.n_records--,this.grid_table.delete(e),this.btn_loadmore.text=`${this.grid_table.rows.length}/${this.n_records}`}catch(t){this.error(__("Error deleting record: {0}",t.toString()),t)}}async edit_record(){try{const t=this.grid_table.selectedCell,e=this.grid_table.selectedRow,i=this.tbl_list.selectedItem;if(!t||!i)return;const a=await this.openDialog(new s,{title:__("View/edit record"),schema:i.data.handle.info.schema,record:t.data.record}),n=t.data.record[i.data.handle.info.schema.pk],l=`${i.data.handle.path}@${n}`.asFileHandle();l.cache=a,await l.write(void 0);const d=[];for(let t of l.info.schema.fields){let e=a[t];e.length>100&&(e=e.substring(0,100)),d.push({text:e,record:a})}e.data=d}catch(t){this.error(__("Error edit/view record: {0}",t.toString()),t)}}async load_table(){if(this.grid_table.rows&&this.grid_table.rows.length>=this.n_records)return;if(!this.tbl_list.selectedItem)return;const t=this.tbl_list.selectedItem.data.handle;await t.onready();const e=t.info.schema.fields.map(t=>({text:t})),i={where:{},limit:10};i.where[t.info.schema.pk+"$gt"]=this.last_max_id;const a=await t.read(i);if(a&&a.length>0){for(let t of a){const i=[];t.id&&t.id>this.last_max_id&&(this.last_max_id=t.id);for(let a in e){let s=t[e[a].text];s&&s.length>100&&(s=s.substring(0,100)),i.push({text:s,record:t})}this.grid_table.push(i,!1)}this.grid_table.scroll_to_bottom()}this.btn_loadmore.text=`${this.grid_table.rows.length}/${this.n_records}`}}e.SQLiteDBBrowser=i,i.dependencies=["pkg://SQLiteDB/libsqlite.js"];class a extends t.GUI.BasicDialog{constructor(){super("NewTableDialog")}main(){super.main(),this.container=this.find("container"),this.find("btnCancel").onbtclick=t=>this.quit(),this.find("btnAdd").onbtclick=t=>this.addField(),$(this.container).css("overflow-y","auto"),this.addField(),this.find("btnOk").onbtclick=t=>{const e=this.find("txt-tblname");if(!e.value||""==e.value)return this.notify(__("Please enter table name"));const i=e.value,a=$("input",this.container),s=$("afx-list-view",this.container);if(0==a.length)return this.notify(__("Please define table fields"));let n={};for(let t=0;t").css("display","flex").css("flex-direction","row").appendTo(this.container);$("").attr("type","text").css("flex","1").appendTo(t);let e=$("").css("flex","1").appendTo(t)[0];e.uify(this.observable),e.dropdown=!0,e.data=[{text:"TEXT"},{text:"INTEGER"},{text:"REAL"},{text:"NUMERIC"}],e.selected=0;const i=$("");i[0].uify(void 0),i[0].iconclass="fa fa-minus",i.on("click",()=>{t.remove()}).appendTo(t)}}a.scheme='\n \n \n \n
\n \n \n
\n \n \n
\n
\n
\n
';class s extends t.GUI.BasicDialog{constructor(){super("RecordEditDialog")}main(){if(super.main(),this.container=this.find("container"),this.find("btnCancel").onbtclick=t=>this.quit(),$(this.container).css("overflow-y","auto"),!this.data||!this.data.schema)throw new Error(__("No data provided for dialog").__());for(let t in this.data.schema.types){const e=$("").appendTo(this.container)[0];e.uify(this.observable),e.label=t,t==this.data.schema.pk&&(e.disable=!0),"TEXT"==this.data.schema.types[t]&&(e.verbose=!0,$(e).css("height","100px")),null!=this.data.record[t]&&(e.value=this.data.record[t])}this.find("btnOk").onbtclick=t=>{const e=$("afx-input",this.container),i={};for(let t of e)i[t.label.__()]=t.value;this.handle&&this.handle(i),this.quit()}}}s.scheme='\n \n
\n \n
\n
\n \n \n
\n
\n
\n
'}(e=t.application||(t.application={}))}(OS||(OS={})); \ No newline at end of file diff --git a/SQLiteDB/build/debug/scheme.html b/SQLiteDB/build/debug/scheme.html index 5e8c268..1fd5902 100644 --- a/SQLiteDB/build/debug/scheme.html +++ b/SQLiteDB/build/debug/scheme.html @@ -16,6 +16,7 @@
+
diff --git a/SQLiteDB/build/release/SQLiteDB.zip b/SQLiteDB/build/release/SQLiteDB.zip index 5a3e81e4b0e9d04733e03be665a487308c78a169..30bc339fea1b37ff28e1a5794ab0790033d9e6f2 100644 GIT binary patch delta 5254 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 delta 5072 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 diff --git a/SQLiteDB/main.ts b/SQLiteDB/main.ts index 74b53d0..b61619f 100644 --- a/SQLiteDB/main.ts +++ b/SQLiteDB/main.ts @@ -235,6 +235,23 @@ namespace OS { 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 (_) => { try { @@ -406,7 +423,7 @@ namespace OS { for(let v in headers) { let text:string = e[headers[v].text]; - if(text.length > 100) + if(text && text.length > 100) { text = text.substring(0,100); } diff --git a/SQLiteDB/scheme.html b/SQLiteDB/scheme.html index 5e8c268..1fd5902 100644 --- a/SQLiteDB/scheme.html +++ b/SQLiteDB/scheme.html @@ -16,6 +16,7 @@
+
diff --git a/packages.json b/packages.json index 0b1fc65..f3fa40d 100644 --- a/packages.json +++ b/packages.json @@ -95,8 +95,8 @@ "description": "https://raw.githubusercontent.com/lxsang/antosdk-apps/2.0.x/Blogger/README.md", "category": "Internet", "author": "Xuan Sang LE", - "version": "0.2.9-a", - "dependencies": ["SimpleMDE@2.18.0-r","Katex@0.11.1-r"],"mimes":["none"], + "version": "0.2.10-a", + "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" }, { @@ -409,6 +409,16 @@ "dependencies": [], "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", "name": "System monitoring",