From a76942f2f37fd0bf7caaeece94e4989169146eca Mon Sep 17 00:00:00 2001 From: DanyLE Date: Wed, 26 Apr 2023 18:51:03 +0200 Subject: [PATCH] WIP: make code compatible with new SILK API --- Makefile | 8 +- blog/ai/cluster.lua | 345 ------- blog/ai/gettext.lua | 29 - blog/ai/stopwords.txt | 151 ---- blog/ai/test.lua | 50 -- blog/assets/afx.css | 1135 ++++++++++++++++++++++++ blog/assets/afx.js | 1 + blog/assets/style.css | 38 +- blog/controllers/PostController.lua | 149 +++- blog/controllers/ServiceController.lua | 4 +- blog/models/AnalyticalModel.lua | 5 +- blog/models/BlogModel.lua | 29 +- blog/router.lua | 32 +- blog/views/default/layout.ls | 35 +- blog/views/default/post/detail.ls | 4 +- blog/views/default/post/posts.ls | 20 +- blog/views/default/post/search.ls | 17 + doc/controllers/IndexController.lua | 3 +- doc/controllers/doccontroller.lua | 10 +- doc/router.lua | 23 +- doc/views/default/index/book.ls | 8 +- doc/views/default/index/search.ls | 4 +- doc/views/default/toc/index.ls | 2 +- get/router.lua | 16 +- grs/sendto.html | 2 +- grs/ubuntu-regular.css | 39 - index.lua | 14 + info/controllers/IndexController.lua | 25 +- info/controllers/UserController.lua | 2 +- info/router.lua | 33 +- info/views/default/index/index.ls | 8 +- info/views/default/layout.ls | 5 +- info/views/default/user/index.ls | 2 +- index.ls => layout.ls | 11 +- mimes.json | 4 + os/Makefile | 6 - os/controllers/IndexController.lua | 33 - os/controllers/SystemController.lua | 257 ------ os/controllers/UserController.lua | 97 -- os/controllers/VDBController.lua | 130 --- os/controllers/VFSController.lua | 222 ----- os/libs/common.lua | 66 -- os/libs/dbmodel.lua | 20 - os/libs/packages.lua | 125 --- os/libs/shared.lua | 47 - os/libs/uman.lua | 27 - os/libs/vfs.lua | 234 ----- os/router.lua | 45 - silk/BaseController.lua | 108 --- silk/BaseModel.lua | 51 -- silk/BaseObject.lua | 34 - silk/DBHelper.lua | 144 --- silk/Logger.lua | 30 - silk/Router.lua | 164 ---- silk/Template.lua | 58 -- silk/api.lua | 57 -- silk/router.lua.tpl | 54 -- talk/assets/quicktalk.js | 20 +- talk/controllers/CommentController.lua | 47 +- talk/router.lua | 33 +- 60 files changed, 1527 insertions(+), 2845 deletions(-) delete mode 100644 blog/ai/cluster.lua delete mode 100644 blog/ai/gettext.lua delete mode 100644 blog/ai/stopwords.txt delete mode 100644 blog/ai/test.lua create mode 100644 blog/assets/afx.css create mode 100644 blog/assets/afx.js create mode 100644 blog/views/default/post/search.ls delete mode 100755 grs/ubuntu-regular.css create mode 100644 index.lua rename index.ls => layout.ls (85%) delete mode 100644 os/Makefile delete mode 100644 os/controllers/IndexController.lua delete mode 100644 os/controllers/SystemController.lua delete mode 100644 os/controllers/UserController.lua delete mode 100644 os/controllers/VDBController.lua delete mode 100644 os/controllers/VFSController.lua delete mode 100644 os/libs/common.lua delete mode 100644 os/libs/dbmodel.lua delete mode 100644 os/libs/packages.lua delete mode 100644 os/libs/shared.lua delete mode 100644 os/libs/uman.lua delete mode 100644 os/libs/vfs.lua delete mode 100644 os/router.lua delete mode 100644 silk/BaseController.lua delete mode 100644 silk/BaseModel.lua delete mode 100644 silk/BaseObject.lua delete mode 100644 silk/DBHelper.lua delete mode 100644 silk/Logger.lua delete mode 100644 silk/Router.lua delete mode 100644 silk/Template.lua delete mode 100644 silk/api.lua delete mode 100644 silk/router.lua.tpl diff --git a/Makefile b/Makefile index 1e807e9..3d00d37 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,13 @@ BUILDDIR?=./build -PROJS?=grs info blog os doc talk get -copyfiles = index.ls mimes.json +PROJS?=grs blog talk get info +# info blog doc talk get +copyfiles = layout.ls index.lua mimes.json main: copy for f in $(PROJS); do BUILDDIR=$(BUILDDIR)/"$${f}" make -C "$${f}" ; done copy: cp -rfv $(copyfiles) $(BUILDDIR) - cp -rv silk $(BUILDDIR) +# cp -rv silk $(BUILDDIR) ar: -[ -d /tmp/antd_web_apps ] && rm -r /tmp/antd_web_apps @@ -19,4 +20,3 @@ ar: clean: -for f in $(PROJS); do rm -r $(BUILDDIR)/"$${f}"; done -for f in $(copyfiles); do rm -r $(BUILDDIR)/"$${f}"; done - -rm -r $(BUILDDIR)/silk diff --git a/blog/ai/cluster.lua b/blog/ai/cluster.lua deleted file mode 100644 index 8e7ac33..0000000 --- a/blog/ai/cluster.lua +++ /dev/null @@ -1,345 +0,0 @@ -local doclassify = {} -local st = require("stmr") -doclassify.bow = function(data, stopwords) - -- first step get a table of worlds that contain - -- world: occurences - local bag = {} - for w in data:gmatch('%w+') do - local word = w:lower() - if not stopwords[word] then - word = st.stmr(word) - if bag[word] then - bag[word].count = bag[word].count + 1 - else - bag[word] = {count=0, tf=0, tfidf=0.0} - bag[word].count = 1 - end - end - end - -- now calculate the tf of the bag - for k,v in pairs(bag) do - bag[k].tf = math.log(1 + bag[k].count) - end - return bag -end -doclassify.len = function(table) - local cnt = 0 - for k,v in pairs(table) do cnt = cnt+1 end - return cnt -end -doclassify.tfidf = function(documents) - -- now for each term in a bag, calculate - -- the inverse document frequency, which - -- is a measure of how much information - -- the word provides, that is, whether the - -- term is common or rare across all documents - local ndoc = doclassify.len(documents) - for k,bag in pairs(documents) do - -- for eacht term in bag - -- calculate its idf across all documents - for term,b in pairs(bag) do - local n = 0 - for id,doc in pairs(documents) do - if doc[term] then n = n+1 end - end - --echo("term:"..term.." appears in"..n.." documents") - b.tfidf = b.tf*math.log(ndoc/n) - end - end - -end - -doclassify.search = function(term, documents) - local r = {} - for id, doc in pairs(documents) do - if doc[term:lower()] then - r[id] = doc[term].tfidf - end - end - return r -end - -doclassify.get_vectors = function(documents) - -- get a list of vector from documents - local index = 0 - local vectors = {} - local maps = {} - local terms = {} - local maxv = 0 - - for id in pairs(documents) do - maps[id] = {} - vectors[id] = {} - end - -- first loop, get the term - for id, doc in pairs(documents) do - for k,v in pairs(doc) do - -- get max value - if v.tfidf > maxv then - maxv = v.tfidf - end - -- get the term - if not terms[k] then - index = index + 1 - terms[k] = index - end - for pid in pairs(documents) do - if not maps[pid][k] then - if id == pid then - maps[pid][k] = v.tfidf - else - maps[pid][k] = 0 - end - else - if maps[pid][k] == 0 and id == pid then - maps[pid][k] = v.tfidf - end - end - end - end - end - -- reindexing the vectors - for id in pairs(documents) do - for k,v in pairs(maps[id]) do - vectors[id][terms[k]] = v - end - end - --echo("Max tfidf "..maxv.." in document #"..maxid.." of term "..term) - return vectors, maxv, index, terms -end - -doclassify.similarity = function(va, vb) - -- using cosin similarity - local dotp = 0 - local maga = 0 - local magb = 0 - for k = 1,#va do - dotp = dotp + va[k]*vb[k] - maga = maga + va[k]*va[k] - magb = magb + vb[k]*vb[k] - end - maga = math.sqrt(maga) - magb = math.sqrt(magb) - local d = 0 - if maga ~= 0 and magb ~= 0 then - d = dotp/ (magb*maga) - end - return d -end -doclassify.similarities = function(v1, collection) - local similarities = {} - assert(#v1 == #(collection[1]), "Incorrect vectors size") - for i=1,#collection do - similarities[i] = doclassify.similarity(v1, collection[i]) - end - return similarities -end - -doclassify.mean_similarity = function(v1, v2) - assert(#v1 == #v2, "Incorrect vectors size") - local similarities = {} - for i = 1,#v1 do similarities[i] = doclassify.similarity(v1[i], v2[i]) end - return doclassify.mean(similarities) -end -doclassify.similarity_chart = function(id, vectors) - local vs = {} - local cnt = 0 - local lut = {} - for k,v in pairs(vectors) do - if k ~= id then - cnt = cnt + 1 - vs[cnt] = v - lut[cnt] = k - end - end - if not vs[1] then return {} end - return doclassify.similarities(vectors[id], vs), lut -end - -doclassify.top_similarity = function(id, vectors, n, th) - local chart,lut = doclassify.similarity_chart(id,vectors) - --echo(JSON.encode(chart)) - --echo(JSON.encode(lut)) - if not lut or #lut <= 0 then return nil end - local top = {} - - local j=0 - local goon = true - if not th then - goon = false - end - - while j < n or goon - do - local i,maxv = doclassify.argmax(chart) - top[lut[i]] = maxv - chart[i] = 0.0 - j=j+1 - if maxv < th and goon then - goon = false - end - end - - --for j=1,n do - -- local i,maxv = doclassify.argmax(chart) - -- top[lut[i]] = maxv - -- chart[i] = 0.0 - --end - return top - -end -doclassify.save_vectors = function(vectors, name) - local f = io.open(name,"w") - if f == nil then return false end - for id, v in pairs(vectors) do - f:write(id) - for i=1,#v do f:write(","..v[i]) end - f:write("\n") - end - f:close() - return true -end -doclassify.save_topchart = function(vectors, name,n) - local f = io.open(name,"w") - if f == nil then return false end - for k,v in pairs(vectors) do - local top = doclassify.top_similarity(k,vectors,n, 0.1) - for a,b in pairs(top) do - f:write(k.." "..a.." "..b.."\n") - end - end - f:close() - return true -end -doclassify.kmean = function(nclass, documents, maxstep, ids) - -- now - local vectors, maxv, size = doclassify.get_vectors(documents) - -- random centroids - local centroids = {} - local old_centroids = {} - local clusters = {} - --for pid in pairs(documents) do clusters[pid] = 0 end - -- add noise to mean_vector - for i = 1,nclass do - if ids == nil then - centroids[i] = doclassify.random(size,math.floor(maxv)) - else - centroids[i] = vectors[ids[i]] - end - old_centroids[i] = doclassify.zeros(size) - end - - -- loop until convergence or maxstep reached - local similarity = doclassify.mean_similarity(centroids, old_centroids) - local step = maxstep - while 1.0-similarity > 1e-9 and step > 0 do - clusters = {} - --echo(JSON.encode(centroids)) - for id,v in pairs(vectors) do - local similarities = doclassify.similarities(v, centroids) - --echo(JSON.encode(similarities)) - local cluster, maxvalue = doclassify.argmax(similarities) - --echo("doc #"..id.." is in clusters #"..cluster.." max value is "..maxvalue) - clusters[id] = cluster - end - -- storing the old centroids - old_centroids = centroids - -- calculate new centroids - local new_centroids = {} - for class in pairs(centroids) do - local cnt = 0 - local cvectors = {} - for id,v in pairs(vectors) do - if clusters[id] == class then - cnt = cnt + 1 - cvectors[cnt] = v - end - end - new_centroids[class] = doclassify.mean_vector(cvectors, size) - end - centroids = new_centroids - --echo(JSON.encode(centroids)) - --echo(JSON.encode(old_centroids)) - similarity = doclassify.mean_similarity(centroids, old_centroids) - echo("step #"..step..", similarity "..similarity) - step = step - 1 - end - local results = {} - for i = 1,nclass do - local list = {} - local cnt = 0 - for id,c in pairs(clusters) do - if c == i then - cnt = cnt + 1 - list[cnt] = id - end - end - results[i] = list - end - return results, clusters, centroids -end - -doclassify.zeros = function(n) - local vector = {} - for i = 1,n do vector[i] = 0.0 end - return vector -end - -doclassify.random = function(n,maxv) - local vector = {} - for i=1,n do - vector[i] = math.random() + math.random(0, maxv) - end - return vector -end - -doclassify.sum = function(v) - local sum = 0.0 - for i=1,#v do sum = sum + v[i] end - return sum -end - -doclassify.mean = function(v) - return doclassify.sum(v)/#v - -end - -doclassify.mean_vector = function(vectors, size) - local means = doclassify.zeros(size) - if not vectors or #vectors == 0 then return means end - --local size = #(vectors[1]) - local times = 0 - for k,v in pairs(vectors) do - for i=1,#v do means[i] = means[i] + v[i] end - times = times + 1 - end - for i = 1,size do means[i] = means[i]/times end - return means -end - -doclassify.argmin = function(v) - local minv = 0.0 - local mini = 0.0 - for i = 1,#v do - if v[i] <= minv then - mini = i - minv = v[i] - end - end - --echo("min index"..mini.." val "..minv) - return mini, minv -end - -doclassify.argmax = function(v) - local maxv = 0.0 - local maxi = 0.0 - for i = 1,#v do - if v[i] >= maxv then - maxi = i - maxv = v[i] - end - end - return maxi,maxv -end - -return doclassify diff --git a/blog/ai/gettext.lua b/blog/ai/gettext.lua deleted file mode 100644 index b0dabbd..0000000 --- a/blog/ai/gettext.lua +++ /dev/null @@ -1,29 +0,0 @@ -local gettext = {} -require("sqlite") -gettext.get = function(q) - local db = require("os.libs.dbmodel").get("mrsang","blogs",nil) - if not db then return nil end - local exp = {["="] =q} - local cond = { - exp = exp, - fields = {"id", "content"} - } - local data, sort = db:find(cond) - db:close() - if not data or #data == 0 then return nil end - --for k,v in pairs(data) do - -- data[k].content = bytes.__tostring(std.b64decode(data[k].content)):gsub("%%","%%%%") - --end - return data -end - -gettext.stopwords = function(ospath) - --local ospath = require("fs/vfs").ospath(path) - local words = {} - for line in io.lines(ospath) do - words[line] = true - end - return words -end - -return gettext \ No newline at end of file diff --git a/blog/ai/stopwords.txt b/blog/ai/stopwords.txt deleted file mode 100644 index abe0cef..0000000 --- a/blog/ai/stopwords.txt +++ /dev/null @@ -1,151 +0,0 @@ -i -me -my -myself -we -our -ours -ourselves -you -your -yours -yourself -yourselves -he -him -his -himself -she -her -hers -herself -it -its -itself -they -them -their -theirs -themselves -what -which -who -whom -this -that -these -those -am -is -are -was -were -be -been -being -have -has -had -having -do -does -did -doing -a -an -the -and -but -if -or -because -as -until -while -of -at -by -for -with -about -against -between -into -through -during -before -after -above -below -to -from -up -down -in -out -on -off -over -under -again -further -then -once -here -there -when -where -why -how -all -any -both -each -few -more -most -other -some -such -no -nor -not -only -own -same -so -than -too -very -s -t -can -will -just -don -should -now -a -b -c -d -e -f -g -h -i -j -k -l -m -n -o -p -q -w -r -s -t -x -y -z \ No newline at end of file diff --git a/blog/ai/test.lua b/blog/ai/test.lua deleted file mode 100644 index 21573a6..0000000 --- a/blog/ai/test.lua +++ /dev/null @@ -1,50 +0,0 @@ -local path = require("fs/vfs").ospath("home://aiws/blog-clustering") -local gettext = loadfile(path.."/gettext.lua")() -local cluster = loadfile(path.."/cluster.lua")() - -local refresh = false - -local file = "/home/mrsang/test.csv" -if refresh then - local data = gettext.get({publish=1}) - local documents = {} - if data then - local sw = gettext.stopwords("home://aiws/blog-clustering/stopwords.txt") - for k,v in pairs(data) do - local bag = cluster.bow(data[k].content, sw) - documents[data[k].id] = bag - end - cluster.tfidf(documents) - --local v = cluster.search("arm", documents) - --echo(JSON.encode(v)) - local vectors, maxv, size = cluster.get_vectors(documents) - local s = cluster.save_topchart(vectors,file, 3) - if s then echo("file saved") else echo("error save file") end - --echo(JSON.encode(r)) - --r = cluster.similarity(vectors["14"],vectors["16"]) - --echo("Similarity "..r) - - --local c,l = cluster.kmean(3, documents, 10) - --echo(JSON.encode(c)) - --echo(JSON.encode(l)) - else - echo("Data missing") - end -else - local f = io.open(file,"r") - local result = {} - for line in f:lines() do - local arr = {} - local cnt = 0 - for i in line:gmatch( "%S+") do - cnt = cnt + 1 - arr[cnt] = i - end - if not result[arr[1]] then result[arr[1]] = {} end - result[arr[1]][arr[2]] = tonumber(arr[3]) - end - f:close() - echo(JSON.encode(result)) - --local r = cluster.top_similarity("2",vectors, 3) - --echo(JSON.encode(r)) -end \ No newline at end of file diff --git a/blog/assets/afx.css b/blog/assets/afx.css new file mode 100644 index 0000000..76def38 --- /dev/null +++ b/blog/assets/afx.css @@ -0,0 +1,1135 @@ +afx-app-window div.afx-window-wrapper{ + padding:0; + display: flex; + flex-direction: column; + width: 100%; + height: 100%; +} + +afx-app-window ul.afx-window-top{ + margin: 0; + padding: 0; + width: 100%; + padding:0; +} +afx-app-window ul.afx-window-top li{ + list-style: none; +} + +afx-app-window ul li.afx-window-title{ + float:none; + overflow: hidden; +} + +afx-app-window div.afx-window-content +{ + overflow: hidden; + width: 100%; + flex-grow: 1; +} +afx-app-window div.afx-window-grip{ + height: 10px; + width: 10px; + background-color: transparent; +} +afx-button button{ + outline: none; +} + +afx-button i.icon-style { + float: left; +} +afx-calendar-view afx-grid-view afx-grid-row:nth-child(even) afx-grid-cell +{ + background-color: transparent; +} +afx-calendar-view afx-grid-view .grid_row_header afx-grid-cell{ + border-right: 0; +} +afx-apps-dock{ + float: left; + position: absolute; + overflow: hidden; +} +afx-file-view { + position: relative; +} +afx-file-view afx-label.status{ + position: absolute; + bottom: 1px; + left:0px; + transform: translateZ(0); +} +afx-file-view afx-list-view > div.list-container > ul li{ + float:left; + display: block; +} + +afx-file-view afx-list-view i{ + display: block; +} + +afx-file-view afx-grid-view i{ + display: inline-block; +} + + +afx-file-view afx-tree-view{ + margin:0; + overflow: hidden; + background-color: transparent; +} + +afx-file-view afx-tree-view .afx_tree_item_odd{ + background-color: transparent; +} + +afx-file-view afx-tree-view div{ + overflow: hidden; + white-space: nowrap; + background-color: transparent; + padding:0; +} + +afx-file-view div.treecontainer{ + display: block; + overflow: auto; + padding:0; + margin:0; +} + +afx-float-list div.list-container > ul{ + padding: 0; + margin: 0; + background-color: transparent; +} + +afx-float-list div.list-container > ul li{ + padding: 0; + margin: 0; + background-color: transparent; + list-style: none; +} +afx-grid-view afx-grid-row afx-grid-cell{ + user-select:none; + -webkit-user-select:none; + cursor:default; +} + +afx-grid-view .grid_row_header afx-grid-cell{ + user-select:none; + -webkit-user-select:none; + cursor:default; + font-weight: bold; +} + +afx-grid-view { + display: block; +} +afx-label i.icon-style { + float: left; +} +afx-label i.label-text{ + font-weight: normal; + font-style: normal; + margin-left: 3px; +} +afx-list-view{ + overflow:hidden; + display: block; +} + +afx-list-view afx-list-item +{ + display: contents; +} +afx-list-view > div.list-container{ + overflow: auto; +} +afx-list-view > div.list-container > ul{ + margin:0; + padding: 0; +} + +afx-list-view > div.list-container > ul li{ + margin:0; + padding:0; + list-style: none; + position: relative; + -webkit-user-select:none; + cursor:default; +} + +afx-list-view i.closable{ + display: inline-block; + position:absolute; + top:0px; + right:2px; + cursor: pointer; +} + +afx-list-view i.closable:before{ + content: "\f00d"; + font-family: "FontAwesome"; + font-style: normal; +} + +afx-list-view.dropdown { + padding:0; + margin: 0; +} +afx-list-view.dropdown > div.list-container{ + overflow: visible; +} +afx-list-view.dropdown > div.list-container > ul{ + overflow-y: auto; + overflow-x: hidden; +} + +afx-list-view.dropdown > div.list-container > ul li{ + display: inline-block; + width:100%; +} + + +afx-menu { + position:relative; + display:inline-block; + z-index: 100000; +} +afx-menu a{ + text-decoration: none; + display: flex; + width: 100%; + height: 100%; + flex-direction: row; +} +afx-menu a afx-label{ + flex:1; +} +afx-menu ul{ + padding:0; + margin: 0; + display:inline-block; +} +afx-menu ul li{ + white-space:nowrap; +} + +afx-menu span.shortcut{ + text-align: right; +} + +afx-menu ul li { + list-style:none; + margin:0; + position: relative; + float: left; + cursor:default; +} +afx-menu ul li.fix_padding{ + padding-top:1px; + padding-bottom: 0; + padding-left: 5px; + padding-right: 5px; +} + +afx-menu afx-menu ul li.fix_padding{ + padding:3px; + padding-left: 5px; + padding-right: 5px; +} +afx-menu afx-menu { + top:100%; + left:0; + position: absolute; + display:none; +} + +afx-menu afx-menu li{ + float:none; + cursor:default; +} +afx-menu afx-menu afx-menu, afx-menu ul.context afx-menu{ + top:-4px; + left: 100%; +} + +afx-menu afx-menu li:hover > afx-menu, ul.context li:hover > afx-menu +{ + display: block; +} +afx-menu li.afx-corner-fix{ + height: 3px; + padding: 0; + margin: 0; + background-color: transparent; +} +afx-menu li.afx-corner-fix:hover{ + background-color: transparent; +} + + + afx-menu ul.context{ + position: absolute; + z-index: 1000000; + padding: 0; + } + afx-menu ul.context li{ + clear:float; + } +afx-nspinner{ + display: flex; + flex-direction: row; +} +afx-nspinner ul{ + padding:0; + margin: 0; + list-style: none; +} +afx-nspinner input{ + margin: 0; +} +afx-nspinner ul li{ + display: block; + padding:0; + margin: 0; +} + +afx-nspinner ul li.incr i:before{ + content: "\f0d8"; + font-family: "FontAwesome"; + font-style: normal; + +} +afx-nspinner ul li.decr i:before{ + content: "\f0d7"; + font-family: "FontAwesome"; + font-style: normal; + +} +afx-slider{ + display: flex; + align-items: center; + justify-content: center; +} +afx-slider div.container{ + display: block; + border:0; + position: relative; + padding:0; + cursor: pointer; +} + +afx-slider div.progress { + padding:0; + margin: 0; + display: block; + border:0; +} + +afx-slider div.dragpoint { + display: block; +} + +afx-switch span{ + display: inline-block; + cursor: pointer; +} + +afx-sys-panel{ + padding:0; + margin: 0; +} +afx-sys-panel > div{ + width: 100%; + margin:0; + padding: 0; + position:absolute; + } +afx-sys-panel .afx-panel-os-menu { + padding:0; + margin: 0; + float:left; + margin-right: 5px; +} + +afx-sys-panel .afx-panel-os-stray{ + float:right; + position: relative; +} + +afx-sys-panel afx-menu.afx-panel-os-stray afx-menu { + right: 0; + position: absolute; +} + +afx-sys-panel afx-menu.afx-panel-os-stray afx-menu afx-menu{ + left: -100%; + right: 100%; + top:-4px; +} + +afx-sys-panel afx-overlay +{ + overflow-y: auto; + overflow-x: hidden; + margin: 0; +} + +afx-sys-panel afx-hbox[data-id="btlist"] afx-button button +{ + width: 100%; + height: 100%; + border-radius: 0; + border: 0px; +} +afx-tab-bar { + display: block; + width: 100%; + +} +afx-tab-bar afx-list-view { + padding:0; + margin:0; +} +afx-tab-bar afx-list-view > div.list-container > ul afx-list-item:nth-child(even) li +{ + background-color: transparent; +} + + +afx-tab-bar afx-list-view > div.list-container > ul li{ + float:left; +} + +afx-tab-container{ + display: block; +} +afx-tree-view{ + overflow: auto; + display: block; +} +afx-tree-view afx-tree-view{ + padding:0; + overflow: hidden; + display: block; +} +afx-tree-view ul{ + margin:0; + padding:0; +} +afx-tree-view li{ + list-style: none; + margin:0; + padding: 0; +} +afx-tree-view div{ + -webkit-user-select:none; + cursor:default; +} +afx-tree-view i.icon-style { + float:left; + margin-right: 3px; +} + +afx-tree-view .afx-tree-view-folder-open:before{ + content: "\f147"; + font-family: "FontAwesome"; +} +afx-tree-view .afx-tree-view-folder-close:before{ + content: "\f196"; + font-family: "FontAwesome"; +} + +afx-app-window div.afx-window-wrapper{ + border:1px solid #a6a6a6; + box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.65); + border-radius: 5px; + background-color:#dfdfdf; +} +afx-app-window.unactive > div.afx-window-wrapper{ + background-color: #f6f6f6; +} + +afx-app-window ul.afx-window-top{ + height: 20px; + border-bottom: 1px solid #a6a6a6; +} +afx-app-window ul.afx-window-top li{ + margin-left: 3px; + margin-top:4px; + +} +afx-app-window ul.afx-window-top .afx-window-close,.afx-window-minimize,.afx-window-maximize{ + width: 11px; + height: 11px; + border-radius: 10px; +} +afx-app-window ul li.afx-window-close{ + background-color: #Fc605b; + float:left; +} +afx-app-window ul li.afx-window-minimize{ + background-color: #fec041; + float:left; +} +afx-app-window ul li.afx-window-maximize{ + background-color: #35cc4b; + float:left; +} + +afx-app-window ul li.afx-window-title{ + margin-top:1px; + text-align: center; +} + +afx-app-window div.afx-window-content +{ + background-color: white; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; +} +afx-app-window.unactive div.afx-window-content +{ + background-color:white; +} +afx-button button{ + padding: 4px; + border: 1px solid #a6a6a6; + background-color: #f6F6F6; + color: #414339; + border-radius: 6px; + font-family: "Ubuntu"; + +} + +afx-button button[disabled]{ + color: #a6a6a6; +} +afx-button i.icon-style { + width: 16px; + height: 16px; + display: inline-block; +} +afx-button button:active, afx-button button.btactive { + background-color: #2786F3; + color: white; + border: 1px solid #dedede; +} +afx-calendar-view div{ + text-align: center; +} +afx-calendar-view > div { + font-weight: bold; +} + +afx-calendar-view i.prevmonth, afx-calendar-view i.nextmonth{ + display: inline-block; + width: 16px; + height: 16px; + cursor: pointer; +} +afx-calendar-view i.prevmonth{ + margin-right: 20px; +} +afx-calendar-view i.nextmonth{ + margin-left: 20px; +} + +afx-calendar-view i.prevmonth:before{ + content: "\f104"; + font-family: "FontAwesome"; + font-size: 16px; + font-style: normal; + /*position:absolute; + top:25%; + right:5px;*/ +} +afx-calendar-view i.nextmonth:before{ + content: "\f105"; + font-family: "FontAwesome"; + font-size: 16px; + font-style: normal; + margin-left: 20px; + /*position:absolute; + top:25%; + right:5px;*/ +} + +/* +afx-calendar-view afx-grid-view afx-grid-row.selected{ + background-color: white; + color:#414339; +}*/ + +afx-calendar-view afx-grid-view afx-grid-row.afx-grid-row-selected afx-grid-cell +{ + background-color: transparent; + color:black; +} +afx-calendar-view afx-grid-view afx-grid-row.afx-grid-row-selected afx-grid-cell.afx-grid-cell-selected +{ + background-color: #116cd6; + color:white; + border-radius: 6px; +} +afx-color-picker canvas.color-palette, afx-color-picker div.color-sample{ + border: 1px solid #a6a6a6; + /*border-radius: 3px;*/ +} +afx-apps-dock{ + bottom: 0; + top: 0; + width: 32px; + background-color:#e7e7e7; + padding:0; + padding-top: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border:1px solid #a6a6a6; + box-shadow: none; +} +afx-apps-dock afx-button button{ + width: 32px; + height: 32px; + font-size: 19px; + margin-bottom: 0; + padding:0px; + background-color: transparent; + border:0; + border-radius: 0; +} +afx-apps-dock afx-button afx-label i.icon-style{ + width: 24px; + height: 24px; + margin-left: 2px; + margin-bottom: 0px; + border:0; +} + +afx-apps-dock afx-button.selected > button { + background-color: #2786F3; + color: white; + border: 1px solid #dedede; +} +afx-file-view afx-label.status{ + padding:3px; + right: 0px; + height: 15px; + background-color: #f6F6F6; + border-top: 1px solid #cbcbcb; +} +afx-file-view afx-list-view > div.list-container > ul li{ + width:70px; + height: 60px; + background-color: transparent; + text-align: center; + margin-right: 5px; + margin-bottom: 5px; +} +afx-file-view afx-list-view > div.list-container > ul afx-list-item:nth-child(even) li { + background-color: transparent; +} +afx-file-view afx-list-view i.dir:before{ + content: "\f07b"; + font-family: "FontAwesome"; + font-size: 32px; + color: #76D2F9; + font-weight: normal; + font-style: normal; +} +afx-file-view afx-list-view i{ + width: 100%; + margin-left: 3px; +} + + +afx-file-view afx-list-view i.file:before{ + content: "\f016"; + font-family: "FontAwesome"; + font-size: 28px; + color: #414339; + font-style: normal; + font-weight: normal; +} +afx-file-view afx-list-view > div.list-container > ul > afx-list-item > li.selected +{ + background-color: transparent; +} +afx-file-view afx-list-view div.list-container > ul li.selected i.label-text { + background-color: #116cd6; + color:white; + border-radius: 6px; +} +afx-file-view afx-grid-view{ + padding:0; +} +afx-file-view afx-grid-view i.file:before{ + content: "\f016"; + font-family: "FontAwesome"; + font-size: 16px; + color: #414339; + font-style: normal; + font-weight: normal; +} +afx-file-view afx-grid-view i.dir:before{ + content: "\f07b"; + font-family: "FontAwesome"; + font-size: 16px; + color: #76D2F9; + font-style: normal; + font-weight: normal; +} +afx-file-view afx-grid-view i{ + margin-right: 5px; +} +afx-file-view afx-grid-view afx-grid-row.afx-grid-row-selected i:before{ + color:white; +} +afx-file-view afx-grid-view afx-grid-row afx-grid-cell +{ + padding: 3px; +} + +afx-file-view afx-grid-view .grid_row_header afx-grid-cell{ + background-color: #dfdfdf; + border-top: 1px solid #a6a6a6; + border-right: 1px solid #a6a6a6; + border-bottom: 1px solid #a6a6a6; + padding: 3px; +} + +afx-file-view afx-tree-view .afx-tree-view-folder-close:before{ + content: "\f07b"; + font-family: "FontAwesome"; + font-size: 16px; + color:#76D2F9; +} +afx-file-view afx-tree-view .afx-tree-view-folder-open:before{ + content: "\f07c"; + font-family: "FontAwesome"; + color:#76D2F9; + font-size: 16px; +} + +afx-file-view afx-tree-view .afx-tree-view-item:before{ + content: "\f016"; + font-family: "FontAwesome"; + font-size: 16px; + font-style: normal; + font-weight: normal; +} + +afx-file-view afx-tree-view div.afx_tree_item_selected, afx-file-view afx-tree-view div.afx_tree_item_selected:hover{ + background-color: transparent; +} + +afx-file-view afx-tree-view li.itemname{ + padding:3px; + padding-right: 5px; +} +afx-file-view afx-tree-view div.afx_tree_item_selected .itemname{ + background-color: #116cd6; + color:white; + border-radius: 3px; + +} +afx-file-view afx-tree-view div.afx_tree_item_selected i.file:before{ + color:white; +} + +afx-file-view afx-tree-view .afx_folder_item{ + font-weight: normal; +} +afx-grid-view afx-grid-row:nth-child(even) afx-grid-cell +{ + background-color: #f5F5F5; +} + +afx-grid-view afx-grid-row.afx-grid-row-selected afx-grid-cell +{ + background-color: #116cd6; + color:white; +} +afx-grid-view afx-grid-row.afx-grid-row-selected afx-grid-cell.afx-grid-cell-selected +{ + font-weight: bold; +} + + +afx-grid-view .grid_row_header afx-grid-cell{ + border: 0; +} + +afx-label i.icon-style { + width: 16px; + height: 16px; +} +afx-list-view > div.list-container > ul li{ + padding: 5px; + padding-top:3px; + padding-bottom: 3px; + padding-right: 10px; + background-color: white; +} +afx-list-view > div.list-container > ul afx-list-item:nth-child(even) li{ + background-color:#f5F5F5; +} +afx-list-view i.closable{ + width: 16px; + height: 16px; +} + +afx-list-view i.closable:before{ + font-size: 10px; + margin-left: 10px; + color: #414339; +} + +afx-list-view > div.list-container > ul li > i { + margin-right: 3px; + +} +afx-list-view > div.list-container > ul > afx-list-item > li.selected{ + background-color: #116cd6; + color:white; +} + +afx-list-view.dropdown > div.list-container > ul{ + border:1px solid #a6a6a6; + box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.65); + border-radius: 3px; + max-height: 150px; + background-color: white; + border-top-left-radius: 0px; + z-index: 10; +} + +afx-list-view.dropdown div.list-container div{ + color: #414339; + padding-top:3px; + padding-bottom: 3px; + border:1px solid #a6a6a6; + border-radius: 3px; + background-color: transparent; + height: 17px; +} +afx-list-view.dropdown div.list-container div > afx-label{ + padding-left:3px; +} +afx-list-view.dropdown div.list-container div:before { + content: "\f107"; + font-family: "FontAwesome"; + font-size: 11px; + font-style: normal; + color: #414339; + position: absolute; + top:25%; + right: 5px; + } +afx-list-view.dropdown > div.list-container > ul li:hover{ + background-color: #dcdcdc; + color: #414339; +} +afx-list-view ul.complex-content{ + padding: 0; + margin: 0; + background-color: transparent; +} +afx-list-view ul.complex-content li{ + padding:0; + background-color: transparent; + color:#5e5f59; + list-style: none; +} +afx-list-view > div.list-container > ul li.selected ul.complex-content li{ + color:white; +} + +afx-list-view div.button_container afx-button{ + margin-right: 2px; +} +afx-list-view div.button_container afx-button button{ + border-radius: 0; + padding-left:5px; + padding-top:1px; + padding-bottom: 1px; + padding-right: 5px; + +} + +afx-menu afx-switch span{ + width: 20px; + height: 16px; + font-size: 16px; + /*margin-top:5px;*/ +} +afx-menu span.shortcut{ + text-align: right; +} +afx-menu li:hover > a afx-switch span:before{ + color:white; +} + +afx-menu afx-menu ul { + padding: 0; + border:1px solid #a6a6a6; + border-radius: 5px; + border-top-left-radius: 0px; + /*box-shadow: 2px 2px 2px #cbcbcb;*/ + box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.65); + background-color: #e7e7e7; +} +afx-menu ul li /*, afx-menu ul >afx-menu-entry > li*/{ + padding:3px; + padding-left: 5px; + padding-right: 5px; +} +afx-menu afx-menu li{ + min-width: 150px; +} + +afx-menu li:hover { + background-color: #2786F3; +} +afx-menu li:hover > a { + color: white; +} + +afx-menu afx-menu .afx_submenu:before, afx-menu ul.context .afx_submenu:before{ + content: "\f054"; + font-family: "FontAwesome"; + font-size: 10px; + right:5px; + color: #414339; + position:absolute; + top:25%; + } + + afx-menu ul.context{ + border:1px solid #a6a6a6; + border-radius: 5px; + border-top-left-radius: 0px; + box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.65); + background-color: #e7e7e7; + } + afx-menu ul.context li{ + min-width: 150px; + } +afx-nspinner ul li{ + border: 1px solid #a6a6a6; + width: 100%; +} + +afx-nspinner ul li.incr{ + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} + + +afx-nspinner ul li.decr{ + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; +} + +afx-nspinner ul li:hover{ + color:#116cD6; +} +afx-nspinner ul li.incr i:before{ + font-size: 16px; +} +afx-nspinner ul li.decr i:before{ + font-size: 16px; +} +afx-overlay { + background-color: rgba(231, 231, 231, 0.7); +} +afx-resizer.vertical { + background-color: transparent; + border-top: 1px solid #cbcbcb; +} +afx-resizer.horizontal { + background-color: transparent; + border-left: 1px solid #cbcbcb; +} + +afx-slider div.container{ + border-radius: 3px; + height: 5px; + background-color: #e6e6e6; +} + +afx-slider div.progress { + background-color: #116cd6; + border-radius: 3px; +} + +afx-slider div.dragpoint { + width: 15px; + height: 15px; + border:1px solid #6b6b6b; + border-radius: 15px; + background-color:#e6e6e6; +} + +afx-switch span{ + width: 30px; + height:24px; + font-size: 24px; + font-family: "FontAwesome"; +} +afx-switch span:before{ + content: "\f204"; + color: #414339; + font-style: normal; + font-weight: normal; +} +afx-switch span.swon:before{ + content: "\f205"; + color: #116cd6; + font-style: normal; + font-weight: normal; +} +afx-sys-panel > div{ + background-color: #e7e7e7; + border-bottom: 1px solid #9c9C9C; + box-shadow:none; + height: 22px; + } + +afx-sys-panel .afx-panel-os-menu li +{ + font-weight: bold; + background-color: #e7414d; + border-top-right-radius: 9px; + border-bottom-right-radius: 9px; +} +afx-sys-panel .afx-panel-os-menu a { + color: white; +} + +afx-sys-panel afx-menu.afx-panel-os-stray afx-menu { + left: calc(100% - 170px); +} + +afx-sys-panel afx-menu.afx-panel-os-stray afx-menu li.afx_submenu a{ + margin-left: 10px; +} + +afx-sys-panel afx-menu.afx-panel-os-stray afx-menu li.afx_submenu:before { + content: "\f054"; + font-family: "FontAwesome"; + font-size: 10px; + position:absolute; + text-align: left; + left:5px; + top:25%; + } +afx-sys-panel afx-menu.afx-panel-os-stray afx-menu ul{ + border:1px solid #a6a6a6; + border-radius: 5px; + border-top-right-radius: 0px; +} +afx-sys-panel afx-menu.afx-panel-os-stray afx-menu li{ + min-width: 150px; +} + +afx-sys-panel afx-overlay +{ + background-color: #e7e7e7; + border: 1px solid #9c9C9C; + width: 250px; +} +afx-sys-panel afx-list-view[data-id="applist"] +{ + border-top: 1px solid #afafaf; + border-bottom: 1px solid #afafaf; +} + +afx-sys-panel afx-list-view[data-id="applist"] > div.list-container > ul li +{ + padding-top: 5px; + padding-bottom: 5px; + background-color: transparent; +} + +afx-sys-panel afx-hbox[data-id="btlist"] afx-button button +{ + border: 0; + border-left: 1px solid #afafaf; +} + +afx-sys-panel afx-list-view[data-id="applist"] > div.list-container > ul li:hover{ + background-color: #cecece; +} +afx-sys-panel afx-list-view[data-id="applist"] > div.list-container > ul li.selected +{ + background-color: #116cd6; + color:white; +} + +afx-sys-panel afx-list-view[data-id="applist"] afx-label.search-header { + font-weight: bold; + +} +afx-sys-panel afx-list-view[data-id="applist"] afx-label i, +afx-sys-panel afx-list-view[data-id="applist"] afx-label i::before { + margin-right: 10px; +} + +afx-sys-panel div[data-id="searchicon"]:before{ + content: "\f002"; + display: block; + background-color:transparent; + color:#afafaf; + font-family: "FontAwesome"; + padding-left:3px; + font-size: 25px; +} +afx-sys-panel input{ + border:0; + height: 25px; + color:#afafaf; + background-color: transparent; +} + +afx-tab-bar afx-list-view > div.list-container > ul > afx-list-item > li.selected +{ + background-color: #116cd6; + color:white; +} + +afx-tab-bar afx-list-view > div.list-container > ul li{ + border-top-left-radius: 5px; + border-top-right-radius: 5px; + padding-bottom: 2px; + padding-right:15px; + padding-top:2px; + border:1px solid #c3c3c3; +} +afx-tree-view div{ + padding:3px; +} + +afx-tree-view i.icon-style { + width: 16px; + height: 16px; +} +afx-tree-view div.afx_tree_item_selected{ + background-color: #116cd6; + color:white; +} +afx-tree-view div.afx_tree_item_selected:hover{ + background-color: #116cd6; + color:white; +} + +afx-tree-view .afx_folder_item{ + font-weight: bold; +} +/* +afx-tree-view .afx_tree_item_odd{ + background-color: #f5F5F5; +} +*/ diff --git a/blog/assets/afx.js b/blog/assets/afx.js new file mode 100644 index 0000000..2af9281 --- /dev/null +++ b/blog/assets/afx.js @@ -0,0 +1 @@ +var OS;!function(t){let e;!function(t){t.zindex=10;class e extends HTMLElement{constructor(){super(),this.observable||(this.observable=new Ant.OS.API.Announcer),this._mounted=!1,this.refs={}}set(t){for(let e in t){let s=this.descriptor_of(e);s&&s.set&&(this[e]=t[e])}}set tooltip(t){t&&$(this).attr("tooltip",t)}descriptor_of(t){let e,s=this;do{e=Object.getOwnPropertyDescriptor(s,t)}while(!e&&(s=Object.getPrototypeOf(s)));return e}set aid(t){$(this).attr("data-id",t)}get aid(){return $(this).attr("data-id")}sync(){this._mounted||(this._mounted=!0,this.mount(),super.sync())}afxml(t){t&&(this.observable=t),this.aid||(this.aid=(Math.floor(1e5*Math.random())+1).toString());const e=$(this).children();for(let t of this.layout()){const e=this.mkui(t);e&&$(e).appendTo(this)}if(this.refs.yield)for(let t of e)$(t).detach().appendTo(this.refs.yield);const s={};for(let t=0;t`);if(t.class&&$(e).addClass(t.class),t.id&&$(e).attr("data-id",t.id),t.height&&$(e).attr("data-height",t.height),t.width&&$(e).attr("data-width",t.width),t.tooltip&&$(e).attr("tooltip",t.tooltip.__()),t.children)for(let s of t.children)$(this.mkui(s)).appendTo(e);return t.ref&&(this.refs[t.ref]=e[0]),e[0]}attsw(t,e,s){t?this.atton(e,s):this.attoff(e,s)}atton(t,e){$(e||this).attr(t,"")}attoff(t,e){(e||this).removeAttribute(t)}hasattr(t,e){return(e||this).hasAttribute(t)}}let s;t.AFXTag=e,HTMLElement.prototype.update=function(t){$(this).children().each((function(){if(this.update)return this.update(t)}))},HTMLElement.prototype.sync=function(){$(this).children().each((function(){return this.sync()}))},HTMLElement.prototype.afxml=function(t){$(this).children().each((function(){return this.afxml(t)}))},HTMLElement.prototype.uify=function(t,e){this.afxml(t),this.sync(),t&&e&&t.trigger("mounted",this.aid)},function(t){t.define=function(t,e){try{customElements.define(t,e)}catch(s){const i=customElements.get(t);if(e){const t=Object.getOwnPropertyNames(e.prototype);for(let s of t)i.prototype[s]=e.prototype[s];return}throw s}}}(s=t.tag||(t.tag={}))}(e=t.GUI||(t.GUI={}))}(OS||(OS={})),function(t){let e;!function(t){let e;!function(e){class s extends t.AFXTag{constructor(){super()}init(){this._shown=!1,this._isMaxi=!1,this._history={},this.desktop=t.workspace,this._desktop_pos=$(this.desktop).offset(),this.minimizable=!0,this.resizable=!0,this.apptitle="Untitled"}calibrate(){}reload(t){}set width(t){this._width=t,t&&this.setsize({w:t,h:this.height})}get width(){return this._width}set height(t){this._height=t,t&&this.setsize({w:this.width,h:t})}get height(){return this._height}set minimizable(t){this.attsw(t,"minimizable"),t?$(this.refs.minbt).show():$(this.refs.minbt).hide()}get minimizable(){return this.hasattr("minimizable")}set resizable(t){this.attsw(t,"resizable"),t?($(this.refs.maxbt).show(),$(this.refs.grip).show()):($(this.refs.maxbt).hide(),$(this.refs.grip).hide())}get resizable(){return this.hasattr("resizable")}set apptitle(t){$(this).attr("apptitle",t.__()),t&&(this.refs.txtTitle.text=t)}get apptitle(){return $(this).attr("apptitle")}resize(){const t=$(this.refs.yield).height()/$(this.refs.yield).children().length;$(this.refs.yield).children().each((function(e){$(this).css("height",t+"px")}))}mount(){this.contextmenuHandle=function(t){},$(this.refs.minbt).click(t=>this.observable.trigger("hide",{id:this.aid})),$(this.refs.maxbt).click(t=>this.toggle_window()),$(this.refs.closebt).click(t=>this.observable.trigger("exit",{id:this.aid}));const t=($(this.desktop).width()-this.width)/2,e=($(this.desktop).height()-this.height)/2;return $(this).css("position","absolute").css("left",t+"px").css("top",e+"px").css("z-index",Ant.OS.GUI.zindex++),$(this).on("mousedown",t=>{if(!this._shown)return this.observable.trigger("focus",{id:this.aid})}),$(this.refs.dragger).dblclick(t=>this.toggle_window()),this.observable.on("resize",t=>this.resize()),this.observable.on("focus",()=>{Ant.OS.GUI.zindex++,$(this).show().css("z-index",Ant.OS.GUI.zindex).removeClass("unactive"),this._shown=!0}),this.observable.on("blur",()=>(this._shown=!1,$(this).addClass("unactive"))),this.observable.on("hide",()=>($(this).hide(),this._shown=!1)),this.observable.on("toggle",()=>this._shown?this.observable.trigger("hide",{id:this.aid}):this.observable.trigger("focus",{id:this.aid})),this.enable_dragging(),this.enable_resize(),this.setsize({w:this.width,h:this.height}),this.observable.trigger("rendered",{id:this.aid})}setsize(t){t&&(this._width=t.w,this._height=t.h,$(this).css("width",t.w+"px").css("height",t.h+"px"),$(this.refs.winwrapper).css("height",t.h+"px"),this.observable.trigger("resize",{id:this.aid,data:t}))}enable_dragging(){$(this.refs.dragger).css("user-select","none").css("cursor","default"),$(this.refs.dragger).on("mousedown",t=>{t.preventDefault();const e=$(this).offset();return e.top=t.clientY-e.top,e.left=t.clientX-e.left,$(window).on("mousemove",t=>{let s,i;if(this._isMaxi){this.toggle_window(),i=0;t.clientX,$(this).width();e.top=10,e.left=$(this).width()/2}else i=t.clientY-e.top-this._desktop_pos.top,s=t.clientX-this._desktop_pos.top-e.left,s=s<0?0:s,i=i<0?0:i;return $(this).css("top",i+"px").css("left",s+"px")}),$(window).on("mouseup",(function(t){return $(window).unbind("mousemove",null),$(window).unbind("mouseup",null)}))})}enable_resize(){$(this.refs.grip).css("user-select","none").css("cursor","default").css("position","absolute").css("bottom","0").css("right","0").css("cursor","nwse-resize"),$(this.refs.grip).on("mousedown",t=>{t.preventDefault();const e={top:0,left:0};e.top=t.clientY,e.left=t.clientX,$(window).on("mousemove",t=>{let s=$(this).width()+t.clientX-e.left,i=$(this).height()+t.clientY-e.top;s=s<100?100:s,i=i<100?100:i,e.top=t.clientY,e.left=t.clientX,this._isMaxi=!1,this.setsize({w:s,h:i})}),$(window).on("mouseup",(function(t){return $(window).unbind("mousemove",null),$(window).unbind("mouseup",null)}))})}toggle_window(){let t,e;this.resizable&&(!1===this._isMaxi?(this._history={top:$(this).css("top"),left:$(this).css("left"),width:$(this).css("width"),height:$(this).css("height")},e=$(this.desktop).width(),t=$(this.desktop).height(),$(this).css("top","0").css("left","0"),this.setsize({w:e,h:t}),this._isMaxi=!0):(this._isMaxi=!1,$(this).css("top",this._history.top).css("left",this._history.left),this.setsize({w:parseInt(this._history.width),h:parseInt(this._history.height)})))}layout(){return[{el:"div",class:"afx-window-wrapper",ref:"winwrapper",children:[{el:"ul",class:"afx-window-top",children:[{el:"li",class:"afx-window-close",ref:"closebt"},{el:"li",class:"afx-window-minimize",ref:"minbt"},{el:"li",class:"afx-window-maximize",ref:"maxbt"},{el:"li",class:"afx-window-title",ref:"dragger",children:[{el:"afx-label",ref:"txtTitle"}]}]},{el:"div",class:"afx-clear"},{el:"div",ref:"yield",class:"afx-window-content"},{el:"div",ref:"grip",class:"afx-window-grip"}]}]}}e.WindowTag=s,e.define("afx-app-window",s)}(e=t.tag||(t.tag={}))}(e=t.GUI||(t.GUI={}))}(OS||(OS={})),function(t){let e;!function(t){let e;!function(e){class s extends t.AFXTag{constructor(){super()}init(){}reload(t){}set name(t){t&&($(this).attr("name",t),$(this.refs.yield).removeClass().addClass(`afx-${t}-container`),this.calibrate())}get name(){return $(this).attr("name")}set dir(t){t&&($(this).attr("dir",t),$(this.refs.yield).css("flex-direction",t),this.calibrate())}get dir(){return $(this).attr("dir")}mount(){return $(this).css("display","block"),$(this.refs.yield).css("display","flex").css("width","100%").css("height","100%"),this.observable.on("resize",t=>this.calibrate()),this.calibrate()}calibrate(){return"row"===this.dir?this.hcalibrate():"column"===this.dir?this.vcalibrate():void 0}hcalibrate(){const t=[];let e=0;const s=$(this).height(),i=$(this).width();$(this.refs.yield).children().each((function(s){$(this).css("height","100%");let r=$(this).attr("data-width"),a=0;r&&"grow"!==r?(a="%"===r[r.length-1]?parseInt(r.slice(0,-1))*i/100:parseInt(r),$(this).css("width",a+"px"),e+=a):($(this).css("flex-grow","1"),t.push(this))}));const r=(i-e)/t.length;return r>0&&$.each(t,(t,e)=>$(e).css("width",r+"px")),this.observable.trigger("hboxchange",{id:this.aid,data:{w:i,h:s}})}vcalibrate(){const t=[];let e=0;const s=$(this).height(),i=$(this).width();$(this.refs.yield).children().each((function(i){let r=0;$(this).css("width","100%");let a=$(this).attr("data-height");a&&"grow"!==a?(r="%"===a[a.length-1]?parseInt(a.slice(0,-1))*s/100:parseInt(a),$(this).css("height",r+"px"),e+=r):($(this).css("flex-grow","1"),t.push(this))}));const r=(s-e)/t.length;return r>0&&$.each(t,(t,e)=>$(e).css("height",r+"px")),this.observable.trigger("vboxchange",{id:this.aid,data:{w:i,h:s}})}layout(){return[{el:"div",ref:"yield"}]}}e.TileLayoutTag=s;class i extends s{constructor(){super()}mount(){super.mount(),this.dir="row",this.name="hbox"}}e.HBoxTag=i;class r extends s{constructor(){super()}mount(){super.mount(),this.dir="column",this.name="vbox"}}e.VBoxTag=r,e.define("afx-tile",s),e.define("afx-hbox",i),e.define("afx-vbox",r)}(e=t.tag||(t.tag={}))}(e=t.GUI||(t.GUI={}))}(OS||(OS={})),function(t){let e;!function(t){let e;!function(e){class s extends t.AFXTag{constructor(){super()}init(){this._resizable_el=void 0,this._parent=$(this).parent().parent()[0],this._minsize=0}reload(t){}set dir(t){let e;$(this).attr("dir",t),$(this).unbind("mousedown",null),"hz"===t?($(this).css("cursor","col-resize"),$(this).addClass("horizontal"),this._resizable_el&&(e=$(this._resizable_el).attr("min-width"),e&&(this._minsize=parseInt(e)))):"ve"===t&&($(this).css("cursor","row-resize"),$(this).addClass("vertical"),this._resizable_el&&(e=$(this._resizable_el).attr("min-height"),e&&(this._minsize=parseInt(e)))),0===this._minsize&&(this._minsize=10),this.make_draggable()}get dir(){return $(this).attr("dir")}get attachnext(){return this.hasattr("attachnext")}set attachnext(t){this.attsw(t,"attachnext")}set onelresize(t){this._onresize=t}get onelresize(){return this._onresize}mount(){$(this).css(" display","block");const t=$(this._parent).prop("tagName");this.attachnext?this._resizable_el=1===$(this).next().length?$(this).next()[0]:void 0:this._resizable_el=1===$(this).prev().length?$(this).prev()[0]:void 0,this.dir="AFX-HBOX"===t?"hz":"AFX-VBOX"===t?"ve":"hz"}make_draggable(){$(this).css("user-select","none"),this.dir&&"none"!=this.dir&&$(this).on("mousedown",t=>(t.preventDefault(),$(window).on("mousemove",t=>{if(this._resizable_el)return"hz"===this.dir?this.horizontalResize(t):"ve"===this.dir?this.verticalResize(t):void 0}),$(window).on("mouseup",(function(t){return $(window).unbind("mousemove",null),$(window).unbind("mouseup",null),$(window).unbind("mouseup",null)}))))}horizontalResize(t){if(!this._resizable_el)return;const e=$(this._resizable_el).offset();let s=0;s=this.attachnext?Math.round(e.left+$(this._resizable_el).width()-t.clientX):Math.round(t.clientX-e.left),s{this.toggle&&(this.selected=!this.selected);const e={id:this.aid,data:t};this._onbtclick(e),this.observable.trigger("btclick",e)})}init(){this.enable=!0,this.toggle=!1,this._onbtclick=t=>{}}calibrate(){}reload(t){}layout(){return[{el:"Button",ref:"button",children:[{el:"afx-label",ref:"label"}]}]}}e.ButtonTag=s,e.define("afx-button",s)}(e=t.tag||(t.tag={}))}(e=t.GUI||(t.GUI={}))}(OS||(OS={})),function(t){let e;!function(t){let e;!function(e){class s extends t.AFXTag{constructor(){super(),this._onselect=this._onctxmenu=this._onclick=this._ondbclick=this._onclose=t=>{}}set closable(t){this.attsw(t,"closable"),t?$(this.refs.btcl).show():$(this.refs.btcl).hide()}get closable(){return this.hasattr("closable")}set onitemselect(t){this._onselect=t}set selected(t){this.attsw(t,"selected"),$(this.refs.item).removeClass(),this._data.selected=t,t&&($(this.refs.item).addClass("selected"),this._onselect({id:this.aid,data:this}))}get selected(){return this.hasattr("selected")}set onctxmenu(t){this._onctxmenu=t}set onitemclick(t){this._onclick=t}set onitemdbclick(t){this._ondbclick=t}set onitemclose(t){this._onclose=t}mount(){$(this.refs.item).attr("dataref","afx-list-item"),$(this.refs.item).contextmenu(t=>{this._onctxmenu({id:this.aid,data:this})}),$(this.refs.item).click(t=>{this._onclick({id:this.aid,data:this})}),$(this.refs.item).dblclick(t=>{this._ondbclick({id:this.aid,data:this})}),$(this.refs.btcl).click(t=>{this._onclose({id:this.aid,data:this}),t.preventDefault(),t.stopPropagation()})}layout(){return[{el:"li",ref:"item",children:[this.itemlayout(),{el:"i",class:"closable",ref:"btcl"}]}]}set data(t){this._data=t,this.ondatachange()}get data(){return this._data}}e.ListViewItemTag=s;class i extends s{constructor(){super()}init(){this.closable=!1,this.data={}}calibrate(){}ondatachange(){const t=this.data;if(!t)return;this.refs.label.set(t),t.selected&&(this.selected=t.selected),t.closable&&(this.closable=t.closable)}reload(){this.data=this.data}itemlayout(){return{el:"afx-label",ref:"label"}}}e.SimpleListItemTag=i;class r extends t.AFXTag{constructor(){super(),this._onlistdbclick=this._onlistselect=this._ondragndrop=t=>{},this._onitemclose=t=>!0,this._onmousedown=this._onmouseup=this._onmousemove=t=>{},this._selectedItems=[],this._selectedItem=void 0}init(){this.data=[],this.multiselect=!1,this.dropdown=!1,this.selected=-1,this.dragndrop=!1,$(this).css("display","flex").css("flex-direction","column"),this.itemtag="afx-list-item"}reload(t){}set dropdown(t){this.attsw(t,"dropdown"),$(this.refs.container).removeAttr("style"),$(this.refs.mlist).removeAttr("style"),$(this.refs.container).css("flex",1),$(this).removeClass("dropdown");const e=t=>this.dropoff(t),s=t=>this.showlist(t);t?($(this).addClass("dropdown"),$(this.refs.current).show(),$(document).on("click",e),$(this.refs.current).on("click",s),$(this.refs.container).css("position","absolute").css("display","inline-block"),$(this.refs.mlist).css("position","absolute").css("display","none").css("top","100%").css("left","0"),this.calibrate()):($(this.refs.current).hide(),$(document).off("click",e),$(this.refs.current).off("click",s))}set ondragndrop(t){this._ondragndrop=t}set onlistselect(t){this._onlistselect=t}set onlistdbclick(t){this._onlistdbclick=t}set onitemclose(t){this._onitemclose=t}get dropdown(){return this.hasAttribute("dropdown")}set itemtag(t){$(this).attr("itemtag",t)}get itemtag(){return $(this).attr("itemtag")}set multiselect(t){this.attsw(t,"multiselect")}get multiselect(){return!this.dropdown&&this.hasattr("multiselect")}set dragndrop(t){this.attsw(t,"dragndrop")}get dragndrop(){return this.hasattr("dragndrop")}set buttons(t){if(!this.dropdown&&t&&t.length>0){$(this.refs.btlist).empty();for(let e of t){$(this.refs.btlist).show();const t=$("").appendTo(this.refs.btlist);t[0].uify(this.observable),t[0].set(e)}}}get data(){return this._data}set data(t){this._data=t,this._selectedItem=void 0,this._selectedItems=[],$(this.refs.mlist).empty();for(let e of t)this.push(e,!1);$(this.refs.container).off("mousedown",this._onmousedown),this.dragndrop&&!this.dropdown&&$(this.refs.container).on("mousedown",this._onmousedown),this.ondatachange()}ondatachange(){}set selected(t){if(!this.data)return;const e=t=>{if(t<0)return void this.unselect();const e=this.data;if(t>=e.length)return;e[t].domel.selected=!0};if(Array.isArray(t)){if(this.multiselect)for(const s of t)e(s)}else e(t)}get selectedItem(){return this._selectedItem}get selectedItems(){return this._selectedItems}get selected(){return this.multiselect?this.selectedItems.map((function(t){return $(t).index()})):$(this.selectedItem).index()}unshift(t){return this.push(t,!0)}has_data(t){return this.data&&this.data.includes(t)}push(t,e){let s=this.itemtag;t.tag&&(s=t.tag);const i=$(`<${s}>`);e?(this.has_data(t)||this.data.unshift(t),$(this.refs.mlist).prepend(i[0])):(this.has_data(t)||this.data.push(t),i.appendTo(this.refs.mlist)),i[0].uify(this.observable);const r=i[0];return r.onctxmenu=t=>this.iclick(t,!0),r.onitemdbclick=t=>{this.idbclick(t),this.iclick(t,!1)},r.onitemclick=t=>this.iclick(t,!1),r.onitemselect=t=>this.iselect(t),r.onitemclose=t=>this.iclose(t),r.data=t,t.domel=i[0],r}delete(t){const e=t.data,s=this.data;this.selectedItem===t&&(this._selectedItem=void 0);const i=this.selectedItems;i.includes(t)&&i.splice(i.indexOf(t),1),s.includes(e)&&s.splice(s.indexOf(e),1),$(t).remove()}selectNext(){if(this.multiselect)return;const t=this.selectedItem;let e=0;t&&(e=$(t).index()+1),this.selected=e}selectPrev(){if(this.multiselect)return;const t=this.selectedItem;let e=0;t&&(e=$(t).index()-1),this.selected=e}unselect(){for(let t of this.selectedItems)t.selected=!1;this._selectedItems=[],this._selectedItem=void 0}iclick(t,e){if(!t.data)return;const s=this.selectedItems;if(this.multiselect&&s.includes(t.data)&&!e)return s.splice(s.indexOf(t.data),1),void(t.data.selected=!1);t.data.selected=!0}idbclick(t){const e={id:this.aid,data:{item:t.data}};return this._onlistdbclick(e),this.observable.trigger("listdbclick",e)}iselect(t){if(!t.data)return;var e={item:t.data,items:[]};if(this.multiselect){if(this.selectedItems.includes(t.data))return;this._selectedItem=t.data,this.selectedItems.push(t.data),e.items=this.selectedItems}else{if(this.selectedItem===t.data)return;this.selectedItem&&(this.selectedItem.selected=!1),this._selectedItem=t.data,this._selectedItems=[t.data],e.items=[t.data];const s=$(t.data).children()[0],i=$(this.refs.container).offset(),r=$(this.refs.container).scrollTop();$(s).offset().top+$(s).height()>$(this.refs.container).height()+i.top?$(this.refs.container).scrollTop(r+$(this.refs.container).height()-$(s).height()):$(s).offset().top{let e=$(t.target).closest("li[dataref='afx-list-item']");0!==e.length&&(e=e.parent()[0],this._dnd.from=e,this._dnd.to=void 0,$(window).on("mouseup",this._onmouseup),$(window).on("mousemove",this._onmousemove))},this._onmouseup=t=>{$(window).off("mouseup",this._onmouseup),$(window).off("mousemove",this._onmousemove),$("#systooltip").hide();let e=$(t.target).closest("li[dataref='afx-list-item']");0!==e.length&&(e=e.parent()[0],e!==this._dnd.from&&(this._dnd.to=e,this._ondragndrop({id:this.aid,data:this._dnd}),this._dnd={from:void 0,to:void 0}))},this._onmousemove=t=>{if(!t)return;if(!this._dnd.from)return;const e=this._dnd.from.data,s=$("#systooltip"),i=t.clientY+5,r=t.clientX+5;s.show();return s[0].set(e),s.css("top",i+"px").css("left",r+"px")},$(this.refs.btlist).hide(),this.observable.on("resize",t=>this.calibrate()),this.calibrate()}iclose(t){if(!t.data)return;const e={id:this.aid,data:{item:t.data}};return this._onitemclose(e)?(this.observable.trigger("itemclose",e),this.delete(t.data)):void 0}showlist(t){if(!this.dropdown)return;const e=$(Ant.OS.GUI.workspace).height();$(this).offset().top+$(this.refs.mlist).height()>e?$(this.refs.mlist).css("top",`-${$(this.refs.mlist).outerHeight()}px`):$(this.refs.mlist).css("top","100%"),$(this.refs.mlist).show()}dropoff(t){0===$(t.target).closest(this.refs.container).length&&$(this.refs.mlist).hide()}calibrate(){if(!this.dropdown)return;const t=$(this).width()+"px";$(this.refs.container).css("width",t),$(this.refs.current).css("width",t),$(this.refs.mlist).css("width",t)}layout(){return[{el:"div",class:"list-container",ref:"container",children:[{el:"div",ref:"current",children:[{el:"afx-label",ref:"drlabel"}]},{el:"ul",ref:"mlist"}]},{el:"div",class:"button_container",ref:"btlist"}]}}e.ListViewTag=r,e.define("afx-list-view",r),e.define("afx-list-item",i)}(e=t.tag||(t.tag={}))}(e=t.GUI||(t.GUI={}))}(OS||(OS={})),function(t){let e;!function(t){let e;!function(e){class s extends t.AFXTag{set swon(t){this.attsw(t,"swon"),$(this.refs.switch).removeClass(),t&&$(this.refs.switch).addClass("swon")}get swon(){return this.hasattr("swon")}set enable(t){this.attsw(t,"enable")}get enable(){return this.hasattr("enable")}set onswchange(t){this._onchange=t}mount(){$(this.refs.switch).click(t=>this.makechange(t))}makechange(t){if(!this.enable)return;this.swon=!this.swon;const e={id:this.aid,data:this.swon};return this._onchange(e),this.observable.trigger("switch",e)}layout(){return[{el:"span",ref:"switch"}]}init(){this.swon=!1,this.enable=!0,this._onchange=t=>{}}calibrate(){}reload(t){}}e.SwitchTag=s,e.define("afx-switch",s)}(e=t.tag||(t.tag={}))}(e=t.GUI||(t.GUI={}))}(OS||(OS={})),function(t){let e;!function(t){let e;!function(e){class s extends t.AFXTag{constructor(){super(),this._onchange=t=>{}}init(){this._value=0,this.step=1}reload(t){}set onvaluechange(t){this._onchange=t}mount(){$(this.refs.holder).attr("type","text"),$(this.refs.incr).click(t=>{this.value=this.value+this.step}),$(this.refs.decr).click(t=>{this.value=this.value-this.step}),this.observable.on("resize",()=>this.calibrate()),$(this.refs.holder).on("keyup",t=>{if(13===t.keyCode){let t=parseInt(this.refs.holder.value);if(!isNaN(t))return t<0&&(t=this.value),this.value=t}}),this.calibrate()}calibrate(){$(this.refs.holder).css("width",$(this).width()-20+"px"),$(this.refs.holder).css("height",$(this).height()+"px"),$(this.refs.spinner).css("width","20px").css("height",$(this).height()+"px"),$(this.refs.incr).css("height",$(this).height()/2-2+"px").css("position","relative"),$(this.refs.decr).css("height",$(this).height()/2-2+"px").css("position","relative"),$(this.refs.spinner).find("li").css("display","block").css("text-align","center").css("vertical-align","middle"),$(this.refs.spinner).find("i").css("font-size","16px").css("position","absolute");const t=function(t,e){const s=$(t).find("i");s.css(e,($(t).height()-s.height())/2+"px").css("left",($(t).width()-s.width())/2+"px")};t(this.refs.decr,"bottom"),t(this.refs.incr,"top")}set value(t){if(this._value===t||isNaN(t))return;this._value=t,$(this.refs.holder).val(this._value);const e={id:this.aid,data:t};this._onchange(e),this.observable.trigger("nspin",e)}get value(){return this._value}layout(){return[{el:"input",ref:"holder"},{el:"ul",ref:"spinner",children:[{el:"li",class:"incr",ref:"incr",children:[{el:"i"}]},{el:"li",class:"decr",ref:"decr",children:[{el:"i"}]}]}]}}e.NSpinnerTag=s,e.define("afx-nspinner",s)}(e=t.tag||(t.tag={}))}(e=t.GUI||(t.GUI={}))}(OS||(OS={})),function(t){let e;!function(t){let e;!function(e){class s extends t.AFXTag{constructor(){super(),this._onmenuselect=this._onchildselect=t=>{}}init(){this.nodes=void 0}set onmenuselect(t){this._onmenuselect=t}set onchildselect(t){this._onchildselect=t}get onchildselect(){return this._onchildselect}set data(t){this._data=t,this.set(t)}get data(){return this._data}has_nodes(){const t=this.nodes;return t&&t.length>0}is_root(){return!this.parent}layout(){return[{el:"li",ref:"container",children:[{el:"a",ref:"entry",children:this.itemlayout()},{el:"afx-menu",ref:"submenu"}]}]}set nodes(t){if($(this.refs.container).removeClass("afx_submenu"),!(t&&t.length>0))return void $(this.refs.submenu).hide();$(this.refs.container).addClass("afx_submenu"),$(this.refs.submenu).show().attr("style","");const e=this.refs.submenu;e.parent=this,e.root=this.root,e.items=t,this._data.nodes=t,this.is_root()&&$(this.refs.container).mouseleave(t=>$(this.refs.submenu).attr("style",""))}get nodes(){if(this.data&&this.data.nodes)return this.data.nodes}mount(){$(this.refs.entry).click(t=>this.select(t))}submenuoff(){const t=this.parent;if(t)return t.submenuoff();$(this.refs.submenu).attr("style","")}select(t){const e={id:this.aid,data:{item:this,event:t}};t.preventDefault(),this.is_root()&&this.has_nodes()?$(this.refs.submenu).show():this.submenuoff(),this._onmenuselect(e),this.parent&&this.parent.onchildselect(e),this.root&&this.root.onmenuitemselect(e)}}e.MenuEntryTag=s;class i extends s{constructor(){super()}init(){super.init(),this.switch=!1,this.radio=!1,this.checked=!1}calibrate(){}reload(t){}set switch(t){this.attsw(t,"switch"),this.radio||t?$(this.refs.switch).show():$(this.refs.switch).hide()}get switch(){return this.hasattr("switch")}set radio(t){this.attsw(t,"radio"),this.switch||t?$(this.refs.switch).show():$(this.refs.switch).hide()}get radio(){return this.hasattr("radio")}set checked(t){this.attsw(t,"checked"),this.data&&(this.data.checked=t),(this.radio||this.switch)&&(this.refs.switch.swon=t)}get checked(){return this.hasattr("checked")}set icon(t){if($(this.refs.container).removeClass("fix_padding"),!t)return;this.refs.label.icon=t,$(this.refs.container).addClass("fix_padding")}set iconclass(t){if(!t)return;this.refs.label.iconclass=t}set text(t){if(void 0===t)return;this.refs.label.text=t}set shortcut(t){$(this.refs.shortcut).hide(),t&&($(this.refs.shortcut).show(),$(this.refs.shortcut).text(t))}reset_radio(){if(this.has_nodes())for(let t of this.nodes)t.domel.radio&&(t.domel.checked=!1)}mount(){super.mount(),this.refs.switch.enable=!1}select(t){if(this.switch)this.checked=!this.checked;else if(this.radio){const t=this.parent;t&&t.reset_radio(),this.checked=!this.checked}return super.select(t)}itemlayout(){return[{el:"afx-switch",ref:"switch"},{el:"afx-label",ref:"label"},{el:"span",class:"shortcut",ref:"shortcut"}]}}e.SimpleMenuEntryTag=i;class r extends t.AFXTag{constructor(){super()}init(){this.contentag="afx-menu-entry",this.context=!1,this._items=[],this._onmenuselect=t=>{}}calibrate(){}reload(t){}set items(t){this._items=t,$(this.refs.container).empty(),t.map(t=>this.push(t,!1))}get items(){return this._items}set context(t){this.attsw(t,"context"),$(this.refs.wrapper).removeClass("context"),t&&($(this.refs.wrapper).addClass("context"),$(this).hide())}get context(){return this.hasattr("context")}set onmenuselect(t){this._onmenuselect=t}set contentag(t){$(this).attr("contentag",t)}get contentag(){return $(this).attr("contentag")}get onmenuitemselect(){return this.handleselect}handleselect(t){this.context&&$(this).hide(),t.id=this.aid,this._onmenuselect(t),this.observable.trigger("menuselect",t)}show(t){this.context&&$(this).css("top",t.clientY-15+"px").css("left",t.clientX-5+"px").show()}is_root(){return void 0===this.root}mount(){$(this.refs.container).css("display","contents"),this.context&&$(this.refs.wrapper).mouseleave(t=>{if(this.is_root())return $(this).hide()})}unshift(t){this.push(t,!0)}delete(t){const e=t.data,s=this.items;s.includes(e)&&s.splice(s.indexOf(e),1),$(t).remove()}push(t,e){let s=this.contentag;t.tag&&(s=t.tag);const i=$(`<${s}>`);e?($(this.refs.container).prepend(i[0]),this.items.includes(t)||this.items.unshift(t)):(i.appendTo(this.refs.container),this.items.includes(t)||this.items.push(t));const r=i[0];return r.uify(this.observable),r.parent=this.parent,r.root=this.parent?this.parent.root:this,r.data=t,t.domel=r,r}layout(){return[{el:"ul",ref:"wrapper",children:[{el:"li",class:"afx-corner-fix"},{el:"div",ref:"container"},{el:"li",class:"afx-corner-fix"}]}]}}e.MenuTag=r,e.define("afx-menu",r),e.define("afx-menu-entry",i)}(e=t.tag||(t.tag={}))}(e=t.GUI||(t.GUI={}))}(OS||(OS={})),function(t){let e;!function(t){let e;!function(e){class s extends t.AFXTag{constructor(){super(),this.refs.yield=this}mount(){}init(){this.data=[]}layout(){return[]}calibrate(){}reload(t){}}e.GridRowTag=s;class i extends t.AFXTag{constructor(){super()}set oncellselect(t){this._oncellselect=t}set oncelldbclick(t){this._oncelldbclick=t}set data(t){t&&(this._data=t,this.ondatachange(),t.selected&&(this.selected=t.selected))}get data(){return this._data}set selected(t){this.attsw(t,"selected"),this._data&&(this._data.selected=t),t&&this.cellselect({id:this.aid,data:this},!1)}get selected(){return this.hasattr("selected")}reload(t){this.data=this.data}mount(){$(this).attr("class","afx-grid-cell"),this.oncelldbclick=this.oncellselect=t=>{},this.selected=!1,$(this).css("display","block"),$(this).click(t=>{let e={id:this.aid,data:this};return this.cellselect(e,!1)}),$(this).dblclick(t=>{let e={id:this.aid,data:this};return this.cellselect(e,!0)})}cellselect(t,e){const s={id:this.aid,data:{item:t.data}};return e?this._oncelldbclick(s):this._oncellselect(s)}}e.GridCellPrototype=i;class r extends i{constructor(){super()}ondatachange(){this.refs.cell.set(this.data)}init(){}calibrate(){}layout(){return[{el:"afx-label",ref:"cell"}]}}e.SimpleGridCellTag=r;class a extends t.AFXTag{constructor(){super()}init(){this._header=[],this.headeritem="afx-grid-cell",this.cellitem="afx-grid-cell",this._selectedCell=void 0,this._selectedRows=[],this._selectedRow=void 0,this._rows=[],this.resizable=!1,this._oncellselect=this._onrowselect=this._oncelldbclick=t=>{}}reload(t){}set oncellselect(t){this._oncellselect=t}set onrowselect(t){this._onrowselect=t}set oncelldbclick(t){this._oncelldbclick=t}set headeritem(t){$(this).attr("headeritem",t)}get headeritem(){return $(this).attr("headeritem")}set cellitem(t){$(this).attr("cellitem",t)}get cellitem(){return $(this).attr("cellitem")}get header(){return this._header}set header(t){if(this._header=t,!t||0===t.length)return void $(this.refs.header).hide();$(this.refs.header).empty();let e=0;for(let s of t){const i=$(`<${this.headeritem}>`).appendTo(this.refs.header)[0];if(i.uify(this.observable),i.data=s,s.domel=i,e++,this.resizable&&e!=t.length){const i=$("").appendTo(this.refs.header)[0];$(i).css("width","3px");let r=void 0;e{s.width=t.data.w+3,r&&delete r.width},i.uify(this.observable)}}this.calibrate()}get selectedRows(){return this._selectedRows}get selectedRow(){return this._selectedRow}get selectedCell(){return this._selectedCell}set rows(t){$(this.refs.grid).empty(),this._rows=t,t.map(t=>this.push(t,!1))}get rows(){return this._rows}set multiselect(t){this.attsw(t,"multiselect")}get multiselect(){return this.hasattr("multiselect")}set resizable(t){this.attsw(t,"resizable")}get resizable(){return this.hasattr("resizable")}delete(t){if(!t)return;const e=t.data,s=this.rows;this.selectedRow===t&&(this._selectedRow=void 0),$(this.selectedCell).parent()[0]===t&&(this._selectedCell=void 0);const i=this.selectedRows;i.includes(t)&&i.splice(i.indexOf(t),1),s.includes(e)&&s.splice(s.indexOf(e),1),$(t).remove()}push(t,e){const s=$("").css("display","contents");e?($(this.refs.grid).prepend(s[0]),this.rows.includes(t)||this.rows.unshift(t)):(s.appendTo(this.refs.grid),this.rows.includes(t)||this.rows.push(t));const i=s[0];s[0].uify(this.observable),i.data=t,t.domel=s[0];for(let e of t){let t=this.cellitem;e.tag&&({tag:t}=e);const i=$(`<${t}>`).appendTo(s);e.domel=i[0];const r=i[0];r.uify(this.observable),r.oncellselect=t=>this.cellselect(t,!1),r.oncelldbclick=t=>this.cellselect(t,!0),r.data=e}}unshift(t){this.push(t,!0)}cellselect(t,e){return t.id=this.aid,this.selectedCell&&$(this.selectedCell).attr("class","afx-grid-cell"),this._selectedCell=t.data.item,$(t.data.item).addClass("afx-grid-cell-selected"),e?(this.observable.trigger("celldbclick",t),this._oncelldbclick(t)):(this.observable.trigger("cellselect",t),this._oncellselect(t),this.rowselect(t))}rowselect(t){if(!t.data.item)return;const e={id:this.aid,data:{item:void 0,items:[]}},s=$(t.data.item).parent()[0];if(this.multiselect)this.selectedRows.includes(s)?(this.selectedRows.splice(this.selectedRows.indexOf(s),1),$(s).removeClass()):(this.selectedRows.push(s),$(s).removeClass().addClass("afx-grid-row-selected")),e.data.items=this.selectedRows;else{if(this.selectedRow===s)return;$(this.selectedRow).removeClass(),this._selectedRows=[s],e.data.item=s,e.data.items=[s],$(s).removeClass().addClass("afx-grid-row-selected"),this._selectedRows=[s]}return this._selectedRow=s,this._onrowselect(e),this.observable.trigger("rowselect",e)}has_header(){const t=this._header;return t&&t.length>0}calibrate(){this.calibrate_header(),this.has_header()?$(this.refs.container).css("height",$(this).height()-$(this.refs.header).height()+"px"):$(this.refs.container).css("height",$(this).height()+"px")}calibrate_header(){if(!this.has_header())return;const t=[];let e=0,s=0;const i=$(this).parent().width();if($.each(this._header,(function(i,r){return r.width?(t.push(r.width),e+=r.width):(t.push(-1),s++)})),s>0){const r=Math.round((i-e)/s);$.each(t,(function(e,s){if(-1===s)return t[e]=r}))}let r="",a="",h=0;for(let e of t)r+=e+"px ",h++,a+=e+"px ",hthis.calibrate()),$(this.refs.container).css("width","100%").css("overflow-x","hidden").css("overflow-y","auto"),this.calibrate()}layout(){return[{el:"div",ref:"header",class:"grid_row_header"},{el:"div",ref:"container",children:[{el:"div",ref:"grid"}]}]}}e.GridViewTag=a,e.define("afx-grid-view",a),e.define("afx-grid-cell",r),e.define("afx-grid-row",s)}(e=t.tag||(t.tag={}))}(e=t.GUI||(t.GUI={}))}(OS||(OS={})),function(t){let e;!function(t){let e;!function(e){class s extends t.AFXTag{constructor(){super(),this._ontabclose=t=>!0,this._ontabselect=t=>{}}init(){this.selected=-1}reload(t){}set closable(t){this.attsw(t,"closable")}get closable(){return this.hasattr("closable")}push(t){return t.closable=this.closable,this.refs.list.push(t)}delete(t){this.refs.list.delete(t)}unshift(t){return t.closable=this.closable,this.refs.list.unshift(t)}set items(t){for(let e of t)e.closable=this.closable;this.refs.list.data=t}get items(){return this.refs.list.data}set selected(t){this.refs.list.selected=t}get selected(){return this.refs.list.selected}set ontabclose(t){this._ontabclose=t}set ontabselect(t){this._ontabselect=t}mount(){$(this.refs.list).css("height","100%"),this.refs.list.onitemclose=t=>(t.id=this.aid,this._ontabclose(t)),this.refs.list.onlistselect=t=>(this._ontabselect(t),this.observable.trigger("tabselect",t))}layout(){return[{el:"afx-list-view",ref:"list"}]}}e.TabBarTag=s,e.define("afx-tab-bar",s)}(e=t.tag||(t.tag={}))}(e=t.GUI||(t.GUI={}))}(OS||(OS={})),function(t){let e;!function(t){let e;!function(e){class s extends t.AFXTag{constructor(){super(),this._ontabselect=t=>{}}init(){this.dir="column"}reload(t){}set ontabselect(t){this._ontabselect=t}get tabs(){return this.refs.bar.items}set selectedIndex(t){this.refs.bar.selected=t}set dir(t){$(this).attr("dir",t),t&&(this.refs.wrapper.dir=t)}get dir(){return $(this).attr("dir")}set selectedTab(t){if(!t)return;const e=this._selectedTab;this._selectedTab=t,e&&$(e.container).hide(),$(t.container).show(),this.observable.trigger("resize",void 0),$(t.container).attr("tabindex",-1).css("outline","none").trigger("focus")}get selectedTab(){return this._selectedTab}set tabbarwidth(t){t&&($(this.refs.bar).attr("data-width",""+t),this.refs.wrapper.calibrate())}set tabbarheight(t){$(this.refs.bar).attr("data-height",""+t),this.refs.wrapper.calibrate()}addTab(t,e){e&&$(this.refs.yield).append(t.container),$(t.container).css("width","100%").css("height","100%").hide();const s=this.refs.bar.push(t);return s.selected=!0,s}removeTab(t){t.data.container&&$(t.data.container).remove(),this.refs.bar.delete(t)}mount(){this.refs.bar.ontabselect=t=>{const e=t.data.item.data;return this.selectedTab=e,this._ontabselect({data:e,id:this.aid})},this.observable.one("mounted",t=>{$(this.refs.yield).children().each((t,e)=>{const s={};$(e).attr("tabname")&&(s.text=$(e).attr("tabname")),$(e).attr("icon")&&(s.icon=$(e).attr("icon")),$(e).attr("iconclass")&&(s.iconclass=$(e).attr("iconclass")),s.container=e,this.addTab(s,!1)})}),this.observable.on("resize",t=>this.calibrate()),this.calibrate()}calibrate(){$(this.refs.wrapper).css("height",$(this).height()+"px")}layout(){return[{el:"afx-tile",ref:"wrapper",children:[{el:"afx-tab-bar",ref:"bar"},{el:"div",ref:"yield"}]}]}}e.TabContainerTag=s,e.define("afx-tab-container",s)}(e=t.tag||(t.tag={}))}(e=t.GUI||(t.GUI={}))}(OS||(OS={})),function(t){let e;!function(e){let s;!function(s){class i extends e.AFXTag{constructor(){super()}reload(t){if(t&&"string"==typeof t)switch(t){case"expand":this.open=!0;break;case"collapse":this.open=!1;break;default:if(t!==this.treepath)return;this.open=!0}}set data(t){this._data=t,t&&(this.open=t.open,t.path&&(this.treepath=t.path),this.selected=t.selected,t.domel=this,this.ondatachange())}get data(){return this._data}set selected(t){this._data&&(this.attsw(t,"selected"),$(this.refs.wrapper).removeClass(),this._data.selected=t,t&&(this.treeroot.unselect(),this.treeroot.itemclick(this._evt),this._evt.data.dblclick=!1,$(this.refs.wrapper).addClass("afx_tree_item_selected")))}get selected(){return this.hasattr("selected")}set open(e){this.is_folder()&&(this.attsw(e,"open"),$(this.refs.toggle).removeClass(),e?(this.fetch?this.fetch(this).then(t=>{if(t)return this.nodes=t}).catch(e=>t.announcer.oserror(e.toString(),e)):this.nodes=this.nodes,$(this.refs.childnodes).show()):$(this.refs.childnodes).hide(),e?$(this.refs.toggle).addClass("afx-tree-view-folder-open"):$(this.refs.toggle).addClass("afx-tree-view-folder-close"))}get open(){return this.hasattr("open")}get indent(){return this._indent}set indent(t){t&&(this._indent=t,$(this.refs.padding).css("display","inline-block").css("height","1px").css("padding",0).css("margin",0).css("background-color","transparent").css("width",15*t+"px"))}is_folder(){return!!this.nodes}get nodes(){if(this._data)return this._data.nodes}set nodes(t){if(!t||!this.data)return;this._data.nodes=t,$(this.refs.childnodes).empty(),$(this.refs.wrapper).addClass("afx_folder_item");const e=this.treeroot;for(let s of t){const t=$("").appendTo(this.refs.childnodes);t[0].uify(this.observable);const i=t[0];i.treeroot=e,i.indent=this.indent+1,i.open=this.open,i.parent=this.parent,i.treepath=`${this.treepath}/${i.aid}`,i.fetch=this.fetch,i.data=s}}init(){this.treeroot=void 0,this.treepath=this.aid.toString(),this._evt={id:this.aid,data:{item:this,dblclick:!1}},this._indent=0}mount(){$(this.refs.container).css("padding",0).css("margin",0).css("white-space","nowrap"),$(this.refs.itemholder).css("display","inline-block"),$(this.refs.wrapper).click(t=>{this.selected=!0}),$(this.refs.wrapper).dblclick(t=>{this._evt.data.dblclick=!0,this.selected=!0}),$(this.refs.toggle).css("display","inline-block").css("width","15px").addClass("afx-tree-view-item").click(t=>(this.open=!this.open,t.preventDefault(),t.stopPropagation()))}layout(){return[{el:"div",ref:"wrapper",children:[{el:"ul",ref:"container",children:[{el:"li",ref:"padding"},{el:"li",ref:"toggle"},{el:"li",ref:"itemholder",class:"itemname",children:this.itemlayout()}]}]},{el:"ul",ref:"childnodes"}]}}s.TreeViewItemPrototype=i;class r extends i{constructor(){super()}ondatachange(){if(!this.data)return;const t=this.data;this.refs.label.set(t)}itemlayout(){return[{el:"afx-label",ref:"label"}]}}s.SimpleTreeViewItem=r;class a extends e.AFXTag{constructor(){super()}init(){this.itemtag="afx-tree-view-item",this._ontreeselect=this._ondragndrop=this._ontreedbclick=t=>{},this.indent=0,this.open=!0,this.treepath=this.aid.toString()}layout(){return[]}reload(t){}set dragndrop(t){this.attsw(t,"dragndrop")}get dragndrop(){return this.hasattr("dragndrop")}set ontreeselect(t){this._ontreeselect=t}set ontreedbclick(t){this._ontreedbclick=t}set itemtag(t){$(this).attr("itemtag",t)}get itemtag(){return $(this).attr("itemtag")}unselect(){this.selectedItem&&(this._selectedItem.selected=!1)}get selectedItem(){return this._selectedItem}set selectedItem(t){t&&t!==this.selectedItem&&(t.selected=!0)}expandAll(){if(!this.is_leaf())return this.update("expand")}collapseAll(){if(!this.is_leaf())return this.update("collapse")}itemclick(t){if(!t||!t.data)return;if(t.data.item===this.selectedItem&&!t.data.dblclick)return;this._selectedItem=t.data.item;const e={id:this.aid,data:t.data};return t.data.dblclick?(this._ontreedbclick(e),this.observable.trigger("treedbclick",e)):(this._ontreeselect(e),this.observable.trigger("treeselect",e))}is_root(){return void 0===this.treeroot}is_leaf(){const t=this.data;return!t||!t.nodes}set ondragndrop(t){this._ondragndrop=t}set data(t){if(this._selectedItem=void 0,!t)return;this._data=t,$(this).empty(),t.path&&(this.treepath=t.path);let e=this.itemtag;t.tag&&(e=t.tag);const s=$(`<${e}>`).appendTo(this);s[0].uify(this.observable);const i=s[0];i.treeroot=this.is_root()?this:this.treeroot,i.indent=this.indent,i.treepath=this.treepath,i.open=this.open,i.fetch=this.fetch,i.parent=this,i.data=t,this.is_root()&&($(this).off("mousedown",this._treemousedown),this.dragndrop&&$(this).on("mousedown",this._treemousedown))}get data(){return this._data}mount(){this._dnd={from:void 0,to:void 0},this._treemousedown=t=>{let e=$(t.target).closest("afx-tree-view");if(0===e.length)return;let s=e[0];return s!==this?(this._dnd.from=s,this._dnd.to=void 0,$(window).on("mouseup",this._treemouseup),$(window).on("mousemove",this._treemousemove)):void 0},this._treemouseup=t=>{$(window).off("mouseup",this._treemouseup),$(window).off("mousemove",this._treemousemove),$("#systooltip").hide();let e=$(t.target).closest("afx-tree-view");if(0===e.length)return;let s=e[0];s.is_leaf()&&(s=s.parent),s!==this._dnd.from&&s!==this._dnd.from.parent&&(this._dnd.to=s,this._ondragndrop({id:this.aid,data:this._dnd}),this._dnd={from:void 0,to:void 0})},this._treemousemove=t=>{if(!t)return;if(!this._dnd.from)return;const e=this._dnd.from.data,s=$("#systooltip"),i=t.clientY+5,r=t.clientX+5;s.show();s[0].set(e),s.css("top",i+"px").css("left",r+"px")}}}s.TreeViewTag=a,s.define("afx-tree-view",a),s.define("afx-tree-view-item",r)}(s=e.tag||(e.tag={}))}(e=t.GUI||(t.GUI={}))}(OS||(OS={})),function(t){let e;!function(t){let e;!function(e){class s extends t.AFXTag{constructor(){super()}init(){this.enable=!0,this._max=100,this._value=0,this._onchange=this._onchanging=()=>{}}reload(t){}set onvaluechange(t){this._onchange=t}set onvaluechanging(t){this._onchanging=t}set enable(t){this.attsw(t,"enable"),t?$(this).mouseover(()=>$(this.refs.point).show()).mouseout(()=>$(this.refs.point).hide()):($(this.refs.point).hide(),$(this).unbind("mouseover").unbind("mouseout"))}get enable(){return this.hasattr("enable")}set value(t){this._value=t,this.calibrate()}get value(){return this._value}set max(t){this._max=t,this.calibrate()}get max(){return this._max}mount(){this.enable_dragging(),$(this.refs.point).css("position","absolute"),$(this.refs.point).hide(),this.observable.on("resize",t=>this.calibrate()),$(this.refs.container).click(t=>{const e=$(this.refs.container).offset(),s=t.clientX-e.left,i=$(this.refs.container).width();this.value=s*this.max/i,this.calibrate();const r={id:this.aid,data:this.value};return this._onchange(r),this._onchanging(r)}),this.calibrate()}calibrate(){this.value>this.max&&(this.value=this.max),$(this.refs.container).css("width",$(this).width()+"px");const t=$(this.refs.container).width()*this.value/this.max;if($(this.refs.prg).css("width",t+"px").css("height",$(this.refs.container).height()+"px"),this.enable){const e=t-$(this.refs.point).width()/2,s=Math.floor(($(this.refs.prg).height()-$(this.refs.point).height())/2);$(this.refs.point).css("left",e+"px").css("top",s+"px")}}enable_dragging(){$(this.refs.point).css("user-select","none").css("cursor","default"),$(this.refs.point).on("mousedown",t=>{t.preventDefault();const e=$(this.refs.container).offset();$(window).on("mousemove",t=>{let s=t.clientX-e.left;s=s<0?0:s;const i=$(this.refs.container).width();return s=s>i?i:s,this.value=s*this.max/i,this.calibrate(),this._onchanging({id:this.aid,data:this.value})}),$(window).on("mouseup",t=>(this._onchange({id:this.aid,data:this.value}),$(window).unbind("mousemove",null),$(window).unbind("mouseup",null)))})}layout(){return[{el:"div",class:"container",ref:"container",children:[{el:"div",class:"progress",ref:"prg"},{el:"div",class:"dragpoint",ref:"point"}]}]}}e.SliderTag=s,e.define("afx-slider",s)}(e=t.tag||(t.tag={}))}(e=t.GUI||(t.GUI={}))}(OS||(OS={})),function(t){let e;!function(t){let e;!function(t){class e extends t.ListViewTag{constructor(){super()}reload(t){}set onready(t){this._onready=t}set dir(t){$(this).attr("dir",t),this.calibrate()}get dir(){return $(this).attr("dir")}set dropdown(t){}set buttons(t){}showlist(t){}dropoff(t){}ondatachange(){this.calibrate()}mount(){if($(this.refs.container).css("width","100%").css("height","100%"),$(this.refs.mlist).css("position","absolute").css("display","block").css("width","100%"),this.observable.on("resize",t=>this.calibrate()),this._onready)return this._onready(this)}push(t){const e=super.push(t);return this.enable_drag(e),e}enable_drag(t){$(t).css("user-select","none").css("cursor","default").css("display","block").css("position","absolute").on("mousedown",e=>{const s=$(this.refs.mlist).offset();e.preventDefault();const i=$(t).offset();i.top=e.clientY-i.top,i.left=e.clientX-i.left;const r=function(e){let r=e.clientY-i.top-s.top,a=e.clientX-s.left-i.left;return a=a<0?0:a,r=r<0?0:r,$(t).css("top",r+"px").css("left",a+"px")};var a=function(t){return $(window).unbind("mousemove",r),$(window).unbind("mouseup",a)};return $(window).on("mousemove",r),$(window).on("mouseup",a)})}calibrate(){let t=20,e=20;$(this.refs.mlist).css("height",$(this.refs.container).height()+"px");const s=$(this.refs.mlist).width(),i=$(this.refs.mlist).height();$(this.refs.mlist).children().each((r,a)=>{$(a).css("top",t+"px").css("left",e+"px");const h=$(a).width(),n=$(a).height();"vertical"===this.dir?(t+=n+20,t+n>i&&(t=20,e+=h+20)):(e+=h+20,e+h>s&&(e=20,t+=n+20))})}}t.FloatListTag=e,t.define("afx-float-list",e)}(e=t.tag||(t.tag={}))}(e=t.GUI||(t.GUI={}))}(OS||(OS={})),function(t){let e;!function(t){let e;!function(e){class s extends t.AFXTag{constructor(){super(),this._day=0,this._month=0,this._year=0,this._ondateselect=t=>{}}init(){$(this).css("height","100%"),$(this.refs.grid).css("width","100%")}reload(t){}get selectedDate(){return this._selectedDate}set ondateselect(t){this._ondateselect=t}mount(){$(this.refs.prev).click(t=>this.prevmonth()),$(this.refs.next).click(t=>this.nextmonth());const t=this.refs.grid;t.header=[{text:"__(Sun)"},{text:"__(Mon)"},{text:"__(Tue)"},{text:"__(Wed)"},{text:"__(Thu)"},{text:"__(Fri)"},{text:"__(Sat)"}],t.oncellselect=t=>{this.dateselect(t)},this.observable.on("resize",t=>this.calibrate()),this.calibrate(),this.calendar(null)}dateselect(t){if(!t.data.item)return;const e=t.data.item.data.text;if(""===e)return;const s={id:this.aid,data:new Date(this._year,this._month,parseInt(e))};return this._ondateselect(s),this._selectedDate=s.data,this.observable.trigger("dateselect",s)}calibrate(){$(this.refs.grid).css("height",$(this).height()-$(this.refs.ctrl).height()+"px")}prevmonth(){this._selectedDate=void 0,this._month--,this._month<0&&(this._month=11,this._year--),this.calendar(new Date(this._year,this._month,1))}nextmonth(){return this._selectedDate=void 0,this._month++,this._month>11&&(this._month=0,this._year++),this.calendar(new Date(this._year,this._month,1))}calendar(t){let e,s,i;t||(t=new Date),this._day=t.getDate(),this._month=t.getMonth(),this._year=t.getFullYear();const r=(new Date).getDate(),a=(new Date).getMonth(),h=(new Date).getFullYear(),n=[__("January"),__("February"),__("March"),__("April"),__("May"),__("June"),__("July"),__("August"),__("September"),__("October"),__("November"),__("December")],o=new Date(this._year,this._month,1),l=new Date(this._year,this._month+1,1),c=o.getDay(),d=Math.round((l.getTime()-o.getTime())/864e5),f=[];let u=[];for(e=0,i=c-1,s=0<=i;s?e<=i:e>=i;s?e++:e--)u.push({text:""});e=c;for(let t=1,s=d,i=1<=s;i?t<=s:t>=s;i?t++:t--)e%=7,0===e&&(f.push(u),u=[]),r===t&&this._month===a&&this._year===h?u.push({text:t,selected:!0}):u.push({text:t}),e++;for(let t=0,e=7-u.length,s=0<=e;s?t<=e:t>=e;s?t++:t--)u.push({text:""});f.push(u);this.refs.grid.rows=f,this.refs.mlbl.text=`${n[this._month]} ${this._year}`}layout(){return[{el:"div",ref:"ctrl",children:[{el:"i",class:"prevmonth",ref:"prev"},{el:"afx-label",ref:"mlbl"},{el:"i",class:"nextmonth",ref:"next"}]},{el:"afx-grid-view",ref:"grid"}]}}e.CalendarTag=s,e.define("afx-calendar-view",s)}(e=t.tag||(t.tag={}))}(e=t.GUI||(t.GUI={}))}(OS||(OS={})),function(t){let e;!function(t){let e;!function(e){class s extends t.AFXTag{constructor(){super(),this._oncolorselect=t=>{}}init(){}reload(t){}get selectedColor(){return this._selectedColor}set oncolorselect(t){this._oncolorselect=t}mount(){$(this.refs.wrapper).css("width","310px").css("height","190px").css("display","block").css("padding","3px"),$(this.refs.palette).css("width","284px").css("height","155px").css("float","left"),$(this.refs.colorval).css("width","15px").css("height","155px").css("text-align","center").css("margin-left","3px").css("display","block").css("float","left"),$(this.refs.inputwrp).css("margin-top","3px"),$(this.refs.hextext).css("width","70px").css("margin-left","3px").css("margin-right","5px"),this.build_palette()}build_palette(){const t=$(this.refs.palette).get(0).getContext("2d");let e=t.createLinearGradient(0,0,$(this.refs.palette).width(),0);e.addColorStop(0,"rgb(255, 0, 0)"),e.addColorStop(.15,"rgb(255, 0, 255)"),e.addColorStop(.33,"rgb(0, 0, 255)"),e.addColorStop(.49,"rgb(0, 255, 255)"),e.addColorStop(.67,"rgb(0, 255, 0)"),e.addColorStop(.84,"rgb(255, 255, 0)"),e.addColorStop(1,"rgb(255, 0, 0)"),e.addColorStop(0,"rgb(0, 0, 0)"),t.fillStyle=e,t.fillRect(0,0,t.canvas.width,t.canvas.height),e=t.createLinearGradient(0,0,0,$(this.refs.palette).width()),e.addColorStop(0,"rgba(255, 255, 255, 1)"),e.addColorStop(.5,"rgba(255, 255, 255, 0)"),e.addColorStop(.5,"rgba(0, 0, 0, 0)"),e.addColorStop(1,"rgba(0, 0, 0, 1)"),t.fillStyle=e,t.fillRect(0,0,t.canvas.width,t.canvas.height);const s=function(t){let e=t.toString(16);return 1===e.length&&(e="0"+e),e},i=e=>{$(this.refs.palette).css("cursor","crosshair");const i=$(this.refs.palette).offset(),r=e.pageX-i.left,a=e.pageY-i.top,h=t.getImageData(r,a,1,1);return{r:h.data[0],g:h.data[1],b:h.data[2],text:"rgb("+h.data[0]+", "+h.data[1]+", "+h.data[2]+")",hex:"#"+s(h.data[0])+s(h.data[1])+s(h.data[2])}},r=t=>{const e=i(t);return $(this.refs.colorval).css("background-color",e.text)};$(this.refs.palette).mouseenter(t=>$(this.refs.palette).on("mousemove",r)),$(this.refs.palette).mouseout(t=>{if($(this.refs.palette).unbind("mousemove",r),this.selectedColor)return $(this.refs.colorval).css("background-color",this.selectedColor.text)}),$(this.refs.palette).on("click",t=>{const e=i(t);$(this.refs.rgbtext).html(e.text),$(this.refs.hextext).val(e.hex),this._selectedColor=e;const s={id:this.aid,data:e};return this._oncolorselect(s),this.observable.trigger("colorselect",e)})}layout(){return[{el:"div",ref:"wrapper",children:[{el:"canvas",class:"color-palette",ref:"palette"},{el:"color-sample",ref:"colorval"},{el:"div",class:"afx-clear"},{el:"div",ref:"inputwrp",children:[{el:"input",ref:"hextext"},{el:"span",ref:"rgbtext"}]}]}]}}e.ColorPickerTag=s,e.define("afx-color-picker",s)}(e=t.tag||(t.tag={}))}(e=t.GUI||(t.GUI={}))}(OS||(OS={})),function(t){let e;!function(e){let s;!function(s){class i extends e.AFXTag{constructor(){super()}init(){this.data=[],this.status=!0,this.showhidden=!1,this.chdir=!0,this.view="list",this._onfileopen=this._onfileselect=t=>{},this._header=[{text:"__(File name)"},{text:"__(Type)"},{text:"__(Size)"}]}reload(t){}set fetch(t){this._fetch=t}set onfileselect(t){this._onfileselect=t}set onfileopen(t){this._onfileopen=t}set view(t){$(this).attr("view",t),this.switchView()}get view(){return $(this).attr("view")}set chdir(t){this.attsw(t,"chdir")}get chdir(){return this.hasattr("chdir")}set status(t){this.attsw(t,"status"),t?$(this.refs.status).show():$(this.refs.status).hide()}get status(){return this.hasattr("status")}set showhidden(t){this.attsw(t,"showhidden"),this.data&&this.switchView()}get showhidden(){return this.hasattr("showhidden")}get selectedFile(){return this._selectedFile}set path(e){e&&(this._path=e,this._fetch&&this._fetch(e).then(t=>{t&&(this.data=t.sort(this.sortByName).sort(this.sortByType),this.status&&(this.refs.status.text=" "))}).catch(e=>t.announcer.oserror(e.toString(),e)))}get path(){return this._path}set data(t){t&&(this._data=t,this.refreshData())}get data(){return this._data}set ondragndrop(t){this.refs.treeview.ondragndrop=t,this.refs.listview.ondragndrop=t}sortByType(t,e){return t.type||(t.type="none"),e.type||(e.type="none"),t.type.toLowerCase().localeCompare(e.type.toLowerCase())}sortByName(t,e){return t.filename&&(t.name=t.filename),e.filename&&(e.name=e.filename),t.name.toLowerCase().localeCompare(e.name.toLowerCase())}calibrate(){let t=$(this).outerHeight();const e=$(this).width();this.status&&(t-=$(this.refs.status).height()+10),$(this.refs.listview).css("height",t+"px"),$(this.refs.gridview).css("height",t+"px"),$(this.refs.treecontainer).css("height",t+"px"),$(this.refs.listview).css("width",e+"px"),$(this.refs.gridview).css("width",e+"px"),$(this.refs.treecontainer).css("width",e+"px")}refreshList(){const t=[];$.each(this.data,(e,s)=>{("."!==s.filename[0]||this.showhidden)&&(s.text=s.filename,s.text.length>10&&(s.text=s.text.substring(0,9)+"..."),s.iconclass=s.iconclass?s.iconclass:s.type,s.icon=s.icon,t.push(s))}),this.refs.listview.data=t}refreshGrid(){const t=[];$.each(this.data,(e,s)=>{if("."===s.filename[0]&&!this.showhidden)return;s.text=s.filename,s.iconclass=s.iconclass?s.iconclass:s.type;const i=[s,{text:s.mime,data:s},{text:s.size,data:s}];return t.push(i)}),this.refs.gridview.rows=t}refreshTree(){const t={text:this.path,path:this.path,open:!0,nodes:this.getTreeData(this.data)};this.refs.treeview.data=t}getTreeData(t){const e=[];return $.each(t,(t,s)=>{if("."!==s.filename[0]||this.showhidden)return s.text=s.filename,"dir"===s.type&&(s.nodes=[],s.open=!1),s.iconclass=s.iconclass?s.iconclass:s.type,s.icon=s.icon,e.push(s)}),e}refreshData(){if(this.data)switch(this.data.sort(this.sortByName).sort(this.sortByType),this.view){case"icon":return this.refreshList();case"list":return this.refreshGrid();default:return this.refreshTree()}}switchView(){switch($(this.refs.listview).hide(),$(this.refs.gridview).hide(),$(this.refs.treecontainer).hide(),this._selectedFile=void 0,this.view){case"icon":$(this.refs.listview).show();break;case"list":$(this.refs.gridview).show();break;default:$(this.refs.treecontainer).show()}this.refreshData(),this.calibrate(),this.status&&(this.refs.status.text=" ")}fileselect(t){t.path===this.path&&(t.type="dir",t.mime="dir"),this.status&&(this.refs.status.text=__("Selected: {0} ({1} bytes)",t.filename,t.size?t.size:"0"));const e={id:this.aid,data:t};this._selectedFile=t,this._onfileselect(e),this.observable.trigger("fileselect",e)}filedbclick(t){if(t.path===this.path&&(t.type="dir",t.mime="dir"),"dir"===t.type&&this.chdir)this.path=t.path;else{const e={id:this.aid,data:t};this._onfileopen(e),this.observable.trigger("fileopen",e)}}mount(){this.observable.on("resize",t=>this.calibrate());const t=this.refs.treeview;t.fetch=t=>new Promise((e,s)=>this._fetch&&t.data.path?this._fetch(t.data.path).then(t=>e(this.getTreeData(t.sort(this.sortByName).sort(this.sortByType)))).catch(t=>s(__e(t))):e(void 0));const e=this.refs.gridview,s=this.refs.listview;e.resizable=!0,e.header=this._header,t.dragndrop=!0,s.dragndrop=!0,s.onlistselect=t=>{this.fileselect(t.data.item.data)},e.onrowselect=t=>{this.fileselect($(t.data.item).children()[0].data)},t.ontreeselect=t=>{this.fileselect(t.data.item.data)},s.onlistdbclick=t=>{this.filedbclick(t.data.item.data)},e.oncelldbclick=t=>{this.filedbclick(t.data.item.data)},t.ontreedbclick=t=>{this.filedbclick(t.data.item.data)},this.switchView()}layout(){return[{el:"afx-list-view",ref:"listview"},{el:"div",class:"treecontainer",ref:"treecontainer",children:[{el:"afx-tree-view",ref:"treeview"}]},{el:"afx-grid-view",ref:"gridview"},{el:"afx-label",class:"status",ref:"status"}]}}s.FileViewTag=i,s.define("afx-file-view",i)}(s=e.tag||(e.tag={}))}(e=t.GUI||(t.GUI={}))}(OS||(OS={})),function(t){let e;!function(t){let e;!function(e){class s extends t.AFXTag{constructor(){super()}init(){$(this.refs.yield).css("position","relative").css("width","100%").css("height","100%"),$(this).css("position","absolute").css("z-index",1e6)}reload(t){}set width(t){t&&(this._width=t,this.calibrate())}get width(){return this._width}set height(t){t&&(this._height=t,this.calibrate())}get height(){return this._height}mount(){return this.calibrate()}calibrate(){return $(this).css("width",this.width).css("height",this.height),this.observable.trigger("resize",{id:this.aid,data:{w:this.width,h:this.height}})}layout(){return[{el:"afx-vbox",ref:"yield"}]}}e.OverlayTag=s,e.define("afx-overlay",s)}(e=t.tag||(t.tag={}))}(e=t.GUI||(t.GUI={}))}(OS||(OS={})),function(t){let e;!function(e){let s;!function(s){class i extends e.AFXTag{constructor(){super(),this._onappselect=t=>{}}reload(t){}init(){this._items=[]}layout(){return[]}get items(){return this._items}set selectedApp(t){this._selectedApp=t;let e=void 0;for(let s of this.items)s.app.blur(),$(s.domel).removeClass(),t&&t===s.app&&(e=s.domel);e&&($(e).addClass("selected"),$(Ant.OS.GUI.workspace)[0].unselect())}get selectedApp(){return this._selectedApp}newapp(t){this.items.push(t);const e=$(""),s=e[0];e.appendTo(this),e[0].uify(this.observable),s.set(t),s.data=t.app,e.attr("tooltip","cr:"+t.app.title()),t.domel=s,s.onbtclick=e=>{e.id=this.aid,this._onappselect(e),t.app.show()},this.selectedApp=t.app}removeapp(t){let e=-1;const s=this.items;for(let i=0;i{if(t.target===this)return;const s=$(t.target).closest("afx-button")[0].data;return e.items=[{text:"__(Show)",dataid:"show"},{text:"__(Hide)",dataid:"hide"},{text:"__(Close)",dataid:"quit"}],e.onmenuselect=function(t){const e=t.data.item.data;if(s[e.dataid])return s[e.dataid]()},e.show(t)},t.announcer.trigger("sysdockloaded",void 0)}}s.AppDockTag=i,s.define("afx-apps-dock",i)}(s=e.tag||(e.tag={}))}(e=t.GUI||(t.GUI={}))}(OS||(OS={})),function(t){let e;!function(t){let e;!function(e){class s extends t.AFXTag{constructor(){super(),this._osmenu={text:__("Start"),iconclass:"fa fa-circle"},this._view=!1}init(){}reload(t){}attachservice(t){return this.refs.systray.unshift(t),t.attach(this.refs.systray)}open(){const t=this.refs.applist,e=t.selectedItem;e&&e.data&&"header"!==e.data.dataid&&(this.toggle(!1),Ant.OS.GUI.openWith(e.data),t.unselect())}search(t){const e=this.refs.applist;switch(t.which){case 27:return this.toggle(!1);case 37:return t.preventDefault();case 38:return e.selectPrev(),t.preventDefault();case 39:return t.preventDefault();case 40:return e.selectNext(),t.preventDefault();case 13:return t.preventDefault(),this.open();default:var s=this.refs.search.value;if(!(s.length>=3))return this.refreshAppList();var i=Ant.OS.API.search(s);if(0===i.length)return;e.data=i}}detachservice(t){this.refs.systray.delete(t.domel)}layout(){return[{el:"div",ref:"panel",children:[{el:"afx-menu",ref:"osmenu",class:"afx-panel-os-menu"},{el:"afx-menu",id:"appmenu",ref:"appmenu",class:"afx-panel-os-app"},{el:"afx-menu",id:"systray",ref:"systray",class:"afx-panel-os-stray"}]},{el:"afx-overlay",id:"start-panel",ref:"overlay",children:[{el:"afx-hbox",height:30,children:[{el:"div",width:30,id:"searchicon"},{el:"input",ref:"search"}]},{el:"afx-list-view",id:"applist",ref:"applist"},{el:"afx-hbox",id:"btlist",height:30,children:[{el:"afx-button",ref:"btscreen",tooltip:__("ct:Toggle fullscreen")},{el:"afx-button",ref:"btuser",tooltip:__("ct:User: {0}",Ant.OS.setting.user.username)},{el:"afx-button",ref:"btlogout",tooltip:__("ct:Logout")}]}]}]}refreshAppList(){let t,e;const s=[];for(t in Ant.OS.setting.system.packages)e=Ant.OS.setting.system.packages[t],e&&e.app&&s.push(e);for(t in Ant.OS.setting.system.menu)e=Ant.OS.setting.system.menu[t],s.push(e);s.sort((function(t,e){return t.texte.text?1:0})),this.refs.applist.data=s}toggle(t){this._view=t,t?($(this.refs.overlay).show(),this.refreshAppList(),this.calibrate(),$(document).on("click",this._cb),this.refs.search.value="",$(this.refs.search).focus()):($(this.refs.overlay).hide(),$(document).unbind("click",this._cb))}calibrate(){this.refs.overlay.height=$(window).height()-$(this.refs.panel).height()+"px"}mount(){this.refs.osmenu.items=[this._osmenu],this._cb=t=>$(t.target).closest($(this.refs.overlay)).length||$(t.target).closest(this.refs.osmenu).length?$(this.refs.search).focus():this.toggle(!1),$(this.refs.appmenu).css("z-index",1e6),$(this.refs.systray).css("z-index",1e6),this.refs.btscreen.set({iconclass:"fa fa-tv",onbtclick:t=>(this.toggle(!1),Ant.OS.GUI.toggleFullscreen())}),this.refs.btuser.set({iconclass:"fa fa-user-circle-o",onbtclick:t=>(this.toggle(!1),Ant.OS.GUI.openDialog("InfoDialog",Ant.OS.setting.user))}),this.refs.btlogout.set({iconclass:"fa fa-power-off",onbtclick:t=>(this.toggle(!1),Ant.OS.exit())}),this.refs.osmenu.onmenuselect=t=>this.toggle(!0),$(this.refs.search).keyup(t=>this.search(t)),$(this.refs.applist).click(t=>this.open()),Ant.OS.GUI.bindKey("CTRL- ",t=>!1===this._view?this.toggle(!0):this.toggle(!1)),Ant.OS.announcer.trigger("syspanelloaded",void 0),$(this.refs.overlay).css("left",0).css("top",$(this.refs.panel).height()+"px").css("bottom","0").hide()}}e.SystemPanelTag=s,e.define("afx-sys-panel",s)}(e=t.tag||(t.tag={}))}(e=t.GUI||(t.GUI={}))}(OS||(OS={})),function(t){let e,s;!function(t){t.Announcer=class{constructor(){this.observable={},this.enable=!0}disable(){return this.off("*"),this.enable=!1}on(t,e){this.enable&&(this.observable[t]||(this.observable[t]={one:new Set,many:new Set}),this.observable[t].many.add(e))}one(t,e){this.enable&&(this.observable[t]||(this.observable[t]={one:new Set,many:new Set}),this.observable[t].one.add(e))}off(t,e){const s=(t,e)=>{if(this.observable[t])return e?(this.observable[t].one.delete(e),this.observable[t].many.delete(e)):this.observable[t]?delete this.observable[t]:void 0};if("*"===t)for(let t in this.observable)s(t,e);else s(t,e)}trigger(t,e){const s=(t,e)=>{const s=[t,"*"];for(let t of s)this.observable[t]&&(this.observable[t].one.forEach(t=>t(e)),this.observable[t].one=new Set,this.observable[t].many.forEach(t=>t(e)))};if("*"!==t)return s(t,e);for(let t in this.observable){this.observable[t];"*"!==t&&s(t,e)}}}}(e=t.API||(t.API={})),function(t){t.observable=new e.Announcer,t.listeners={},t.on=function(e,s,i){t.listeners[i.pid]||(t.listeners[i.pid]=[]),t.listeners[i.pid].push({e:e,f:s}),t.observable.on(e,s)},t.trigger=function(e,s){t.observable.trigger(e,s)},t.osfail=function(e,s){t.ostrigger("fail",{m:e,e:s})},t.oserror=function(e,s){t.ostrigger("error",{m:e,e:s})},t.osinfo=function(e){t.ostrigger("info",{m:e,e:null})},t.ostrigger=function(e,s){t.trigger(e,{id:0,data:s,name:"OS"})},t.unregister=function(e){if(t.listeners[e.pid]&&t.listeners[e.pid].length>0){for(let s of t.listeners[e.pid])t.observable.off(s.e,s.f);delete t.listeners[e.pid]}},t.getMID=function(){return t.quota+=1,t.quota}}(s=t.announcer||(t.announcer={}))}(OS||(OS={}));var Ant=this; \ No newline at end of file diff --git a/blog/assets/style.css b/blog/assets/style.css index ac38933..a2ef6bd 100644 --- a/blog/assets/style.css +++ b/blog/assets/style.css @@ -260,10 +260,43 @@ button { white-space: -o-pre-wrap; /* Opera 7 */ word-wrap: break-word; /* Internet Explorer 5.5+ */ } -#container .blogentry a { +.search-result { + color: #24292e; +} +.search-result ul { + list-style: none; + margin: 0; + padding: 0; +} +.search-result ul li{ + margin: 0; +} +.search-result ul li b { + color: #878887; +} +.search-result ul li p.title +{ + font-size: 16px; +} +.search-result ul li p.preview { + margin: 0; + padding: 0; + padding-left: 20px; +} +#container .blogentry a, +.search-result a { text-decoration: none; color: #3170b2; } +.search-result h2 { + font-size: 18px; + text-align: left; + padding-bottom: 0.3em; + border-bottom: 1px solid #eaecef; + margin-bottom: 16px; + font-weight: 600; + line-height: 1.25; +} #container .blogentry hr { display: block; height: 1px; @@ -330,6 +363,9 @@ button { display: block; margin: 0 auto; } +form.search-form { + display: contents; +} input.search-box { flex: 1; padding: 0; diff --git a/blog/controllers/PostController.lua b/blog/controllers/PostController.lua index 39793c1..0463557 100644 --- a/blog/controllers/PostController.lua +++ b/blog/controllers/PostController.lua @@ -6,6 +6,34 @@ BaseController:subclass( } ) +local tools = {} +tools.sum = function(v) + local sum = 0.0 + for i=1,#v do sum = sum + v[i] end + return sum +end + +tools.mean = function(v) + return tools.sum(v)/#v + +end + +tools.argmax = function(v) + local maxv = 0.0 + local maxi = 0.0 + for i = 1,#v do + if v[i] >= maxv then + maxi = i + maxv = v[i] + end + end + return maxi,maxv +end + +tools.cmp = function(a,b) + return a[2] > b[2] +end + function PostController:index(...) return self:top(table.unpack({...})) end @@ -22,7 +50,7 @@ end function PostController:afterof(id, limit) limit = limit or POST_LIMIT - local data, order = self.blog:fetch({[">"] = {id = id}}, limit, {ctime = "ASC"}) + local data, order = self.blog:fetch({["id$gt"] = tonumber(id)}, limit, { "ctime$asc"}) if not data or #order == 0 then return self:notfound("No entry found") end @@ -36,9 +64,68 @@ function PostController:afterof(id, limit) return true end +function PostController:search(...) + local index_file = DB_FILE..".index.json" + local st = require("stmr") + local indexes, err_code = JSON.decodeFile(index_file) + local terms = REQUEST.q + if not err_code then + -- prepare the vectors + local docs = {} + local tid = 1 + local tokens = {} + local search_vector = {} + for word in string.gmatch(terms,'%w+') do + local token = st.stmr(word:lower()) + local index = indexes[token] + if index then + for id,v in pairs(index) do + if not docs[id] then + docs[id] = {} + end + docs[id][token] = v + end + tokens[tid] = token + tid = tid + 1 + end + end + --echo(JSON.encode(docs)) + --echo(JSON.encode(tokens)) + + -- now create one vector for each documents + local mean_tfidf = {} + for id,doc in pairs(docs) do + local vector = {} + for i,token in ipairs(tokens) do + if doc[token] then + vector[i] = doc[token] + else + vector[i] = 0 + end + end + local data, order = self.blog:find({ + where = {id = tonumber(id)}, + fields = {"id", "title", "utime", "ctime", "content"} + }) + if data and data[1] then + data[1].content = data[1].content:sub(1,255) + table.insert(mean_tfidf, {id, tools.mean(vector), data[1]}) + end + end + table.sort(mean_tfidf, tools.cmp) + self.template:setView("search") + self.template:set("result", mean_tfidf) + self.template:set("title", "Search result") + return true + else + LOG_ERROR("Unable to parse file %s", index_file) + return self:notfound("Internal search error") + end +end + function PostController:beforeof(id, limit) limit = limit or POST_LIMIT - local data, order = self.blog:fetch({["<"] = {id = id}}, limit) + local data, order = self.blog:fetch({["id$lt"] = tonumber(id)}, limit) if not data or #order == 0 then return self:notfound("No entry found") end @@ -58,15 +145,15 @@ function PostController:list(data, order) end function PostController:bytag(b64tag, limit, action, id) - local tag = bytes.__tostring(std.b64decode(b64tag .. "==")) - local cond = {["LIKE"] = {tags = "%%" .. tag .. "%%"}} + local tag = tostring(enc.b64decode(b64tag .. "==")) + local cond = {["tags$like"] = "%%"..tag.."%%"} local order = nil limit = limit or POST_LIMIT if action == "before" then - cond = {["and"] = {cond, {["<"] = {id = id}}}} + cond["id$lt"] = tonumber(id) elseif action == "after" then - cond = {["and"] = {cond, {[">"] = {id = id}}}} - order = {ctime = "ASC"} + cond["id$gt"] = tonumber(id) + order = {"ctime$asc"} end local data, sort = self.blog:fetch(cond, limit, order) if not data or #sort == 0 then @@ -93,7 +180,7 @@ function PostController:json(id) error = false, result = false } - local data, order = self.blog:fetch({["="] = {id = id}}) + local data, order = self.blog:fetch({id = tonumber(id)}) if not data or #order == 0 then obj.error = "No data found" else @@ -126,7 +213,7 @@ function PostController:json(id) end function PostController:id(pid) - local data, order = self.blog:fetch({["="] = {id = pid}}) + local data, order = self.blog:fetch({id = tonumber(pid)}) if not data or #order == 0 then return self:notfound("No post found") end @@ -149,7 +236,8 @@ function PostController:id(pid) self.template:set("similar_posts", similar_posts) self.template:set("render", true) self.template:set("tags", data.tags) - self.template:set("url", HTTP_ROOT .. "/post/id/" .. pid) + self.template:set("url", string.format(HTTP_ROOT .. "/post/id/%d",pid)) + -- self.template:set("url", string.format("https://blog.lxsang.me/post/id/%d",pid)) self.template:setView("detail") return true end @@ -168,7 +256,7 @@ function PostController:actionnotfound(...) end function PostController:graph_json(...) - local nodes = self.blog:find({exp= { ["="] = { publish = 1}}, fields = {"id", "title"}}) + local nodes = self.blog:find({ where = {publish = 1}, fields = {"id", "title"}}) local output = { error = false, result = false } local lut = {} std.json() @@ -195,7 +283,7 @@ function PostController:graph_json(...) else key = v.sid..v.pid end - key = std.sha1(key) + key = enc.sha1(key) if not lut[key] then output.result.links[i] = link i = i + 1 @@ -212,40 +300,3 @@ function PostController:graph(...) self.template:set("d3", true) return true end - -function PostController:analyse(n) - if not n then - n = 5 - end - local path = WWW_ROOT..DIR_SEP.."ai" - local gettext = loadfile(path .. "/gettext.lua")() - local cluster = loadfile(path .. "/cluster.lua")() - local data = gettext.get({publish = 1}) - local documents = {} - if data then - local sw = gettext.stopwords(path .. "/stopwords.txt") - for k, v in pairs(data) do - local bag = cluster.bow(data[k].content, sw) - documents[data[k].id] = bag - end - cluster.tfidf(documents) - --local v = cluster.search("arm", documents) - --echo(JSON.encode(v)) - local vectors, maxv, size = cluster.get_vectors(documents) - -- purge the table - self.analytical:delete({["="] = {["1"] = 1}}) - -- get similarity and put to the table - for id, v in pairs(vectors) do - local top = cluster.top_similarity(id, vectors, tonumber(n), 0.1) - for a, b in pairs(top) do - local record = {pid = id, sid = a, score = b} - self.analytical:create(record) - end - end - self.template:set("message", "Analyse complete") - else - self.template:set("message", "Cannot analyse") - end - self.template:set("title", "TFIDF-analyse") - return true -end diff --git a/blog/controllers/ServiceController.lua b/blog/controllers/ServiceController.lua index 3ec530f..0d32fb2 100644 --- a/blog/controllers/ServiceController.lua +++ b/blog/controllers/ServiceController.lua @@ -21,7 +21,7 @@ function ServiceController:sendmail() fail("unknown request") end local rq = (JSON.decodeString(REQUEST.json)) - local to = "mrsang@lxsang.me" + local to = "mrsang@iohub.dev" local from = "From: " .. rq.email .. "\n" local suject = "Subject: " .. rq.subject .. "\n" local content = "Contact request from:" .. rq.name .. "\n Email: " .. rq.email .. "\n" .. rq.content .. "\n" @@ -45,7 +45,7 @@ function ServiceController:subscribe() end local rq = (JSON.decodeString(REQUEST.json)) -- check if email is exist - local data = self.subscribers:find({exp = {["="] = {email = rq.email}}}) + local data = self.subscribers:find({where = {email = rq.email}}) if data and #data > 0 then fail("You are already/previously subscribed") else diff --git a/blog/models/AnalyticalModel.lua b/blog/models/AnalyticalModel.lua index 57e2ce7..c54e9e7 100644 --- a/blog/models/AnalyticalModel.lua +++ b/blog/models/AnalyticalModel.lua @@ -9,5 +9,8 @@ BaseModel:subclass("AnalyticalModel",{ }) function AnalyticalModel:similarof(id) - return self:find({ exp = {["="] = {pid = id}}, order = {score = "DESC"}}) + return self:find({ + where = {pid = id}, + order = { "score$desc"} + }) end \ No newline at end of file diff --git a/blog/models/BlogModel.lua b/blog/models/BlogModel.lua index b792bd5..f8e9bb3 100644 --- a/blog/models/BlogModel.lua +++ b/blog/models/BlogModel.lua @@ -15,28 +15,27 @@ BaseModel:subclass("BlogModel",{ }) function BlogModel:fetch(cnd, limit, order) - local exp = {} - exp[1] = {["="] = { publish = 1 }} - if cnd then - exp[2] = cnd - else - - end - - local cond = { - exp = {["and"] = exp }, - order = { ctime = "DESC" }, + local filter = { + order = { "ctime$desc" }, fields = { "id", "title", "utime", "ctime", "utimestr", "content", "ctimestr", "rendered", "tags" - } + } } + if limit then - cond.limit = limit + filter.limit = limit end if order then - cond.order = order + filter.order = order end - return self:find(cond) + + filter.where = {} + if cnd then + filter.where = cnd + end + filter.where.publish = 1 + + return self:find(filter) end function BlogModel:minid() diff --git a/blog/router.lua b/blog/router.lua index 6703800..e03184f 100644 --- a/blog/router.lua +++ b/blog/router.lua @@ -1,26 +1,30 @@ - -- the rewrite rule for the framework -- should be something like this -- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>& -- some global variables -DIR_SEP = "/" +package.path = _SERVER["LIB_DIR"].."/lua/?.lua" +require("silk.api") +-- crypto lib +enc = require("enc") WWW_ROOT = __ROOT__.."/blog" +-- TODO: change me +DB_FILE = "/home/dany/databases/mrsang.db" +-- add aditional paths +package.path = package.path..";"..WWW_ROOT .. '/?.lua' + +DIR_SEP = "/" if HEADER.Host then HTTP_ROOT= "https://"..HEADER.Host else - HTTP_ROOT = "https://blog.lxsang.me" + HTTP_ROOT = "https://blog.iohub.dev" end --- class path: path.to.class -BASE_FRW = "" --- class path: path.to.class -CONTROLLER_ROOT = BASE_FRW.."blog.controllers" -MODEL_ROOT = BASE_FRW.."blog.models" +-- TODO remove me +HTTP_ROOT = HTTP_ROOT.."/next/blog" +CONTROLLER_ROOT = "blog.controllers" +MODEL_ROOT = "blog.models" -- file path: path/to/file VIEW_ROOT = WWW_ROOT..DIR_SEP.."views" -LOG_ROOT = WWW_ROOT..DIR_SEP.."logs" -POST_LIMIT = 10 --- require needed library -require(BASE_FRW.."silk.api") +POST_LIMIT = 3 if REQUEST.r then REQUEST.r = REQUEST.r:gsub("%:", "/") @@ -29,8 +33,8 @@ end -- registry object store global variables local REGISTRY = {} -- set logging level -REGISTRY.logger = Logger:new{ levels = {INFO = false, ERROR = true, DEBUG = false}} -REGISTRY.db = DBHelper:new{db="mrsang"} +REGISTRY.logger = Logger:new{ level = Logger.INFO} +REGISTRY.db = DBModel:new{db=DB_FILE} REGISTRY.layout = 'default' REGISTRY.fileaccess = true diff --git a/blog/views/default/layout.ls b/blog/views/default/layout.ls index 08f66b8..602b02e 100644 --- a/blog/views/default/layout.ls +++ b/blog/views/default/layout.ls @@ -1,6 +1,7 @@ - - - + + + + + - - + + @@ -41,8 +45,8 @@ - - + + @@ -54,9 +58,9 @@ - + - + - + + - + diff --git a/info/views/default/user/index.ls b/info/views/default/user/index.ls index 947a646..7abf54e 100644 --- a/info/views/default/user/index.ls +++ b/info/views/default/user/index.ls @@ -1,6 +1,6 @@
- +

diff --git a/index.ls b/layout.ls similarity index 85% rename from index.ls rename to layout.ls index 8572293..9713088 100644 --- a/index.ls +++ b/layout.ls @@ -1,7 +1,5 @@ Hi, I'm - - + + diff --git a/mimes.json b/mimes.json index ba584b1..f3fbdbe 100644 --- a/mimes.json +++ b/mimes.json @@ -74,5 +74,9 @@ "map":{ "mime": "application/json", "binary": false + }, + "sh":{ + "mime": "text/plain", + "binary": false } } \ No newline at end of file diff --git a/os/Makefile b/os/Makefile deleted file mode 100644 index f81fc0e..0000000 --- a/os/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -copyfiles = controllers libs router.lua - -main: - - mkdir -p $(BUILDDIR) - cp -rfv $(copyfiles) $(BUILDDIR) - - cd $(BUILDDIR) && ln -s ../grs ./rst \ No newline at end of file diff --git a/os/controllers/IndexController.lua b/os/controllers/IndexController.lua deleted file mode 100644 index 91e325c..0000000 --- a/os/controllers/IndexController.lua +++ /dev/null @@ -1,33 +0,0 @@ -BaseController:subclass( - "IndexController", - { - registry = {}, - models = {} - } -) - -function IndexController:actionnotfound(...) - return self:index(table.unpack({...})) -end - -function IndexController:index(...) - html() - std.f(WWW_ROOT..DIR_SEP.."index.html") -end - -function IndexController:doc(...) - local api = { - author = "Xuan Sang LE", - email = "xsang.le@gmail.com", - api_name = "AntOS API", - version = "0.2.4 a", - documents = { - vfs = HTTP_ROOT.."/VFS", - vdb = HTTP_ROOT.."/VDB", - user = HTTP_ROOT.."/user", - system = HTTP_ROOT.."/system" - } - } - result(api) - return false -end \ No newline at end of file diff --git a/os/controllers/SystemController.lua b/os/controllers/SystemController.lua deleted file mode 100644 index f290db8..0000000 --- a/os/controllers/SystemController.lua +++ /dev/null @@ -1,257 +0,0 @@ -BaseController:subclass( - "SystemController", - { - registry = {}, - models = {} - } -) - -function SystemController:actionnotfound(...) - return self:index(table.unpack({...})) -end - -function SystemController:index(...) - local api = { - description = "This api handle system operations", - actions = { - ["/packages"] = "Handle all operation relate to package: list, install, cache, uninstall", - ["/settings"] = "Save user setting", - ["/application"] = "Call a specific server side application api", - ["/apigateway"] = "Gateway for executing custom server side code" - } - } - result(api) - return false -end - -function SystemController:packages(...) - auth_or_die("User unauthorized. Please login") - local rq = (JSON.decodeString(REQUEST.json)) - local packages = require("packages") - packages.init(rq.args.paths) - if rq ~= nil then - -- check user command here - if (rq.command == "install") then - packages.install(rq.args) - elseif rq.command == "cache" then - packages.cache(rq.args) - elseif rq.command == "list" then - packages.list(rq.args.paths) - elseif rq.command == "uninstall" then - packages.uninstall(rq.args.path) - else - fail("Uknown packages command") - end - else - fail("Uknown request") - end -end - -function SystemController:settings(...) - auth_or_die("User unauthorized. Please login") - local user = SESSION.user - if user then - local ospath = require("vfs").ospath("home:///", user) - if REQUEST and REQUEST.json then - local file_path = ospath .. "/" .. ".settings.json" - local f = io.open(file_path, "w") - if f then - f:write(REQUEST.json) - f:close() - os.execute("chmod o-r "..file_path) - result(true) - else - fail("Cannot save setting") - end - else - fail("No setting founds") - end - else - fail("User not found") - end -end - -function SystemController:application(...) - auth_or_die("User unauthorized. Please login") - local rq = nil - if REQUEST.json ~= nil then - rq = (JSON.decodeString(REQUEST.json)) - else - rq = REQUEST - end - - if rq.path ~= nil then - local pkg = require("vfs").ospath(rq.path) - if pkg == nil then - pkg = WWW_ROOT .. "/packages/" .. rq.path - --die("unkown request path:"..rq.path) - end - pkg = pkg .. "/api.lua" - if ulib.exists(pkg) then - dofile(pkg).exec(rq.method, rq.arguments) - else - fail("Uknown application handler: " .. pkg) - end - else - fail("Uknown request") - end -end - -function SystemController:apigateway(...) - local args={...} - local use_ws = false - if REQUEST and REQUEST.ws == "1" then - -- override the global echo command - echo = std.ws.swrite - use_ws = true - --else - -- std.json() - end - local exec_with_user_priv = function(data) - local uid = ulib.uid(SESSION.user) - -- disable unused modules - package.loaded["silk.Router"] = nil - package.loaded["silk.BaseController"] = nil - package.loaded["silk.DBHelper"] = nil - package.loaded["silk.Template"] = nil - package.loaded["silk.api"] = nil - package.loaded["silk.Logger"] = nil - package.loaded["silk.BaseModel"] = nil - package.loaded["silk.BaseObject"] = nil - package.loaded["os.controllers.SystemController"] = nil - -- user only allowed to load module in the following paths - package.path = __api__.apiroot.."/?.lua;"..WWW_ROOT .. '/libs/?.lua' - if not ulib.setgid(uid.gid) or not ulib.setuid(uid.id) then - echo("Cannot set permission to execute the code") - return - end - local r, e - e = "{'error': 'Unknow function'}" - -- set env var - local home = ulib.home_dir(uid.id) - ulib.setenv("USER", SESSION.user, 1) - ulib.setenv("LOGNAME", SESSION.user, 1) - if home then - ulib.setenv("HOME", home, 1) - ulib.setenv("PWD", home,1) - local paths = "" - if ulib.exists(home.."/bin") then - paths = home.."/bin:" - end - if ulib.exists(home.."/.local/bin") then - paths = paths..home.."/.local/bin:" - end - local envar = ulib.getenv("PATH") - if envar then - paths = paths..envar - end - ulib.setenv("PATH", paths,1) - end - -- run the code as user - if data.code then - r, e = load(data.code) - if r then - local status, result = pcall(r) - if result then - if (status) then - echo(JSON.encode(result)) - else - echo(result) - end - end - else - echo(e) - end - elseif data.path then - local ospath = require("vfs").ospath(data.path) - r, e = loadfile(ospath) - if r then - local status, result = pcall(r, data.parameters) - if result then - if (status) then - echo(JSON.encode(result)) - else - echo(result) - end - end - else - echo(e) - end - else - echo(e) - end - end - - if (is_auth()) then - local pid = ulib.fork()--std.pfork(HTTP_REQUEST.id) - if (pid == -1) then - echo("{'error':'Cannot create process'}") - elseif pid > 0 then -- parent - -- wait for the child exit or websocket exit - ulib.waitpid(pid, 0) - --ulib.kill(pid) - print("Parent exit") - else -- child - if use_ws then - if std.ws.enable() then - -- read header - local header = nil - -- wait until we receive request - while(not header) do header = std.ws.header() end - if header then - if header.mask == 0 then - print("Data is not masked") - std.ws.close(1012) - elseif header.opcode == std.ws.CLOSE then - print("Connection closed") - -- std.ws.close(1000) - elseif header.opcode == std.ws.TEXT then - -- read the file - local data = std.ws.read(header) - if data then - data = (JSON.decodeString(data)) - exec_with_user_priv(data) - std.ws.close(1011) - else - print("Error: Invalid request") - std.ws.close(1011) - end - end - else - std.ws.close(1011) - end - else - print("Web socket is not available.") - end - else - if REQUEST.path then - exec_with_user_priv(REQUEST) - elseif REQUEST.json then - data = JSON.decodeString(REQUEST.json) - exec_with_user_priv(data) - elseif args and #args > 0 then - -- data is encoded in url safe base64 - local encoded = args[1]:gsub('_', '/'):gsub('-', '+') - if #encoded % 4 == 2 then - encoded = encoded.."==" - elseif #encoded %4 == 3 then - encoded = encoded.."=" - end - local decoded = std.b64decode(encoded) - data = JSON.decodeString(bytes.__tostring(decoded)) - if data and data.path then - exec_with_user_priv(data) - else - fail("Unknown request") - end - else - fail("Unkown request") - end - end - print("Child exit") - ulib.kill(-1) - end - else - echo('{"error":"User unauthorized. Please login"}') - end -end diff --git a/os/controllers/UserController.lua b/os/controllers/UserController.lua deleted file mode 100644 index c237326..0000000 --- a/os/controllers/UserController.lua +++ /dev/null @@ -1,97 +0,0 @@ -BaseController:subclass( - "UserController", - { - registry = {}, - models = {} - } -) - -function UserController:actionnotfound(...) - return self:index(table.unpack({...})) -end - -function UserController:index(...) - local api = { - description = "This api handle the user authentification", - actions = { - ["/auth"] = "Return user information if a user is alreay logged in", - ["/login"] = "Perform a login operation", - ["/logout"] = "Perform a logout operation" - } - } - result(api) - return false -end ---[[ - request query: none - return: - -]] -function UserController:auth(...) - auth_or_die("User unauthorized. Please login") - local user = require("uman").userinfo(SESSION.user) - result(user) - return false -end - ---[[ request: - {"username":"mrsang", "password":"pass"} - return: - {} ]] -function UserController:login(...) - if REQUEST.json ~= nil then - local request = JSON.decodeString(REQUEST.json) - local r = ulib.auth(request.username,request.password) - if r == true then - local salt = utils.generate_salt(20) - local cookie = {sessionid=std.sha1(request.username..request.password..salt)} -- iotos_user = request.username - local db = sysdb(); - if db == nil then return fail("Cannot setup session") end - local cond = {exp= {["="] = { sessionid = cookie.sessionid }}} - local data = db:find(cond) - --print(data) - if data == nil or data[1] == nil then - --print("insert new data") - data = {sessionid = cookie.sessionid, username=request.username, stamp=os.time(os.date("!*t"))} - else - data = data[1] - --print("Update old data") - data.stamp = os.time(os.date("!*t")) - end - if data.id == nil then - db:insert(data) - else - db:update(data) - end - db:close() - std.cjson(cookie) - SESSION.user = request.username - local user = { - result = require("uman").userinfo(request.username), - error = false - } - std.t(JSON.encode(user)) - else - fail("Invalid login") - end - else - fail("Invalid request") - end - return false -end - -function UserController:logout(...) - if SESSION.sessionid ~= nil and SESSION.sessionid ~= '0' then - local cookie = {sessionid='0'} - local db = sysdb() - if db ~= nil then - local cond = {["="] = { sessionid = SESSION.sessionid }} - db:delete(cond) - db:close() - end - std.cjson(cookie) - else - std.json() - end - std.t(JSON.encode({error=false,result=true})) -end \ No newline at end of file diff --git a/os/controllers/VDBController.lua b/os/controllers/VDBController.lua deleted file mode 100644 index e590be6..0000000 --- a/os/controllers/VDBController.lua +++ /dev/null @@ -1,130 +0,0 @@ -BaseController:subclass( - "VDBController", - { - registry = {}, - models = {} - } -) - -function VDBController:actionnotfound(...) - return self:index(table.unpack({...})) -end - -function VDBController:index(...) - local api = { - description = "This api handle database operation", - actions = { - ["/save"] = "Save a record to a table", - ["/get"] = "Get all records or Get a record by id", - ["/select"] = "Select records by a condition", - ["/delete"] = "Delete record(s) by condition or by id" - } - } - result(api) - return false -end - -function VDBController:save(...) - auth_or_die("User unauthorized. Please login") - local rq = (JSON.decodeString(REQUEST.json)) - if (rq ~= nil and rq.table ~= nil) then - local model = require("dbmodel").get(SESSION.user, rq.table, rq.data) - local ret - if model == nil then - fail("Cannot get table metadata:" .. rq.table) - else - if (rq.data.id ~= nil) then - rq.data.id = tonumber(rq.data.id) - ret = model:update(rq.data) - else - ret = model:insert(rq.data) - end - model:close() - if ret == true then - result(ret) - else - fail("Cannot modify/update table " .. rq.table) - end - end - else - fail("Unknown database request") - end - return false -end - -function VDBController:get(...) - auth_or_die("User unauthorized. Please login") - local rq = (JSON.decodeString(REQUEST.json)) - if (rq ~= nil and rq.table ~= nil) then - local model = require("dbmodel").get(SESSION.user, rq.table, nil) - local ret - if model == nil then - fail("Cannot get table metadata:" .. rq.table) - else - if (rq.id == nil) then - ret = model:getAll() - else - ret = model:get(rq.id) - end - model:close() - result(ret) - end - else - fail("Unknown database request") - end -end - -function VDBController:select(...) - auth_or_die("User unauthorized. Please login") - local rq = (JSON.decodeString(REQUEST.json)) - if (rq ~= nil and rq.table ~= nil) then - local model = require("dbmodel").get(SESSION.user, rq.table, nil) - local ret - if model == nil then - fail("Cannot get table metadata:" .. rq.table) - else - if (rq.cond == nil) then - model:close() - return fail("Unknow condition") - else - ret = model:find(rq.cond) - end - model:close() - result(ret) - end - else - fail("Unknown database request") - end -end - -function VDBController:delete(...) - auth_or_die("User unauthorized. Please login") - local rq = (JSON.decodeString(REQUEST.json)) - if (rq ~= nil and rq.table ~= nil) then - local model = require("dbmodel").get(SESSION.user, rq.table, nil) - local ret - if model == nil then - fail("Cannot get table metadata:" .. rq.table) - else - if (rq.id == nil) then - if (rq.cond) then - ret = model:delete(rq.cond) - model:close() - else - model:close() - return fail("Unknow element to delete") - end - else - ret = model:deleteByID(rq.id) - model:close() - end - if ret then - result(ret) - else - fail("Querry error or database is locked") - end - end - else - fail("Unknown database request") - end -end diff --git a/os/controllers/VFSController.lua b/os/controllers/VFSController.lua deleted file mode 100644 index a34ab34..0000000 --- a/os/controllers/VFSController.lua +++ /dev/null @@ -1,222 +0,0 @@ -BaseController:subclass( - "VFSController", - { - registry = {}, - models = {} - } -) - -function VFSController:actionnotfound(...) - return self:index(table.unpack({...})) -end - -function VFSController:index(...) - local api = { - description = "This api handle file operations", - actions = { - ["/fileinfo"] = "Get file information", - ["/exists"] = "Check if file exists", - ["/delete"] = "Delete a file", - ["/get"] = "Get a file content", - ["/mkdir"] = "Create directory", - ["/move"] = "Move file to a new destination", - ["/publish"] = "Share a file to all users", - ["/scandir"] = "List all files and folders", - ["/write"] = "Write data to file", - ["/shared"] = "Get shared file content" - } - } - result(api) - return false -end - -function VFSController:fileinfo(...) - auth_or_die("User unauthorized. Please login") - local vfspath = (JSON.decodeString(REQUEST.json)).path - local r, m = require("vfs").fileinfo(vfspath) - if r then - result(m) - else - fail(m) - end - return false -end - -function VFSController:exists(...) - auth_or_die("User unauthorized. Please login") - - local vfs = require("vfs") - local rq = (JSON.decodeString(REQUEST.json)) - - if rq ~= nil then - result(vfs.exists(rq.path)) - else - fail("Uknown request") - end - return false -end - -function VFSController:delete(...) - auth_or_die("User unauthorized. Please login") - - local vfs = require("vfs") - local rq = (JSON.decodeString(REQUEST.json)) - - if rq ~= nil then - local r, e = vfs.delete(rq.path) - if r then - result(r) - else - fail(e) - end - else - fail("Uknown request") - end - return false -end - -function VFSController:get(...) - local args = {...} - local uri = implode(args, "/") - auth_or_die("User unauthorized. Please login") - local vfsfile = utils.decodeURI(uri) - local r, m = require("vfs").checkperm(vfsfile, "read") - if r then - std.sendFile(m) - else - fail(m) - end - - return false -end - -function VFSController:mkdir(...) - auth_or_die("User unauthorized. Please login") - - local rq = (JSON.decodeString(REQUEST.json)) - - if rq ~= nil then - local r, m = require("vfs").mkdir(rq.path) - if r then - result(r) - else - fail(m) - end - else - fail("Uknown request") - end - return false -end - -function VFSController:move(...) - auth_or_die("User unauthorized. Please login") - - local rq = (JSON.decodeString(REQUEST.json)) - - if rq ~= nil then - local r, m = require("vfs").move(rq.src, rq.dest) - if r then - result(r) - else - fail(m) - end - else - fail("Uknown request") - end - return false -end - -function VFSController:publish(...) - auth_or_die("User unauthorized. Please login") - - local rq = (JSON.decodeString(REQUEST.json)) - - if rq ~= nil then - local p = nil - if rq.publish then - p = require("vfs").ospath(rq.path) - else - p = require("shared").ospath(rq.path) - end - local user = SESSION.user - local uid = ulib.uid(user) - local st = ulib.file_stat(p) - if uid.id ~= st.uid then - die("Only the owner can share or unshare this file") - end - local entry = {sid = std.sha1(p), user = SESSION.user, path = p, uid = uid.id} - local db = require("dbmodel").get("sysdb", "shared", entry) - if db == nil then - die("Cannot get system database") - end - local cond = nil - if rq.publish then - cond = {exp = {["="] = {path = p}}} - local data = db:find(cond) - if data == nil or data[0] == nil then - -- insert entry - db:insert(entry) - end - else - cond = {["="] = {sid = rq.path}} - db:delete(cond) - end - db:close() - result(entry.sid) - else - fail("Uknown request") - end - return false -end - -function VFSController:scandir(...) - auth_or_die("User unauthorized. Please login") - local rq = JSON.decodeString(REQUEST.json) - local vfspath = rq.path - local r = require("vfs").readDir(vfspath) - if r == nil then - fail("Resource not found: " .. rq.path) - else - --print(JSON.encode(readDir(ospath, vfspath))) - result(r) - end - return false -end - -function VFSController:upload(...) - auth_or_die("User unauthorized. Please login") - local vfs = require("vfs") - if REQUEST and REQUEST.path then - local r, m = require("vfs").upload(REQUEST.path) - if r then - result(r) - else - self:error(m) - fail(m) - end - else - fail("Invalid query") - end - return false -end - -function VFSController:write(...) - auth_or_die("User unauthorized. Please login") - local rq = (JSON.decodeString(REQUEST.json)) - - if rq ~= nil then - local r, m = require("vfs").write(rq.path, rq.data) - if r then - result(r) - else - fail(m) - end - else - fail("Uknown request") - end - return false -end - -function VFSController:shared(sid) - require("shared").get(sid) -end \ No newline at end of file diff --git a/os/libs/common.lua b/os/libs/common.lua deleted file mode 100644 index 542cf27..0000000 --- a/os/libs/common.lua +++ /dev/null @@ -1,66 +0,0 @@ -require("sqlite") -local TUNNEL_KEYCHAIN = __api__.tmpdir.."/channels/antunnel_keychain" -function fail(msg) - std.custom_header("Connection","close") - std.json() - std.t(JSON.encode({error=msg})) -end - -function result(obj) - std.custom_header("Connection","close") - std.json() - std.t(JSON.encode({result=obj, error=false})) -end - -function die (msg) - fail(msg) - debug.traceback=nil - error("Permission denied") - return false -end - --- check if the sysdb is create, otherwise create the table -function sysdb() - local meta = {} - meta.sessionid = "" - meta.username = "" - meta.stamp = 0 - return require("dbmodel").get("sysdb", "sessions", meta) -end - -function is_auth() - local sessionid = nil - if SESSION.sessionid and SESSION.sessionid ~= '0' then - sessionid = SESSION.sessionid - -- should be used only by API call - elseif REQUEST.sessionid and REQUEST.sessionid ~= '0' then - sessionid = REQUEST.sessionid - elseif REQUEST.access_token and REQUEST.access_token ~= '0' then - sessionid = REQUEST.access_token - end - if sessionid == nil then - return false - end - -- query session id from database - local db = sysdb() - if db == nil then return false end - local cond = {exp= {["="] = { sessionid = sessionid }}} - local data = db:find(cond) - --print(JSON.encode(data)) - db:close() - if data == nil or data[1] == nil then return die("No user data found") end - -- next time check the stamp - SESSION.user = data[1].username - local f = io.open(TUNNEL_KEYCHAIN, "w") - if f then - f:write(sessionid..SESSION.user) - f:close() - end - return true -end - -function auth_or_die(msg) - if(is_auth() == false) then - die(msg) - end -end diff --git a/os/libs/dbmodel.lua b/os/libs/dbmodel.lua deleted file mode 100644 index 4cadb11..0000000 --- a/os/libs/dbmodel.lua +++ /dev/null @@ -1,20 +0,0 @@ -local model = {} - -model.get = function(name, tbl, data) - local db = DBModel:new{db = name, name=tbl} - db:open() - if db:available() then return db end - if data == nil then return nil end - local meta = {} - --print(JSON.encode(data)) - for k,v in pairs(data) do - if type(v) == "number" or type(v) == "boolean" then - meta[k] = "NUMERIC" - else - meta[k] = "TEXT" - end - end - db:createTable(meta) - return db -end -return model \ No newline at end of file diff --git a/os/libs/packages.lua b/os/libs/packages.lua deleted file mode 100644 index ecb3394..0000000 --- a/os/libs/packages.lua +++ /dev/null @@ -1,125 +0,0 @@ -local packages={} -local vfs = require("vfs") -local uid = ulib.uid(SESSION.user) - -packages._cache = function(y) - local p = vfs.ospath(y).."/packages.cache" - if y:find("^os:/") then - p = __api__.tmpdir.."/packages.cache" - end - local f = io.open(p, "w") - local has_cache = false - local i = 1 - local meta = {} - if f then - local files = vfs.readDir(y) - for k,v in pairs(files) do - if v.type == "dir" then - local f1 = io.open(vfs.ospath(v.path.."/package.json")) - if f1 then - - local name = std.basename(v.path) - local mt = JSON.decodeString(f1:read("*all")) - mt.path = v.path - meta[i] ='"'..name..'":'..JSON.encode(mt) - i = i+1 - f1:close() - has_cache = true; - end - end - end - f:write(table.concat(meta, ",")) - f:close() - if has_cache == false then - ulib.delete(p); - end - end -end - --- we will change this later -packages.list = function(paths) - std.json() - std.t("{\"result\" : { ") - local first = true - --std.f(__ROOT__.."/system/packages.json") - for k,v in pairs(paths) do - local osp = vfs.ospath(v.."/packages.cache") - if v:find("^os:/") then - osp = __api__.tmpdir.."/packages.cache" - end - if ulib.exists(osp) == false then - packages._cache(v) - end - if ulib.exists(osp) then - if first == false then - std.t(",") - else - first = false - end - std.f(osp) - end - end - std.t("}, \"error\":false}") -end - --- generate the packages caches -packages.cache = function(args) - -- perform a packages caches - for x,y in pairs(args.paths) do - packages._cache(y) - end - result(true) -end --- install a function from zip file -packages.install = function(args) - local path = vfs.ospath(args.dest) - local zip = vfs.ospath(args.zip) - if(ulib.exists(path) == false) then - -- create directory if not exist - ulib.mkdir(path) - -- change permission - ulib.chown(path, uid.id, uid.gid) - end - -- extract the zip file to it - if(ulib.unzip(zip, path)) then - -- read metadata - local meta = JSON.decodeFile(path.."/metadata.json") - meta.path = args.dest - meta.scope = "user" - local f=io.open(path.."/package.json","w") - if f then - f:write(JSON.encode(meta)) - f:close() - end - result(true) - else - fail("Problem extracting zip file") - end - -end --- uninstall the package -packages.uninstall = function(path) - local osf = vfs.ospath(path) - if(osf and ulib.exists(osf) ) then - --remove it - ulib.delete(osf) - result(true) - else - fail("Cannot find package") - end -end --- set user packages environment -packages.init = function(paths) - if(paths) then - for k,v in pairs(paths) do - local p = vfs.ospath(v) - if p and (ulib.exists(p) == false) then - ulib.mkdir(p) - -- change permission - ulib.chown(p, uid.id, uid.gid) - end - end - end -end - -return packages diff --git a/os/libs/shared.lua b/os/libs/shared.lua deleted file mode 100644 index 6a576ec..0000000 --- a/os/libs/shared.lua +++ /dev/null @@ -1,47 +0,0 @@ -local shared = {} -shared.get = function(sharedid) - if sharedid == "all" then - -- get all shared files - local db = require("dbmodel").get("sysdb", "shared", nil) - if db == nil then die("Cannot get shared database") end - local data = db:getAll() - if data == nil then die("No file found") end - local i = 1 - local ret = {} - for k,v in pairs(data) do - if(ulib.exists(v.path)) then - local r = ulib.file_stat(v.path) - if(r.error == nil) then - r.path = "shared://"..v.sid - r.filename = std.basename(v.path) - if r.mime == "application/octet-stream" then - r.mime = std.extra_mime(r.filename) - end - ret[i] = r - i = i+1 - end - else - local cond = { ["="] = { sid = v.sid } } - db:delete(cond) - end - end - db:close() - --std.json() - result(ret) - else - - local p = shared.ospath(sharedid) - std.sendFile(p) - end -end - -shared.ospath = function(sharedid) - local db = require("dbmodel").get("sysdb", "shared", nil) - if db == nil then die("Cannot get shared database") end - local cond = { exp = { ["="] = { sid = sharedid } } } - local data = db:find(cond) - db:close() - if data == nil or data[1] == nil then die("Cannot get shared file with: "..sharedid) end - return data[1].path -end -return shared; diff --git a/os/libs/uman.lua b/os/libs/uman.lua deleted file mode 100644 index b268e9b..0000000 --- a/os/libs/uman.lua +++ /dev/null @@ -1,27 +0,0 @@ -local uman={} - -uman.userinfo = function(user) - local info = {} - local uid = ulib.uid(user) - if uid then - -- read the setting - -- use the decodeFile function of JSON instead - local file = require('vfs').ospath("home:///").."/.settings.json" - local st = JSON.decodeFile(file) - if(st) then - info = st - end - info.user = { - username = user, - id = uid.id, - name = user, - groups = uid.groups - } - --print(JSON.encode(info)) - return info - else - return {} - end -end - -return uman \ No newline at end of file diff --git a/os/libs/vfs.lua b/os/libs/vfs.lua deleted file mode 100644 index b1d9b85..0000000 --- a/os/libs/vfs.lua +++ /dev/null @@ -1,234 +0,0 @@ -local vfs = {} - - -vfs.ospath = function(path) - local user = SESSION.user - local prefix = string.match(path, "%a+:/") - if(prefix ~= nil) then - local suffix = string.gsub(path,prefix,"") - if prefix == "home:/" then - return string.format(VFS_HOME,user)..'/'..suffix - elseif prefix == "desktop:/" then - return string.format(VFS_HOME,user).."/.desktop/"..suffix - elseif prefix == "shared:/" then - return require("shared").ospath(std.trim(suffix,"/")) - elseif prefix == "os:/" then - return WWW_ROOT.."/"..suffix - else - return nil - end - else - return nil; - end -end - - -vfs.delete = function(path) - local r,m = vfs.checkperm(path,"write") - if r then - if ulib.delete(m) then - -- change permission - return true,nil - else - return false,"Cant not delete the file" - end - else - return r,m - end -end - -vfs.exists = function(path) - local osfile = vfs.ospath(path) - return ulib.exists(osfile) -end - -vfs.fileinfo = function(vfspath) - local ospath = vfs.ospath(vfspath) - if ospath then - if(ulib.exists(ospath) == false) then return false,"File not found" end - local r = ulib.file_stat(ospath) - if(r.error ~= nil) then return false,r.error end - r.path = vfspath - r.name = std.basename(vfspath) - if r.mime == "application/octet-stream" then - r.mime = std.extra_mime(r.name) - end - return true,r - else - return false,"Resource not found" - end -end - -vfs.mkdir = function(path) - local file = std.basename(path) - local folder = string.gsub(path, utils.escape_pattern(file).."$","") - local r,m = vfs.checkperm(folder,"write") - - if r then - local osfile = m.."/"..file - local uid = ulib.uid(SESSION.user) - ulib.mkdir(osfile) - -- change permission - ulib.chown(osfile, uid.id, uid.gid) - return true,nil - else - return r,m - end -end - -vfs.move = function(src,dest) - local file = std.basename(dest) - local folder = string.gsub(dest, utils.escape_pattern(file),"") - - local sp,sm = vfs.checkperm(src,"write") - if sp then - local dp,dm = vfs.checkperm(folder,"write") - if dp then - ulib.move(sm,dm.."/"..file) - -- change permission - return true,nil - else - return dp,dm - end - else - return sp,sm - end -end - -vfs.write = function(path,data) - local file = std.basename(path) - local folder = string.gsub(path, utils.escape_pattern(file),"") - - local r,m = vfs.checkperm(folder,"write") - if r then - local osfile = m.."/"..file - - if ulib.exists(osfile) then - local r1,m1 = vfs.checkperm(path,"write") - if not r1 then - return r1, m1..": "..path - end - end - - local uid = ulib.uid(SESSION.user) - -- - if data ~= "" then - local header = string.match(data, "^data%:[%w%.-%+]+%/[%w%.-%+]+;base64,") - if header ~= nil then - local b64data = string.gsub(data, utils.escape_pattern(header),"") - local barr = std.b64decode(b64data) - bytes.write(barr,osfile) - --[[ if std.isBinary(osfile) then - - else - local f = io.open(osfile, "w") - f:write(bytes.__tostring(barr)) - f:close() - end ]] - else - return false, "Wrong data format" - end - else - bytes.write(bytes.new(0),osfile) - end - --f:close() - -- change permission - ulib.chown(osfile, uid.id, uid.gid) - return true,nil - else - return r,m..": "..folder - end -end - -vfs.upload = function(path) - if(not path) then - return false, "Unknown upload destination, abort!" - end - local r,m = vfs.checkperm(path,"write") - if(r) then - local uid = ulib.uid(SESSION.user) - local index = 0 - while(REQUEST["upload-"..index..".tmp"] ~= nil) do - local file = m.."/"..REQUEST["upload-"..index..".file"] - local ret = ulib.move(REQUEST["upload-"..index..".tmp"], file) - if not ret then - ret = ulib.send_file(REQUEST["upload-"..index..".tmp"], file) - ulib.delete(REQUEST["upload-"..index..".tmp"]) - end - if not ret then - return false, "Unable to copy file" - end - ulib.chown(file, uid.id, uid.gid) - index = index + 1 - end - if(index == 0) then - return false, "No file is uploaded" - end - return true, index - else - return r,m - end -end - -vfs.checkperm = function(path, right) - local osfile = vfs.ospath(path) - local perm = vfs.perm(osfile) - --print(osfile) - if not ulib.exists(osfile) then - return false,"Resource does not exist" - end - -- check if user own the file - if perm ~= nil then - if perm[right] == true then - --print("Permission granted") - return true,osfile - else - print("Permission denie") - return false,"You dont have "..right.." permission on this file" - end - else - return false,"User is unrecognized" - end -end - -vfs.perm = function(file) - local user = SESSION.user - local uid = ulib.uid(user) - local st = ulib.file_stat(file) - -- check if user own the file - if uid ~= nil and st ~= nil and st.perm ~= nil then - --print(JSON.encode({uid, st})) - if(uid.id == st.uid) then -- the user owned the file - --print("file belong to user") - return st.perm.owner - elseif uid.groups and uid.groups[st.gid] then - --print("User belong to this group") - return st.perm.group - else - --print("User belong to other") - return st.perm.other - end - else - return nil - end -end - -vfs.readDir = function(vfspath) - if(string.sub(vfspath,-1) == "/") then - prefix = string.sub(vfspath,1,-2) - else - prefix = vfspath - end - local ospath = vfs.ospath(vfspath,SESSION.user) - local r = ulib.read_dir(ospath, prefix) - if(r.error ~= nil) then return nil end - -- add extra mime type - for k,v in pairs(r) do - if v.mime == "application/octet-stream" then - v.mime = std.extra_mime(v.filename) - end - end - return r -end - -return vfs diff --git a/os/router.lua b/os/router.lua deleted file mode 100644 index de5454f..0000000 --- a/os/router.lua +++ /dev/null @@ -1,45 +0,0 @@ - --- the rewrite rule for the framework --- should be something like this --- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>& --- some global variables -DIR_SEP = "/" -WWW_ROOT = __ROOT__.."/os" -if HEADER.Host then - HTTP_ROOT= "https://"..HEADER.Host -else - HTTP_ROOT = "https://os.lxsang.me" -end --- class path: path.to.class -BASE_FRW = "" --- class path: path.to.class -CONTROLLER_ROOT = BASE_FRW.."os.controllers" -MODEL_ROOT = BASE_FRW.."os.models" --- file path: path/to/file -VIEW_ROOT = WWW_ROOT..DIR_SEP.."views" -LOG_ROOT = WWW_ROOT..DIR_SEP.."logs" - -VFS_HOME = "/home/%s" - -package.path = package.path..";"..WWW_ROOT .. '/libs/?.lua' --- require needed library -require(BASE_FRW.."silk.api") -require("common") - - --- registry object store global variables -local REGISTRY = {} --- set logging level -REGISTRY.logger = Logger:new{ levels = {INFO = false, ERROR = true, DEBUG = false}} ---REGISTRY.db = DBHelper:new{db="sysdb"} -REGISTRY.layout = 'default' -REGISTRY.fileaccess = true - ---REGISTRY.db:open() -local router = Router:new{registry = REGISTRY} -REGISTRY.router = router -router:setPath(CONTROLLER_ROOT) - -router:delegate() ---if REGISTRY.db then REGISTRY.db:close() end - diff --git a/silk/BaseController.lua b/silk/BaseController.lua deleted file mode 100644 index 29644ad..0000000 --- a/silk/BaseController.lua +++ /dev/null @@ -1,108 +0,0 @@ --- create class -BaseObject:subclass("BaseController", - { - registry = {}, - models = {}, - main = false - }) - --- set the name here in each subclasses -function BaseController:initialize() - for k, v in pairs(self.models) do - --- infer the class here - local modelname = firstToUpper(v).."Model" - local path = MODEL_ROOT.."."..modelname - -- require it - pcall(require, path) - --require(controller_path) - if not _G[modelname] then - self:modelnotfound(v) - else - -- create new model object - self[v] = _G[modelname]:new{registry = self.registry} - end - end - -- create template - self.template = Template:new{registry = self.registry} -end - -function BaseController:print(...) - return self:actionnotfound("print") -end - -function BaseController:redirect(url) - std.status(301, "Moved Permanently") - std.custom_header("Content-Type","text/html") - std.custom_header("Location", url) - std.header_flush() -end - -function BaseController:switchLayout(name) - if self.main then - self.registry.layout = name - else - self:log("Cannot switch layout since the controller "..self.class.." is not the main controller") - end -end - -function BaseController:setSession(key, value) - SESSION[key] = value -end -function BaseController:getSession(key) - return SESSION[key] -end -function BaseController:removeSession(key) - self:setSession(key, nil) -end -function BaseController:index(...) - self:error("#index: subclasses responsibility") -end - --- not found action -function BaseController:actionnotfound(...) - local args = {...} - self:error("#action "..args[1].." is not found in controller "..self.class) -end --- not found model -function BaseController:modelnotfound(...) - local args = {...} - self:error("Model "..firstToUpper(args[1]).."Model is not found in controller "..self.class) -end --- The not found controller - -BaseController:subclass("NotfoundController",{ registry = {}, models = {} }) - -function NotfoundController:index(...) - local args = {...} - local error = args[2] or "" - if self.template:path() then - self.template:set("error", error) - self.template:set("title", "404 not found") - return true - end - self:error("404: Controller "..args[1].." not found : "..error) - return false -end - --- The asset controller for the static file -BaseController:subclass("AssetController", {registry={}, models={}}) -function AssetController:index(...) - local args = {...} - return self:get(table.unpack(args)) -end - -function AssetController:get(...) - local path = WWW_ROOT..DIR_SEP..implode({...}, DIR_SEP) - - if self.registry.fileaccess and ulib.exists(path) then - local mime = std.mimeOf(path) - if POLICY.mimes[mime] then - std.sendFile(path) - else - self:error("Access forbidden: "..path) - end - else - self:error("Asset file not found or access forbidden: "..path) - end - return false -end \ No newline at end of file diff --git a/silk/BaseModel.lua b/silk/BaseModel.lua deleted file mode 100644 index f26b304..0000000 --- a/silk/BaseModel.lua +++ /dev/null @@ -1,51 +0,0 @@ --- create class -BaseObject:subclass("BaseModel", {registry = {}}) - -function BaseModel:initialize() - self.db = self.registry.db - if self.db and self.name and self.name ~= "" and self.fields and - not self.db:available(self.name) then - self.db:createTable(self.name, self.fields) - end -end - -function BaseModel:create(m) - if self.db and m then return self.db:insert(self.name, m) end - return false -end - -function BaseModel:update(m) - if self.db and m then return self.db:update(self.name, m) end - return false -end - -function BaseModel:delete(cond) - if self.db and cond then return self.db:delete(self.name, cond) end - return false -end - -function BaseModel:find(cond) - if self.db and cond then return self.db:find(self.name, cond) end - return false -end - -function BaseModel:get(id) - local data, order = self:find({exp = {["="] = {id = id}}}) - if not data or #order == 0 then return false end - return data[1] -end - -function BaseModel:findAll() - if self.db then return self.db:getAll(self.name) end - return false -end - -function BaseModel:query(sql) - if self.db then return self.db:query(sql) end - return false -end - -function BaseModel:select(sel, sql_cnd) - if self.db then return self.db:select(self.name, sel, sql_cnd) end - return nil -end diff --git a/silk/BaseObject.lua b/silk/BaseObject.lua deleted file mode 100644 index efc5935..0000000 --- a/silk/BaseObject.lua +++ /dev/null @@ -1,34 +0,0 @@ -BaseObject = Object:extends{registry = {}, class="BaseObject"} -function BaseObject:subclass(name, args) - _G[name] = self:extends(args) - _G[name].class = name -end - -function BaseObject:log(msg, level) - level = level or "INFO" - if self.registry.logger then - self.registry.logger:log(msg,level) - end -end - -function BaseObject:debug(msg) - self:log(msg, "DEBUG") -end - -function BaseObject:print() - print(self.class) -end - -function BaseObject:error(msg, trace) - html() - --local line = debug.getinfo(1).currentline - echo(msg) - self:log(msg,"ERROR") - if trace then - debug.traceback=nil - error(msg) - else - error(msg) - end - return false -end \ No newline at end of file diff --git a/silk/DBHelper.lua b/silk/DBHelper.lua deleted file mode 100644 index 5ee9c3f..0000000 --- a/silk/DBHelper.lua +++ /dev/null @@ -1,144 +0,0 @@ -sqlite = modules.sqlite() - -if sqlite == nil then return 0 end --- create class -BaseObject:subclass("DBHelper", {db = {}}) - -function DBHelper:createTable(tbl, m) - if self:available(tbl) then return true end - local sql = "CREATE TABLE " .. tbl .. "(id INTEGER PRIMARY KEY" - for k, v in pairs(m) do - if k ~= "id" then sql = sql .. "," .. k .. " " .. v end - end - sql = sql .. ");" - return sqlite.query(self.db, sql) == 1 -end - -function DBHelper:insert(tbl, m) - local keys = {} - local values = {} - for k, v in pairs(m) do - if k ~= "id" then - table.insert(keys, k) - if type(v) == "number" then - table.insert(values, v) - else - local t = "\"" .. v:gsub('"', '""') .. "\"" - table.insert(values, t) - end - end - end - local sql = "INSERT INTO " .. tbl .. " (" .. table.concat(keys, ',') .. - ') VALUES (' - sql = sql .. table.concat(values, ',') .. ');' - return sqlite.query(self.db, sql) == 1 -end - -function DBHelper:get(tbl, id) - return sqlite.select(self.db, tbl, "*", "id=" .. id)[1] -end - -function DBHelper:getAll(tbl) - local data = sqlite.select(self.db, tbl, "*", "1=1") - if data == nil then return nil end - local a = {} - for n in pairs(data) do table.insert(a, n) end - table.sort(a) - return data, a -end - -function DBHelper:find(tbl, cond) - local cnd = "1=1" - local sel = "*" - if cond.exp then cnd = self:gencond(cond.exp) end - if cond.order then - cnd = cnd .. " ORDER BY " - local l = {} - local i = 1 - for k, v in pairs(cond.order) do - l[i] = k .. " " .. v - i = i + 1 - end - cnd = cnd .. table.concat(l, ",") - end - if cond.limit then cnd = cnd .. " LIMIT " .. cond.limit end - if cond.fields then - sel = table.concat(cond.fields, ",") - -- print(sel) - end - local data = sqlite.select(self.db, tbl, sel, cnd) - if data == nil then return nil end - local a = {} - for n in pairs(data) do table.insert(a, n) end - table.sort(a) - return data, a -end - -function DBHelper:select(tbl, sel, cnd) - local data = sqlite.select(self.db, tbl, sel, cnd) - if data == nil then return nil end - local a = {} - for n in pairs(data) do table.insert(a, n) end - table.sort(a) - return data, a -end - -function DBHelper:query(sql) return sqlite.query(self.db, sql) == 1 end - -function DBHelper:update(tbl, m) - local id = m['id'] - if id ~= nil then - local lst = {} - for k, v in pairs(m) do - if (type(v) == "number") then - table.insert(lst, k .. "=" .. v) - else - table.insert(lst, k .. "=\"" .. v:gsub('"', '""') .. "\"") - end - end - local sql = "UPDATE " .. tbl .. " SET " .. table.concat(lst, ",") .. - " WHERE id=" .. id .. ";" - return sqlite.query(self.db, sql) == 1 - end - return false -end - -function DBHelper:available(tbl) return sqlite.hasTable(self.db, tbl) == 1 end -function DBHelper:deleteByID(tbl, id) - local sql = "DELETE FROM " .. tbl .. " WHERE id=" .. id .. ";" - return sqlite.query(self.db, sql) == 1 -end -function DBHelper:gencond(o) - for k, v in pairs(o) do - if k == "and" or k == "or" then - local cnd = {} - local i = 1 - for k1, v1 in pairs(v) do - cnd[i] = self:gencond(v1) - i = i + 1 - end - return " (" .. table.concat(cnd, " " .. k .. " ") .. ") " - else - for k1, v1 in pairs(v) do - local t = type(v1) - if (t == "string") then - return - " (" .. k1 .. " " .. k .. ' "' .. v1:gsub('"', '""') .. - '") ' - end - return " (" .. k1 .. " " .. k .. " " .. v1 .. ") " - end - end - end -end -function DBHelper:delete(tbl, cond) - local sql = "DELETE FROM " .. tbl .. " WHERE " .. self:gencond(cond) .. ";" - return sqlite.query(self.db, sql) == 1 -end - -function DBHelper:lastInsertID() return sqlite.lastInsertID(self.db) end - -function DBHelper:close() if self.db then sqlite.dbclose(self.db) end end -function DBHelper:open() - if self.db ~= nil then self.db = sqlite.getdb(self.db) end -end diff --git a/silk/Logger.lua b/silk/Logger.lua deleted file mode 100644 index 4cdcc5b..0000000 --- a/silk/Logger.lua +++ /dev/null @@ -1,30 +0,0 @@ -Logger = Object:extends{levels = {}} - -function Logger:initialize() -end - -function Logger:log(msg,level) - if self.levels[level] and ulib.exists(LOG_ROOT) then - local path = LOG_ROOT..DIR_SEP..level..'.txt' - local f = io.open(path, 'a') - local text = '['..level.."]: "..msg - if f then - f:write(text..'\n') - f:close() - end - print(text) - end -end - -function Logger:info(msg) - self:log(msg, "INFO") -end - -function Logger:debug(msg) - self:log(msg, "DEBUG") -end - - -function Logger:error(msg) - self:log(msg, "ERROR") -end \ No newline at end of file diff --git a/silk/Router.lua b/silk/Router.lua deleted file mode 100644 index 855bbe0..0000000 --- a/silk/Router.lua +++ /dev/null @@ -1,164 +0,0 @@ ---define the class -BaseObject:subclass("Router", {registry = {}}) -function Router:setPath(path) - self.path = path -end - -function Router:initialize() - self.routes = {} - self.remaps = {} -end - ---function Router:setArgs(args) --- self.args = args ---end - ---function Router:arg(name) --- return self.args[name] ---end - -function Router:infer(url) - -- a controller is like this /a/b/c/d/e - -- a is controller name - -- b is action - -- c,d,e is parameters - -- if user dont provide the url, try to infer it - -- from the REQUEST - url = url or REQUEST.r or "" - url = std.trim(url, "/") - local args = explode(url, "/") - local data = { - name = "index", - action = "index", - args = {} - } - if args and #args > 0 and args[1] ~= "" then - data.name = args[1]:gsub("%.", "") - if args[2] then - data.action = args[2]:gsub("%.", "") - end - for i = 3, #args do - table.insert(data.args, args[i]) - end - end - - -- remap if needed - if self.remaps[data.name] ~= nil then - data.name = self.remaps[data.name] - end - -- find the controller class and init it - local controller_name = firstToUpper(data.name) .. "Controller" - local controller_path = self.path .. "." .. controller_name - -- require the controller module - -- ignore the error - local r, e = pcall(require, controller_path) - --require(controller_path) - if not _G[controller_name] then - -- verify if it is an asset - url = url:gsub("/", DIR_SEP) - local filepath = WWW_ROOT..DIR_SEP..url - if ulib.exists(filepath) then -- and not std.is_dir(filepath) - data.controller = AssetController:new {registry = self.registry} - data.action = "get" - data.name = "asset" - data.args ={url} - else - -- let the notfound controller handle the error - data.controller = NotfoundController:new {registry = self.registry} - data.args = {controller_name, e} - data.action = "index" - data.name = "notfound" - end - else - -- create the coresponding controller - data.controller = _G[controller_name]:new {registry = self.registry} - if not data.controller[data.action] then - --data.args = {data.action} - table.insert(data.args, 1, data.action) - data.action = "actionnotfound" - end - end - - self:log("Controller: " .. data.controller.class .. ", action: "..data.action..", args: ".. JSON.encode(data.args)) - return data -end - -function Router:delegate() - local views = {} - local data = self:infer() - -- set the controller to the main controller - data.controller.main = true - views.__main__ = self:call(data) - if not views.__main__ then - --self:error("No view available for this action") - return - end - -- get all visible routes - local routes = self:dependencies(data.name .. "/" .. data.action) - for k, v in pairs(routes) do - data = self:infer(v) - views[k] = self:call(data) - end - -- now require the main page to put the view - local view_args = {} - local view_argv = {} - for k,v in pairs(views) do - table.insert( view_args, k ) - table.insert( view_argv, v ) - end - - local fn, e = loadscript(VIEW_ROOT .. DIR_SEP .. self.registry.layout .. DIR_SEP .. "layout.ls", view_args) - html() - if fn then - local r, o = pcall(fn, table.unpack(view_argv)) - if not r then - self:error(o) - end - else - e = e or "" - self:error("The index page is not found for layout: " .. self.registry.layout..": "..e) - end -end - -function Router:dependencies(url) - if not self.routes[self.registry.layout] then - return {} - end - local list = {} - --self:log("comparing "..url) - for k, v in pairs(self.routes[self.registry.layout]) do - v.url = std.trim(v.url, "/") - if v.visibility == "ALL" then - list[k] = v.url - elseif v.visibility.routes then - if v.visibility.shown == true or v.visibility.shown == nil then - if v.visibility.routes[url] then - list[k] = v.url - end - else - if not v.visibility.routes[url] then - list[k] = v.url - end - end - end - end - return list -end - -function Router:call(data) - data.controller.template:setView(data.action, data.name) - local obj = data.controller[data.action](data.controller, table.unpack(data.args)) - if obj then - return data.controller.template - else - return false - end -end - -function Router:remap(from, to) - self.remaps[from] = to -end - -function Router:route(layout, dependencies) - self.routes[layout] = dependencies -end diff --git a/silk/Template.lua b/silk/Template.lua deleted file mode 100644 index 38eeb7c..0000000 --- a/silk/Template.lua +++ /dev/null @@ -1,58 +0,0 @@ --- create class - BaseObject:subclass("Template",{registry = {}}) - -function Template:initialize() - self.vars = {} -end - -function Template:set(k, v, ow) - if not self.vars[k] or (self.vars[k] and ow) then - self.vars[k] = v - end -end - -function Template:get(k) - return self.vars[k] -end - -function Template:remove(k) - self.vars[k] = nil -end - --- infer view path -function Template:setView(name, controller) - self.name = name - if controller then - self.controller = controller - end -end -function Template:path() - local path = VIEW_ROOT..DIR_SEP..self.registry.layout..DIR_SEP..self.controller..DIR_SEP..self.name..".ls" - if ulib.exists(path) then - return path - else - return false, path - end -end --- render the page -function Template:render() - local path, err = self:path() - if not path then - return self:error("View not found: "..err) - end - local args = {} - local argv = {} - for k, v in pairs(self.vars) do - table.insert( args, k ) - table.insert( argv,v ) - end - local fn, e = loadscript(self:path(), args) - if fn then - local r,o = pcall(fn, table.unpack(argv)) - if not r then - self:error(o) - end - else - self:error(e) - end -end \ No newline at end of file diff --git a/silk/api.lua b/silk/api.lua deleted file mode 100644 index 6dc685d..0000000 --- a/silk/api.lua +++ /dev/null @@ -1,57 +0,0 @@ -require("OOP") -ulib = require("ulib") -require(BASE_FRW.."silk.BaseObject") -require(BASE_FRW.."silk.DBHelper") -require(BASE_FRW.."silk.Router") -require(BASE_FRW.."silk.BaseController") -require(BASE_FRW.."silk.BaseModel") -require(BASE_FRW.."silk.Logger") -require(BASE_FRW.."silk.Template") - --- mime type allows --- this will bypass the default server security --- the default list is from the server setting -POLICY = {} -POLICY.mimes = { - ["application/javascript"] = true, - ["image/bmp"] = true, - ["image/jpeg"] = true, - ["image/png"] = true, - ["text/css"] = true, - ["text/markdown"] = true, - ["text/csv"] = true, - ["application/pdf"] = true, - ["image/gif"] = true, - ["text/html"] = true, - ["application/json"] = true, - ["application/javascript"] = true, - ["image/x-portable-pixmap"] = true, - ["application/x-rar-compressed"] = true, - ["image/tiff"] = true, - ["application/x-tar"] = true, - ["text/plain"] = true, - ["application/x-font-ttf"] = true, - ["application/xhtml+xml"] = true, - ["application/xml"] = true, - ["application/zip"] = true, - ["image/svg+xml"] = true, - ["application/vnd.ms-fontobject"] = true, - ["application/x-font-woff"] = true, - ["application/x-font-otf"] = true, - ["audio/mpeg"] = true, - -} - - -HEADER_FLAG = false - -function html() - if not HEADER_FLAG then - std.chtml(SESSION) - HEADER_FLAG = true - end -end - -function import(module) - return require(BASE_FRW.."silk.api."..module) -end \ No newline at end of file diff --git a/silk/router.lua.tpl b/silk/router.lua.tpl deleted file mode 100644 index 5eb7f2f..0000000 --- a/silk/router.lua.tpl +++ /dev/null @@ -1,54 +0,0 @@ - --- the rewrite rule for the framework --- should be something like this --- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>& --- some global variables -DIR_SEP = "/" -WWW_ROOT = "/opt/www/htdocs/apps" -HTTP_ROOT = "https://apps.localhost:9195/" --- class path: path.to.class -BASE_FRW = "" --- class path: path.to.class -CONTROLLER_ROOT = BASE_FRW.."apps.controllers" -MODEL_ROOT = BASE_FRW.."apps.models" --- file path: path/to/file -VIEW_ROOT = WWW_ROOT..DIR_SEP.."views" -LOG_ROOT = WWW_ROOT..DIR_SEP.."logs" - --- require needed library -require(BASE_FRW.."silk.api") - --- registry object store global variables -local REGISTRY = {} --- set logging level -REGISTRY.logger = Logger:new{ levels = {INFO = true, ERROR = true, DEBUG = true}} -REGISTRY.db = DBHelper:new{db="iosapps"} -REGISTRY.layout = 'default' - -REGISTRY.db:open() -local router = Router:new{registry = REGISTRY} -REGISTRY.router = router -router:setPath(CONTROLLER_ROOT) ---router:route('edit', 'post/edit', "ALL" ) - --- example of depedencies to the current main route --- each layout may have different dependencies -local default_routes_dependencies = { - edit = { - url = "post/edit", - visibility = { - shown = true, - routes = { - ["post/index"] = true - } - } - }, - --category = { - -- url = "cat/index", - -- visibility = "ALL" - --} -} -router:route('default', default_routes_dependencies ) -router:delegate() -if REGISTRY.db then REGISTRY.db:close() end - diff --git a/talk/assets/quicktalk.js b/talk/assets/quicktalk.js index 4393d87..3bd1121 100644 --- a/talk/assets/quicktalk.js +++ b/talk/assets/quicktalk.js @@ -21,7 +21,15 @@ class QuickTalk { this.instant_compose.parentNode.removeChild(this.instant_compose); } this.instant_compose = this.compose(editor, 0, true, (data) => { - this.show_comment(container, data, true).scrollIntoView(); + this.show_comment(container, data, true); + if(this.options.page) + { + this.options.page.scrollTop = this.options.page.scrollHeight; + } + else + { + window.scrollTo(0, document.body.scrollHeight); + } }); }); } @@ -199,9 +207,9 @@ class QuickTalk { container.appendChild(preview); container.appendChild(footer); at.appendChild(container); - if (this.options.page) { - this.options.page.scrollTop = container.offsetTop - this.options.page.offsetTop; - } + //if (this.options.page) { + // this.options.page.scrollTop = container.offsetTop - this.options.page.offsetTop; + //} //container.scrollIntoView(); return container; @@ -228,6 +236,7 @@ class QuickTalk { show_comment(at, comment, show_footer) { let container = document.createElement("div"); container.setAttribute("class", "quick-talk-comment"); + container.setAttribute("id", "comment-" + comment.id); let header = document.createElement("div"); header.setAttribute("class", "quick-talk-comment-header"); let username = document.createElement("span"); @@ -265,7 +274,8 @@ class QuickTalk { this.instant_compose.parentNode.removeChild(this.instant_compose); } this.instant_compose = this.compose(editor, parseInt(comment.id), true, (data) => { - this.show_comment(sub_comments, data, false).scrollIntoView(); + this.show_comment(sub_comments, data, false); + //.scrollIntoView(); }); }); container.appendChild(footer); diff --git a/talk/controllers/CommentController.lua b/talk/controllers/CommentController.lua index 4cf9260..d41045a 100644 --- a/talk/controllers/CommentController.lua +++ b/talk/controllers/CommentController.lua @@ -10,6 +10,7 @@ local function process_md(input) end local function sendmail(to, subject, content) + LOG_DEBUG("Sending email to %s", to) local from = "From: contact@iohub.dev\nTo: " .. to .. "\n" local suject = "Subject: " .. subject .. "\n" @@ -31,17 +32,17 @@ function CommentController:index(...) end local rq = (JSON.decodeString(REQUEST.json)) if (rq) then - local pages, order = self.pages:find({exp = {["="] = {uri = rq.page}}}) + local pages, order = self.pages:find({where = {uri = rq.page}}) if not pages or #order == 0 then fail("Be the first to comment") else local pid = pages[1].id - local comments, order = self.comment:find( - { - exp = { - ["and"] = {{["="] = {pid = pid}}, {[" = "] = {rid = 0}}} + local comments, order = self.comment:find({ + where = { + pid = pid, + rid = 0 }, - order = {time = "ASC"}, + order = {"time$asc"}, fields = {"id", "time", "name", "rid", "pid", "content"} }) if not comments or #order == 0 then @@ -55,13 +56,11 @@ function CommentController:index(...) local sub_comments, suborder = self.comment:find( { - exp = { - ["and"] = { - {["="] = {pid = pid}}, - {[" = "] = {rid = data.id}} - } + where = { + pid = pid, + rid = data.id }, - order = {time = "ASC"} + order = {"time$asc"} }) if sub_comments and #suborder ~= 0 then @@ -92,7 +91,7 @@ function CommentController:post(...) end local rq = (JSON.decodeString(REQUEST.json)) if rq then - local pages, order = self.pages:find({exp = {["="] = rq.page}}) + local pages, order = self.pages:find({where = rq.page}) if not pages or #order == 0 then -- insert data if self.pages:create(rq.page) then @@ -120,19 +119,27 @@ function CommentController:post(...) ".\nBest regards,\nEmail automatically sent by QuickTalk API") end -- send mail to all users of current page - local cmts, cmti = self.comment:select("MIN(id) as id,email", - "pid=" .. rq.comment.pid .. - " AND email != '" .. - rq.comment.email .. - "' GROUP BY email") + local cmts, cmti = self.comment:find( + { + where = { + pid = rq.comment.pid, + ["email$ne"] = rq.comment.email + }, + fields = {"id", "email"} + }) + -- check duplicate email if cmts and #cmti > 0 then + local sent = {} for idx, v in pairs(cmti) do - sendmail(cmts[v].email, rq.comment.name .. + if not sent[cmts[v].email] then + sendmail(cmts[v].email, rq.comment.name .. " has written something on a page that you've commented on", rq.comment.name .. " has written something on a page that you've commented. \nPlease visit this page: " .. - rq.page.uri .. + rq.page.uri.. " for updates on the discussion.\nBest regards,\nEmail automatically sent by QuickTalk API") + sent[cmts[v].email] = true + end end end rq.comment.email = "" diff --git a/talk/router.lua b/talk/router.lua index daadb25..28857e3 100644 --- a/talk/router.lua +++ b/talk/router.lua @@ -2,6 +2,13 @@ -- should be something like this -- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>& -- some global variables +package.path = _SERVER["LIB_DIR"].."/lua/?.lua" +require("silk.api") +-- crypto lib +enc = require("enc") +WWW_ROOT = __ROOT__.."/talk" +-- TODO change me +DB_FILE = "/home/dany/databases/quicktalk.db" function fail(msg) std.json() std.t(JSON.encode({error = msg})) @@ -12,34 +19,30 @@ function result(obj) std.t(JSON.encode({result = obj, error = false})) end DIR_SEP = "/" -WWW_ROOT = __ROOT__ .. "/talk" if HEADER.Host then HTTP_ROOT = "https://" .. HEADER.Host else HTTP_ROOT = "https://talk.iohub.dev" end --- class path: path.to.class -BASE_FRW = "" --- class path: path.to.class -CONTROLLER_ROOT = BASE_FRW .. "talk.controllers" -MODEL_ROOT = BASE_FRW .. "talk.models" --- file path: path/to/file -VIEW_ROOT = WWW_ROOT .. DIR_SEP .. "views" -LOG_ROOT = WWW_ROOT .. DIR_SEP .. "logs" --- require needed library -require(BASE_FRW .. "silk.api") +-- TODO remove me +HTTP_ROOT = HTTP_ROOT.."/next/talk" + +-- class path: path.to.class +CONTROLLER_ROOT = "talk.controllers" +MODEL_ROOT = "talk.models" +-- file path: path/to/file +VIEW_ROOT = WWW_ROOT..DIR_SEP.."views" + -- registry object store global variables local REGISTRY = {} -- set logging level -REGISTRY.logger = Logger:new{ - levels = {INFO = false, ERROR = false, DEBUG = false} -} +REGISTRY.logger = Logger:new{ level = Logger.INFO} REGISTRY.layout = 'default' REGISTRY.fileaccess = true -REGISTRY.db = DBHelper:new{db = "quicktalk"} +REGISTRY.db = DBModel:new{db = DB_FILE} REGISTRY.db:open() local router = Router:new{registry = REGISTRY}