all repos — litestore @ 4603541ab2899aa060d97158da1a7803efe7013b

A minimalist nosql document store.

Finished refactoring components.
* Closes #23.
h3rald h3rald@h3rald.com
Sat, 16 May 2015 14:36:09 +0200
commit

4603541ab2899aa060d97158da1a7803efe7013b

parent

8b44ecc4b7de1f2e39aad779190d1391ded9782c

M admin/index.htmladmin/index.html

@@ -37,12 +37,12 @@ <script src="js/components/navbar.js"> </script>

<script src="js/components/uploader.js"> </script> <script src="js/components/editor.js"> </script> <script src="js/components/doclist.js"> </script> - <script src="js/modules/info.js"> </script> - <script src="js/modules/tags.js"> </script> - <script src="js/modules/htmldoc.js"> </script> - <script src="js/modules/guide.js"> </script> - <script src="js/modules/document.js"> </script> - <script src="js/modules/search.js"> </script> + <script src="js/components/info.js"> </script> + <script src="js/components/tags.js"> </script> + <script src="js/components/htmldoc.js"> </script> + <script src="js/components/guide.js"> </script> + <script src="js/components/document.js"> </script> + <script src="js/components/search.js"> </script> <script src="js/app.js"> </script> </body> -</html>+</html>
M admin/js/components/doclist.jsadmin/js/components/doclist.js

@@ -2,7 +2,6 @@ (function(){

'use strict'; var app = window.LS || (window.LS = {}); var u = app.utils; - var w = app.widgets; app.doclist = {};

@@ -25,10 +24,10 @@ obj.title = m("a", {href: path+args.id, config: m.route}, [args.id]);

obj.content = m("div", [ m("p", [args.content]), m("p", args.tags.map(function(tag){ - return w.taglink({name: tag, key: u.guid()}); + return u.taglink({name: tag, key: u.guid()}); })) ]); - return m(".row.search-result", m(".col-md-12", [w.panel(obj)])); + return m(".row.search-result", m(".col-md-12", [u.panel(obj)])); } };

@@ -49,9 +48,9 @@

return m("section", [ m(".row", [args.title]), m(".row", [args.subtitle]), - m(".row.text-center", [w.paginator(args.querydata)]), + m(".row.text-center", [u.paginator(args.querydata)]), results, - m(".row.text-center", [w.paginator(args.querydata)]) + m(".row.text-center", [u.paginator(args.querydata)]) ]); };
A admin/js/components/document.js

@@ -0,0 +1,252 @@

+(function(){ + 'use strict'; + var app = window.LS || (window.LS = {}); + var u = app.utils; + + // Document module + app.document = {vm: {}}; + app.document.vm.init = function() { + var vm = this; + vm.dir = app.system.directory; + vm.id = m.prop(m.route.param("id")); + vm.action = m.route.param("action"); + vm.readOnly = true; + vm.contentType = m.prop(""); + vm.updatedTags = m.prop(""); + vm.content = ""; + vm.binary = false; + vm.image = false; + vm.tags = []; + try { + vm.ext = vm.id().match(/\.(.+)$/)[1]; + } catch(e) { + vm.ext = ""; + } + + // Retrieve single document & update relevant variables + vm.getDoc = function(cb){ + vm.doc = Doc.get(vm.id()); + vm.doc.then(function(doc){ + vm.content = doc.data; + vm.tags = doc.tags; + vm.updatedTags(vm.tags.filter(function(t){return !/^\$/.test(t)}).join(", ")); + if (vm.tags.filter(function(t){return t === "$format:binary"}).length > 0) { + vm.binary = true; + if (vm.tags.filter(function(t){return t === "$type:image"}).length > 0) { + vm.image = true; + } + } + }, vm.flashError); + }; + + // Reset some properties based on action + switch (vm.action) { + case 'create': + vm.readOnly = false; + vm.content = ""; + break; + case 'edit': + vm.getDoc(); + vm.readOnly = false; + break; + case 'view': + vm.getDoc(); + break; + } + + // View document in editor + vm.viewDocument = function(){ + if (vm.ext === "md" && vm.id().match(new RegExp("^"+vm.dir+"\/md\/"))) { + // If editing a documentation page, go back to the guide. + m.route("/guide/"+vm.id().replace(/\.md$/, "").replace(new RegExp("^"+vm.dir+"\/md\/"), "")); + } else { + m.route("/document/view/"+vm.id()); + } + }; + + // Set current document editable + vm.edit = function(){ + vm.editor.setReadOnly(false); + vm.action = "edit"; + vm.flash(""); + }; + + // Save document + vm.save = function(){ + var doc = {}; + doc.id = vm.id(); + doc.data = vm.editor.getValue(); + doc.tags = vm.tags; + var put = function(){ + Doc.put(doc, vm.contentType()).then(function(){ + LS.flash({type: "success", content: "Document saved successfully."}); + vm.viewDocument(); + }, vm.flashError); + }; + if (vm.action === "create") { + doc.id = vm.id(); + vm.id(doc.id); + Doc.get(doc.id) + .then(function(){ + vm.showFlash({type: "danger", content: "Document '"+doc.id+"' already exists."}); + }, function(){ + put(); + }); + } else { + put(); + } + }; + + // Delete Document + vm.delete = function(){ + Doc.delete(vm.id()).then(function(){ + LS.flash({type: "success", content: "Document '"+vm.id()+"' deleted successfully."}); + // Tags may be changed, update infos + Info.get().then(function(info){ + app.system = info; + m.route("/info"); + }); + }, vm.flashError); + }; + + // Cancel editing + vm.cancel = function(){ + if (vm.action === "create"){ + m.route("/info"); + } else { + vm.viewDocument(); + } + }; + + // Patch document (update tags) + vm.patch = function(){ + var sysTags = vm.tags.filter(function(t){return /^\$/.test(t)}); + var newTags = sysTags.concat(vm.updatedTags().split(/,\s*/)); + Doc.patch(vm.id(), newTags).then(function(){ + LS.flash({type: "success", content: "Tags for document '"+vm.id()+"' updated successfully."}); + Info.get().then(function(info){ + app.system = info; + vm.viewDocument(); + }); + }, vm.flashError); + }; + + // File uploader callbacks. + var onSuccess = function(data){ + vm.id(data.id); + LS.flash({type: "success", content: "Document '"+vm.id()+"' uploader successfully."}); + Info.get().then(function(info){ + app.system = info; + vm.viewDocument(); + }); + }; + + var onFailure = function(data){ + vm.flashError; + }; + + var modalId = u.guid(); + + vm.uploader = u.uploader({docid: vm.id() || "", onSuccess: onSuccess, onFailure: onFailure, id: modalId}); + + // Populate tools based on current action + vm.tools = function(){ + if (app.system.read_only) { + return []; + } + // Configure edit tags popover + var cfg = {}; + cfg.title = "Edit Tags"; + cfg.contentId = "#edit-tags-popover"; + var tools = []; + var show = function(){ u.showModal()} + switch (vm.action){ + case "view": + tools.push({title: "Upload", icon: "upload", action: vm.uploader.show()}); + if (!vm.binary) { + tools.push({title: "Edit Content", icon: "edit", action: vm.edit}); + } + tools.push({title: "Edit Tags", icon: "tags", action: u.showModal("#edit-tags-modal")}); + tools.push({title: "Delete", icon: "trash", action: u.showModal("#delete-document-modal")}); + break; + default: + tools.push({title: "Upload", icon: "upload", action: vm.uploader.show()}); + tools.push({title: "Save", icon: "save", action: vm.save}); + tools.push({title: "Cancel", icon: "times-circle", action: vm.cancel}); + } + return tools; + }; + }; + + // Module main view + app.document.main = function(){ + var vm = app.document.vm; + var titleLeft = vm.id(); + var titleRight = m("span.pull-right", vm.tags.map(function(t){return u.taglink({name: t, key: u.guid()});})); + // Delete confirmation dialog + var deleteDialogCfg = { + title: "Delete Document", + id: "delete-document-modal", + action: vm.delete, + actionText: "Delete", + content: m("p", "Do you want to delete document '"+vm.id()+"'?") + }; + // Configuration for the Edit Tags dialog + var editTagsDialogCfg = { + title: "Add/Edit User Tags", + id: "edit-tags-modal", + action: vm.patch, + actionText: "Update", + content: m("div", [ + m("input", { + type: "text", + class:"form-control", + onchange: m.withAttr("value", vm.updatedTags), + value: vm.updatedTags(), + placeholder: "Enter comma-separated tags..." + }), + m("div.tip", [ + m("p", "Tip") , + m("p", "Each user tag can contain letters, numbers, and any of the following special characters:"), + m("p", "?, ~, :, ., @, #, ^, !, +, _, or -") + ]) + ]) + }; + if (vm.action === "create"){ + titleLeft = m("span", [m("input", { + placeholder: "Document ID", + onchange: m.withAttr("value", function(value){ + vm.id(value); + vm.editor.updateMode(value); + }), + size: 35, + value: vm.id() + })]); + titleRight = m("span.pull-right", [m("input", { + placeholder: "Content Type", + onchange: m.withAttr("value", function(value){ + vm.contentType(value); + }), + size: 25, + value: vm.contentType() + })]); + } + var panelContent; + if (vm.image){ + panelContent = m("div.text-center", [m("img", {src: app.host+"/docs/"+vm.id(), title: vm.id()})]); + } else { + panelContent = m.component(app.editor, vm); + } + var title = m("span",[titleLeft, titleRight]); + + return m("div", [ + vm.uploader, + u.modal(deleteDialogCfg), + u.modal(editTagsDialogCfg), + m(".row", [u.toolbar({links: vm.tools()})]), + m(".row", [u.panel({title: title, content:panelContent})]) + ]); + }; + + u.layout(app.document); +}());
A admin/js/components/guide.js

@@ -0,0 +1,26 @@

+(function(){ + 'use strict'; + var app = window.LS || (window.LS = {}); + var u = app.utils; + + // Guide Module + app.guide = {vm: {}}; + app.guide.vm.init = function() { + var vm = this; + vm.id = m.prop(m.route.param("id")); + vm.content = Page.get(vm.id()).then(function(content){return content}, vm.flashError); + vm.edit = function(){ + m.route("/document/edit/"+app.system.directory+"/md/"+vm.id()+".md"); + }; + vm.links = app.system.read_only ? m.prop([]) : m.prop([{action: vm.edit, title: "Edit", icon: "edit"}]); + }; + app.guide.main = function(){ + return m("article.row", [ + u.toolbar({links: app.guide.vm.links()}), + m.trust(app.guide.vm.content()) + ]); + }; + + u.layout(app.guide); + +}());
A admin/js/components/htmldoc.js

@@ -0,0 +1,28 @@

+(function(){ + 'use strict'; + var app = window.LS || (window.LS = {}); + var u = app.utils; + + // HTMLDoc Module + app.htmldoc = {vm: {}}; + app.htmldoc.vm.init = function() { + var vm = this; + vm.id = m.prop(m.route.param("id")); + vm.content = Doc.get(vm.id()).then(function(content){ + return $("<div>").html(content.data).html(); + }, vm.flashError); + vm.view = function(){ + m.route("/document/view/"+vm.id()); + }; + vm.links = m.prop([{action: vm.view, title: "View Source", icon: "code"}]); + }; + app.htmldoc.main = function(){ + return m("article.row", [ + u.toolbar({links: app.htmldoc.vm.links()}), + m.trust(app.htmldoc.vm.content()) + ]); + }; + + u.layout(app.htmldoc); + +}());
A admin/js/components/info.js

@@ -0,0 +1,42 @@

+(function(){ + 'use strict'; + var app = window.LS || (window.LS = {}); + var u = app.utils; + + // Info Module + app.info = {vm: {}}; + app.info.vm.init = function() {}; + app.info.main = function(){ + var info = app.system; + var li = function(title, content, hide) { + if (hide) { + return ""; + } else { + return m("li", [m("span", title+": "), m("strong", content)]); + } + }; + var readonly = info.read_only ? m("span.label.label-success", "Yes") : m("span.label.label-danger", "No"); + var infolist = m(".col-sm-6", [m("ul.list-unstyled", [ + li("Version", info.version), + li("Size", info.size), + li("Mounted Directory", info.directory, info.directory === null), + li("Log Level", info.log_level), + li("Read-Only", readonly), + li("Total Documents", m("span.badge", info.total_documents)), + li("Total Tags", m("span.badge", info.total_tags)), + ])]); + var logo = m(".col-sm-6", [m("img", {src: "images/litestore.png"})]); + var taglist = m("ul.list-unstyled", info.tags.map(function(tag){ + var key = Object.keys(tag)[0]; + return m("li", [u.tagbutton({name: key, n: tag[key], key: u.guid()})]); + }) + ); + var v = m(".row", [ + m(".col-md-6", [u.panel({title: "Datastore Information", content: m(".row", [logo, infolist])})]), + m(".col-md-6", [u.panel({title: "Tags", content: taglist})]) + ]); + return v; + }; + u.layout(app.info); + +}())
M admin/js/components/navbar.jsadmin/js/components/navbar.js

@@ -2,7 +2,6 @@ (function(){

'use strict'; var app = window.LS || (window.LS = {}); var u = app.utils; - var w = app.widgets; app.navlinks = { controller: function(args){

@@ -32,8 +31,8 @@ view: function(ctrl){

var links = [ m("li", {class: ctrl.activelink("info")}, [m("a", {href: "/info", config: m.route}, [m("i.fa.fa-info-circle"), " Info"])]), - w.dropdown({title: "Guide", icon:"fa-book", links: ctrl.guidelinks, active: ctrl.activelink("guide")}), - w.dropdown({title: "Tags", icon:"fa-tags", links: ctrl.taglinks(app.system), active: ctrl.activelink("tags")})]; + u.dropdown({title: "Guide", icon:"fa-book", links: ctrl.guidelinks, active: ctrl.activelink("guide")}), + u.dropdown({title: "Tags", icon:"fa-tags", links: ctrl.taglinks(app.system), active: ctrl.activelink("tags")})]; if (!app.system.read_only) { links.push(m("li", {class: ctrl.activelink("new")}, [m("a", {href: "/document/create/", config: m.route},
A admin/js/components/search.js

@@ -0,0 +1,39 @@

+(function(){ + 'use strict'; + var app = window.LS || (window.LS = {}); + var u = app.utils; + + // Search Module + app.search = {vm: {}}; + app.search.vm.init = function(){ + var vm = this; + vm.query = m.route.param("q"); + vm.baseurl = "/search/" + vm.query + "/"; + vm.limit = m.route.param("limit") || 10; + vm.page = m.route.param("page") || 1; + vm.page -= 1; // pages are 0-based + vm.offset = vm.page * vm.limit; + vm.result = m.prop({total: 0, results: []}); + vm.total = 0; + vm.execTime = 0; + Doc.search(vm.query, vm.offset, vm.limit).then(function(result){ + vm.result(result); + vm.total = result.total; + vm.execTime = (result.execution_time*1000).toFixed(0); + }, vm.flashError); + }; + app.search.main = function(){ + var vm = app.search.vm; + var result = vm.result(); + var obj = {}; + obj.title = m("h2.col-md-12", ["You searched for: ", m("em", vm.query)]); + obj.subtitle = m("p.col-md-12", [m("strong", result.total), " results ("+vm.execTime+" ms)"]); + obj.items = result.results; + obj.items.forEach(function(item){ item.content = m.trust(item.highlight) }); + obj.querydata = vm; + return m.component(app.doclist, obj); + }; + + u.layout(app.search); + +}());
A admin/js/components/tags.js

@@ -0,0 +1,43 @@

+(function(){ + 'use strict'; + var app = window.LS || (window.LS = {}); + var u = app.utils; + + // Tags Module + app.tags = {vm: {}}; + app.tags.vm.init = function(){ + var vm= this; + vm.id = m.route.param("id"); + vm.limit = m.route.param("limit") || 10; + vm.page = m.route.param("page") || 1; + vm.page -= 1; // pages are 0-based + vm.baseurl = "/tags/"+vm.id+"/"; + vm.offset = vm.page * vm.limit; + vm.total= 0; + vm.execTime = 0; + vm.docs = Doc.getByTag(vm.id, vm.offset, vm.limit).then(function(docs){ + vm.total = docs.total; + vm.execTime = (docs["execution_time"]*1000).toFixed(0); + return docs; + }, vm.flashError); + }; + app.tags.main = function(){ + var vm = app.tags.vm; + var docs = vm.docs(); + var obj = {}; + obj.querydata = vm; + obj.title = m("h2", ["Tag: ", m("em", docs.tags)]); + obj.subtitle = m("p", [m("strong",docs.total), " results, ("+vm.execTime+" ms)"]); + obj.items = docs.results; + obj.items.forEach(function(item){ + item.content = m("ul", [ + m("li", [m("strong", "Created: "), u.date(item.created)]), + m("li", [m("strong", "Modified: "), u.date(item.modified)]), + ]); + }); + return m.component(app.doclist, obj); + }; + + u.layout(app.tags); + +}());
M admin/js/components/uploader.jsadmin/js/components/uploader.js

@@ -1,107 +1,85 @@

-/* - * Dependencies: - * - models.js - * - utils.js - */ (function(){ 'use strict'; var app = window.LS || (window.LS = {}); var u = app.utils; - var w = app.widgets; - - /** - * @param {Object} obj - * @param {string} obj.docid - * @param {string} obj.id - * @param {Function} obj.onSuccess - * @param {Function} obj.onFailure - */ - app.uploader = function(obj){ - var modalId = "#upload-"+obj.id+"-modal"; - var uploader = {}; - - uploader.config = function(obj){ - return function(element, isInitialized, context){ - $(element).change(function(event){ - obj.file(element.files[0]); - if (obj.reader.readyState != 1) { - obj.reader.readAsDataURL(obj.file()); - } - }); - }; + app.uploader = {}; + + app.uploader.config = function(obj){ + return function(element, isInitialized, context){ + $(element).change(function(event){ + obj.file(element.files[0]); + if (obj.reader.readyState != 1) { + obj.reader.readAsDataURL(obj.file()); + } + }); }; - - uploader.controller = function(args) { - var vm = this; + }; - vm.docid = m.prop(args.docid); - vm.file = m.prop(); - vm.id = args.id; - vm.btnId = "#upload-"+vm.id+"-btn"; - vm.reader = new FileReader(); - vm.contents = m.prop(); - vm.isText = m.prop(false); + app.uploader.controller = function(args) { + var vm = this; - vm.reader.onloadstart = function() { - vm.contents(""); - $(modalId).find(".btn-primary").attr("disabled", true); - }; + vm.docid = m.prop(args.docid); + vm.file = m.prop(); + vm.id = args.id; + vm.btnId = "#upload-"+vm.id+"-btn"; + vm.reader = new FileReader(); + vm.contents = m.prop(); + vm.isText = m.prop(false); - vm.reader.onloadend = function() { - vm.contents(vm.reader.result); - $(modalId).find(".btn-primary").removeAttr("disabled"); - }; + vm.reader.onloadstart = function() { + vm.contents(""); + $(modalId).find(".btn-primary").attr("disabled", true); + }; - vm.save = function() { - var doc = {id: vm.docid()}; - doc.data = vm.contents().split(',')[1]; - if (vm.isText()) { - doc.data = window.atob(doc.data); - } - return Doc.put(doc, vm.file().type).then(args.onSuccess, args.onFailure); - }; - return vm; - } + vm.reader.onloadend = function() { + vm.contents(vm.reader.result); + $(modalId).find(".btn-primary").removeAttr("disabled"); + }; + + vm.save = function() { + var doc = {id: vm.docid()}; + doc.data = vm.contents().split(',')[1]; + if (vm.isText()) { + doc.data = window.atob(doc.data); + } + return Doc.put(doc, vm.file().type).then(args.onSuccess, args.onFailure); + }; + return vm; + } - uploader.view = function(ctrl, args){ - var config = { - title: "Upload Document", - id: "upload-"+ctrl.id+"-modal", - action: ctrl.save, - actionText: "Upload", - content: m("div", [ - m(".form-group", [ - m("label", "Document ID"), - m("input.form-control", { - placeholder: "Enter document ID", - onchange: m.withAttr("value", ctrl.docid), - size: 35, - disabled: (ctrl.docid() === "") ? false : true, - value: ctrl.docid() - }) - ]), - m(".form-group", [ - m("label", "File"), - m("input.form-control#upload-"+ctrl.id+"-btn", {type:"file", config: uploader.config(ctrl)}), - m("p.help-block", "Select a file to upload as document.") + app.uploader.view = function(ctrl, args){ + var config = { + title: "Upload Document", + id: "upload-"+ctrl.id+"-modal", + action: ctrl.save, + actionText: "Upload", + content: m("div", [ + m(".form-group", [ + m("label", "Document ID"), + m("input.form-control", { + placeholder: "Enter document ID", + onchange: m.withAttr("value", ctrl.docid), + size: 35, + disabled: (ctrl.docid() === "") ? false : true, + value: ctrl.docid() + }) + ]), + m(".form-group", [ + m("label", "File"), + m("input.form-control#upload-"+ctrl.id+"-btn", {type:"file", config: app.uploader.config(ctrl)}), + m("p.help-block", "Select a file to upload as document.") + ]), + m(".checkbox", [ + m("label", [ + m("input", {type: "checkbox", value: ctrl.isText(), onchange: m.withAttr("value", ctrl.isText)}), + "Text File" ]), - m(".checkbox", [ - m("label", [ - m("input", {type: "checkbox", value: ctrl.isText(), onchange: m.withAttr("value", ctrl.isText)}), - "Text File" - ]), - m("p.help-block", "Select if the file to upload contains textual content.") - ]) + m("p.help-block", "Select if the file to upload contains textual content.") ]) - }; - return w.modal(config); - }; - - var instance = m.component(uploader, obj); - instance.show = function() { - return u.showModal(modalId); + ]) }; - return instance; + return u.modal(config); }; + }());
M admin/js/components/widgets.jsadmin/js/components/widgets.js

@@ -3,242 +3,161 @@ 'use strict';

var app = window.LS || (window.LS = {}); app.widgets = {}; - - /** - * Creates a Panel component. - * @param {Object} obj - * @param {string} obj.title - * @param {string} obj.footer - * @param {string} obj.content - */ - app.widgets.panel = function(obj){ - var panel = { - view: function(ctrl, args){ - var title = ""; - var footer = ""; - if (args.title){ - title = m(".panel-heading", [ - m("h2.panel-title", [args.title]) - ]); - } - if (args.footer){ - footer = m(".panel-footer", args.footer); - } - return m(".panel.panel-default", [ - title, - m(".panel-body", [ - args.content - ]), - footer + + /* PANEL */ + app.widgets.panel = { + view: function(ctrl, args){ + var title = ""; + var footer = ""; + if (args.title){ + title = m(".panel-heading", [ + m("h2.panel-title", [args.title]) ]); } - }; - return m.component(panel, obj); + if (args.footer){ + footer = m(".panel-footer", args.footer); + } + return m(".panel.panel-default", [ + title, + m(".panel-body", [ + args.content + ]), + footer + ]); + } }; - /** - * @typedef {Object} PaginatorConfig - * @prop {string} baseurl - * @prop {int} total - * @prop {int} limit - * @prop {int} offset - * - * Creates a Paginator component. - * @param {PaginatorConfig} obj - */ - app.widgets.paginator = function(obj) { - var paginator = { - view: function(ctrl, args){ - var max_page = Math.min(14, Math.ceil(args.total/args.limit)-1); - var c_page = Math.ceil(args.offset/args.limit); - var page = function(n, sign, disabled){ - var klass; - if (disabled) { - klass = "disabled"; - } else { - klass = (n === c_page) ? "active" : "inactive"; - } - var first = (n === 0); - var last = (n == max_page); - var offset = args.limit * n; - sign = sign || n+1; - return m("li", {class: klass}, - [m("a", { - href: args.baseurl +(n+1), // assuming 10 elements per page //+"/"+obj.limit, - config: m.route - }, [m.trust(sign)] - )] - ); - }; - - var pages = []; - var prev; - var next; - for (var i=0; i<=max_page; i++){ - var p; - switch(i){ - case c_page-1: - prev = page(i, "&laquo;"); - break; - case c_page+1: - next = page(i, "&raquo;"); - break; - } - if (c_page === 0){ - prev = page(0, "&laquo;", true); - } - if (c_page === max_page){ - next = page(max_page, "&raquo;", true); - } - pages.push(page(i)); + /* PAGINATOR */ + app.widgets.paginator = { + view: function(ctrl, args){ + var max_page = Math.min(14, Math.ceil(args.total/args.limit)-1); + var c_page = Math.ceil(args.offset/args.limit); + var page = function(n, sign, disabled){ + var klass; + if (disabled) { + klass = "disabled"; + } else { + klass = (n === c_page) ? "active" : "inactive"; + } + var first = (n === 0); + var last = (n == max_page); + var offset = args.limit * n; + sign = sign || n+1; + return m("li", {class: klass}, + [m("a", { + href: args.baseurl +(n+1), // assuming 10 elements per page //+"/"+obj.limit, + config: m.route + }, [m.trust(sign)] + )] + ); + }; + + var pages = []; + var prev; + var next; + for (var i=0; i<=max_page; i++){ + var p; + switch(i){ + case c_page-1: + prev = page(i, "&laquo;"); + break; + case c_page+1: + next = page(i, "&raquo;"); + break; + } + if (c_page === 0){ + prev = page(0, "&laquo;", true); } - pages.unshift(prev); - pages.push(next); - return m("nav", [m("ul.pagination", pages)]); - } - }; - return m.component(paginator, obj); + if (c_page === max_page){ + next = page(max_page, "&raquo;", true); + } + pages.push(page(i)); + } + pages.unshift(prev); + pages.push(next); + return m("nav", [m("ul.pagination", pages)]); + } }; - /** - * @typedef {Object} DropdownLink - * @prop {string} path - * @prop {string} title - * - * Creates a Dropdown component. - * @param {Object} obj - * @param {string} obj.icon - * @param {string} obj.title - * @param {string} obj.active - * @param {array.DropdownLink} obj.links - */ - app.widgets.dropdown = function(obj) { - var dropdown = { - view: function(ctrl, args){ - var el = "li.dropdown"; - var icon = (args.icon) ? m("i.fa."+args.icon) : ""; - if (args.active.length > 0) { - el += "."+args.active; - } - return m(el, [ - m("a.dropdown-toggle[href='#'][data-toggle='dropdown'][role='button'][aria-expanded='false']", - [icon, m("span", " "+args.title+" "), m("span.caret")]), - m("ul.dropdown-menu[role='menu']", - args.links.map(function(e){ - return m("li", - [m("a", {href: e.path, config: m.route}, m.trust(e.title))]);})) - ]); + /* DROPDOWN */ + app.widgets.dropdown = { + view: function(ctrl, args){ + var el = "li.dropdown"; + var icon = (args.icon) ? m("i.fa."+args.icon) : ""; + if (args.active.length > 0) { + el += "."+args.active; } - }; - return m.component(dropdown, obj); + return m(el, [ + m("a.dropdown-toggle[href='#'][data-toggle='dropdown'][role='button'][aria-expanded='false']", + [icon, m("span", " "+args.title+" "), m("span.caret")]), + m("ul.dropdown-menu[role='menu']", + args.links.map(function(e){ + return m("li", + [m("a", {href: e.path, config: m.route}, m.trust(e.title))]);})) + ]); + } }; - /** - * Creates a TagLink component. - * @param {Object} obj - * @param {string} obj.name - */ - app.widgets.taglink = function(obj){ - var taglink = { - view: function(ctrl, args) { - var color = /^\$/.test(args.name) ? "warning" : "primary" - return m("span.tag-label.label.label-"+color, - [m("i.fa.fa-tag"), " ", m("a", {href: "/tags/"+args.name, config:m.route}, args.name)]); - } - }; - return m.component(taglink, obj); + /* DROPDOWN */ + app.widgets.taglink = { + view: function(ctrl, args) { + var color = /^\$/.test(args.name) ? "warning" : "primary" + return m("span.tag-label.label.label-"+color, + [m("i.fa.fa-tag"), " ", m("a", {href: "/tags/"+args.name, config:m.route}, args.name)]); + } }; - - /** - * Creates a DocLink component. - * @param {Object} obj - * @param {string} obj.id - */ - app.widgets.doclink = function(obj) { - var doclink = { - view: function(ctrl, args) { - return m("a", {href: "/document/view/"+args.id, config: m.route}, id); - } - }; - return m.component(doclink, obj); + + /* DOCLINK */ + app.widgets.doclink = { + view: function(ctrl, args) { + return m("a", {href: "/document/view/"+args.id, config: m.route}, id); + } }; - /** - * Creates a TagButton component. - * @param {Object} obj - * @param {string} obj.name - * @param {int} obj.n - */ - app.widgets.tagbutton = function(obj) { - var tagbutton = { - view: function(ctrl, args) { - return m("a", {href: "/tags/"+args.name, config:m.route}, - [m("i.fa.fa-tag"), " "+args.name+" ", m("span.badge", args.n)]); - } - }; - return m.component(tagbutton, obj); + /* TAGBUTTON */ + app.widgets.tagbutton = { + view: function(ctrl, args) { + return m("a", {href: "/tags/"+args.name, config:m.route}, + [m("i.fa.fa-tag"), " "+args.name+" ", m("span.badge", args.n)]); + } }; - /** - * @typedef {Object} ToolbarLink - * @prop {Function} action - * @prop {Function} config - * @prop {string} title - * @prop {string} icon - * - * Creates a ToolBar component. - * @param {Object} obj - * @param {array.<ToolbarLink>} obj.links - */ - app.widgets.toolbar = function(obj) { - var toolbar = { - view: function(ctrl, args){ - return m("nav.toolbar.btn-group[role='group'][aria-label='...'].pull-right", - args.links.map(function(l){ - return m("a.btn.btn-default", - {onclick:l.action, config: l.config}, - [m("i.fa.fa-"+l.icon), " "+l.title]); - }) - ); - } - }; - return m.component(toolbar, obj); + /* TOOLBAR */ + app.widgets.toolbar = { + view: function(ctrl, args){ + return m("nav.toolbar.btn-group[role='group'][aria-label='...'].pull-right", + args.links.map(function(l){ + return m("a.btn.btn-default", + {onclick:l.action, config: l.config}, + [m("i.fa.fa-"+l.icon), " "+l.title]); + }) + ); + } }; - /** - * Creates a Modal component. - * @param {Object} obj - * @param {string} obj.id - * @param {string} obj.content - * @param {string} obj.title - * @param {Function} obj.action - * @param {string} obj.actionText - */ - app.widgets.modal = function(obj) { - var modal = { - view: function(ctrl, args){ - return m(".modal.fade", - {id: args.id, tabindex: "-1", role: "dialog"}, - [ - m(".modal-dialog", [ - m(".modal-content", [ - m(".modal-header", [ - m("button", {type: "button", class: "close", "data-dismiss": "modal"}, - [m.trust("&times;")]), - m("h4.modal-title", args.title) - ]), - m(".modal-body", [args.content]), - m(".modal-footer", [ - m("button.btn.btn-default[data-dismiss='modal']", "Close"), - m("button.btn.btn-primary[data-dismiss='modal']", {onclick: args.action}, args.actionText) - ]) + /* MODAL */ + app.widgets.modal = { + view: function(ctrl, args){ + return m(".modal.fade", + {id: args.id, tabindex: "-1", role: "dialog"}, + [ + m(".modal-dialog", [ + m(".modal-content", [ + m(".modal-header", [ + m("button", {type: "button", class: "close", "data-dismiss": "modal"}, + [m.trust("&times;")]), + m("h4.modal-title", args.title) + ]), + m(".modal-body", [args.content]), + m(".modal-footer", [ + m("button.btn.btn-default[data-dismiss='modal']", "Close"), + m("button.btn.btn-primary[data-dismiss='modal']", {onclick: args.action}, args.actionText) ]) ]) - ] - ); - } - }; - return m.component(modal, obj); + ]) + ] + ); + } }; }());
D admin/js/modules/document.js

@@ -1,253 +0,0 @@

-(function(){ - 'use strict'; - var app = window.LS || (window.LS = {}); - var u = app.utils; - var w = app.widgets; - - // Document module - app.document = {vm: {}}; - app.document.vm.init = function() { - var vm = this; - vm.dir = app.system.directory; - vm.id = m.prop(m.route.param("id")); - vm.action = m.route.param("action"); - vm.readOnly = true; - vm.contentType = m.prop(""); - vm.updatedTags = m.prop(""); - vm.content = ""; - vm.binary = false; - vm.image = false; - vm.tags = []; - try { - vm.ext = vm.id().match(/\.(.+)$/)[1]; - } catch(e) { - vm.ext = ""; - } - - // Retrieve single document & update relevant variables - vm.getDoc = function(cb){ - vm.doc = Doc.get(vm.id()); - vm.doc.then(function(doc){ - vm.content = doc.data; - vm.tags = doc.tags; - vm.updatedTags(vm.tags.filter(function(t){return !/^\$/.test(t)}).join(", ")); - if (vm.tags.filter(function(t){return t === "$format:binary"}).length > 0) { - vm.binary = true; - if (vm.tags.filter(function(t){return t === "$type:image"}).length > 0) { - vm.image = true; - } - } - }, vm.flashError); - }; - - // Reset some properties based on action - switch (vm.action) { - case 'create': - vm.readOnly = false; - vm.content = ""; - break; - case 'edit': - vm.getDoc(); - vm.readOnly = false; - break; - case 'view': - vm.getDoc(); - break; - } - - // View document in editor - vm.viewDocument = function(){ - if (vm.ext === "md" && vm.id().match(new RegExp("^"+vm.dir+"\/md\/"))) { - // If editing a documentation page, go back to the guide. - m.route("/guide/"+vm.id().replace(/\.md$/, "").replace(new RegExp("^"+vm.dir+"\/md\/"), "")); - } else { - m.route("/document/view/"+vm.id()); - } - }; - - // Set current document editable - vm.edit = function(){ - vm.editor.setReadOnly(false); - vm.action = "edit"; - vm.flash(""); - }; - - // Save document - vm.save = function(){ - var doc = {}; - doc.id = vm.id(); - doc.data = vm.editor.getValue(); - doc.tags = vm.tags; - var put = function(){ - Doc.put(doc, vm.contentType()).then(function(){ - LS.flash({type: "success", content: "Document saved successfully."}); - vm.viewDocument(); - }, vm.flashError); - }; - if (vm.action === "create") { - doc.id = vm.id(); - vm.id(doc.id); - Doc.get(doc.id) - .then(function(){ - vm.showFlash({type: "danger", content: "Document '"+doc.id+"' already exists."}); - }, function(){ - put(); - }); - } else { - put(); - } - }; - - // Delete Document - vm.delete = function(){ - Doc.delete(vm.id()).then(function(){ - LS.flash({type: "success", content: "Document '"+vm.id()+"' deleted successfully."}); - // Tags may be changed, update infos - Info.get().then(function(info){ - app.system = info; - m.route("/info"); - }); - }, vm.flashError); - }; - - // Cancel editing - vm.cancel = function(){ - if (vm.action === "create"){ - m.route("/info"); - } else { - vm.viewDocument(); - } - }; - - // Patch document (update tags) - vm.patch = function(){ - var sysTags = vm.tags.filter(function(t){return /^\$/.test(t)}); - var newTags = sysTags.concat(vm.updatedTags().split(/,\s*/)); - Doc.patch(vm.id(), newTags).then(function(){ - LS.flash({type: "success", content: "Tags for document '"+vm.id()+"' updated successfully."}); - Info.get().then(function(info){ - app.system = info; - vm.viewDocument(); - }); - }, vm.flashError); - }; - - // File uploader callbacks. - var onSuccess = function(data){ - vm.id(data.id); - LS.flash({type: "success", content: "Document '"+vm.id()+"' uploader successfully."}); - Info.get().then(function(info){ - app.system = info; - vm.viewDocument(); - }); - }; - - var onFailure = function(data){ - vm.flashError; - }; - - var modalId = u.guid(); - - vm.uploader = app.uploader({docid: vm.id() || "", onSuccess: onSuccess, onFailure: onFailure, id: modalId}); - - // Populate tools based on current action - vm.tools = function(){ - if (app.system.read_only) { - return []; - } - // Configure edit tags popover - var cfg = {}; - cfg.title = "Edit Tags"; - cfg.contentId = "#edit-tags-popover"; - var tools = []; - var show = function(){ u.showModal()} - switch (vm.action){ - case "view": - tools.push({title: "Upload", icon: "upload", action: vm.uploader.show()}); - if (!vm.binary) { - tools.push({title: "Edit Content", icon: "edit", action: vm.edit}); - } - tools.push({title: "Edit Tags", icon: "tags", action: u.showModal("#edit-tags-modal")}); - tools.push({title: "Delete", icon: "trash", action: u.showModal("#delete-document-modal")}); - break; - default: - tools.push({title: "Upload", icon: "upload", action: vm.uploader.show()}); - tools.push({title: "Save", icon: "save", action: vm.save}); - tools.push({title: "Cancel", icon: "times-circle", action: vm.cancel}); - } - return tools; - }; - }; - - // Module main view - app.document.main = function(){ - var vm = app.document.vm; - var titleLeft = vm.id(); - var titleRight = m("span.pull-right", vm.tags.map(function(t){return w.taglink({name: t, key: u.guid()});})); - // Delete confirmation dialog - var deleteDialogCfg = { - title: "Delete Document", - id: "delete-document-modal", - action: vm.delete, - actionText: "Delete", - content: m("p", "Do you want to delete document '"+vm.id()+"'?") - }; - // Configuration for the Edit Tags dialog - var editTagsDialogCfg = { - title: "Add/Edit User Tags", - id: "edit-tags-modal", - action: vm.patch, - actionText: "Update", - content: m("div", [ - m("input", { - type: "text", - class:"form-control", - onchange: m.withAttr("value", vm.updatedTags), - value: vm.updatedTags(), - placeholder: "Enter comma-separated tags..." - }), - m("div.tip", [ - m("p", "Tip") , - m("p", "Each user tag can contain letters, numbers, and any of the following special characters:"), - m("p", "?, ~, :, ., @, #, ^, !, +, _, or -") - ]) - ]) - }; - if (vm.action === "create"){ - titleLeft = m("span", [m("input", { - placeholder: "Document ID", - onchange: m.withAttr("value", function(value){ - vm.id(value); - vm.editor.updateMode(value); - }), - size: 35, - value: vm.id() - })]); - titleRight = m("span.pull-right", [m("input", { - placeholder: "Content Type", - onchange: m.withAttr("value", function(value){ - vm.contentType(value); - }), - size: 25, - value: vm.contentType() - })]); - } - var panelContent; - if (vm.image){ - panelContent = m("div.text-center", [m("img", {src: app.host+"/docs/"+vm.id(), title: vm.id()})]); - } else { - panelContent = m.component(app.editor, vm); - } - var title = m("span",[titleLeft, titleRight]); - - return m("div", [ - vm.uploader, - w.modal(deleteDialogCfg), - w.modal(editTagsDialogCfg), - m(".row", [w.toolbar({links: vm.tools()})]), - m(".row", [w.panel({title: title, content:panelContent})]) - ]); - }; - - u.layout(app.document); -}());
D admin/js/modules/guide.js

@@ -1,27 +0,0 @@

-(function(){ - 'use strict'; - var app = window.LS || (window.LS = {}); - var u = app.utils; - var w = app.widgets; - - // Guide Module - app.guide = {vm: {}}; - app.guide.vm.init = function() { - var vm = this; - vm.id = m.prop(m.route.param("id")); - vm.content = Page.get(vm.id()).then(function(content){return content}, vm.flashError); - vm.edit = function(){ - m.route("/document/edit/"+app.system.directory+"/md/"+vm.id()+".md"); - }; - vm.links = app.system.read_only ? m.prop([]) : m.prop([{action: vm.edit, title: "Edit", icon: "edit"}]); - }; - app.guide.main = function(){ - return m("article.row", [ - w.toolbar({links: app.guide.vm.links()}), - m.trust(app.guide.vm.content()) - ]); - }; - - u.layout(app.guide); - -}());
D admin/js/modules/htmldoc.js

@@ -1,29 +0,0 @@

-(function(){ - 'use strict'; - var app = window.LS || (window.LS = {}); - var u = app.utils; - var w = app.widgets; - - // HTMLDoc Module - app.htmldoc = {vm: {}}; - app.htmldoc.vm.init = function() { - var vm = this; - vm.id = m.prop(m.route.param("id")); - vm.content = Doc.get(vm.id()).then(function(content){ - return $("<div>").html(content.data).html(); - }, vm.flashError); - vm.view = function(){ - m.route("/document/view/"+vm.id()); - }; - vm.links = m.prop([{action: vm.view, title: "View Source", icon: "code"}]); - }; - app.htmldoc.main = function(){ - return m("article.row", [ - w.toolbar({links: app.htmldoc.vm.links()}), - m.trust(app.htmldoc.vm.content()) - ]); - }; - - u.layout(app.htmldoc); - -}());
D admin/js/modules/info.js

@@ -1,43 +0,0 @@

-(function(){ - 'use strict'; - var app = window.LS || (window.LS = {}); - var u = app.utils; - var w = app.widgets; - - // Info Module - app.info = {vm: {}}; - app.info.vm.init = function() {}; - app.info.main = function(){ - var info = app.system; - var li = function(title, content, hide) { - if (hide) { - return ""; - } else { - return m("li", [m("span", title+": "), m("strong", content)]); - } - }; - var readonly = info.read_only ? m("span.label.label-success", "Yes") : m("span.label.label-danger", "No"); - var infolist = m(".col-sm-6", [m("ul.list-unstyled", [ - li("Version", info.version), - li("Size", info.size), - li("Mounted Directory", info.directory, info.directory === null), - li("Log Level", info.log_level), - li("Read-Only", readonly), - li("Total Documents", m("span.badge", info.total_documents)), - li("Total Tags", m("span.badge", info.total_tags)), - ])]); - var logo = m(".col-sm-6", [m("img", {src: "images/litestore.png"})]); - var taglist = m("ul.list-unstyled", info.tags.map(function(tag){ - var key = Object.keys(tag)[0]; - return m("li", [w.tagbutton({name: key, n: tag[key], key: u.guid()})]); - }) - ); - var v = m(".row", [ - m(".col-md-6", [w.panel({title: "Datastore Information", content: m(".row", [logo, infolist])})]), - m(".col-md-6", [w.panel({title: "Tags", content: taglist})]) - ]); - return v; - }; - u.layout(app.info); - -}())
D admin/js/modules/search.js

@@ -1,39 +0,0 @@

-(function(){ - 'use strict'; - var app = window.LS || (window.LS = {}); - var u = LS.utils; - - // Search Module - app.search = {vm: {}}; - app.search.vm.init = function(){ - var vm = this; - vm.query = m.route.param("q"); - vm.baseurl = "/search/" + vm.query + "/"; - vm.limit = m.route.param("limit") || 10; - vm.page = m.route.param("page") || 1; - vm.page -= 1; // pages are 0-based - vm.offset = vm.page * vm.limit; - vm.result = m.prop({total: 0, results: []}); - vm.total = 0; - vm.execTime = 0; - Doc.search(vm.query, vm.offset, vm.limit).then(function(result){ - vm.result(result); - vm.total = result.total; - vm.execTime = (result.execution_time*1000).toFixed(0); - }, vm.flashError); - }; - app.search.main = function(){ - var vm = app.search.vm; - var result = vm.result(); - var obj = {}; - obj.title = m("h2.col-md-12", ["You searched for: ", m("em", vm.query)]); - obj.subtitle = m("p.col-md-12", [m("strong", result.total), " results ("+vm.execTime+" ms)"]); - obj.items = result.results; - obj.items.forEach(function(item){ item.content = m.trust(item.highlight) }); - obj.querydata = vm; - return m.component(app.doclist, obj); - }; - - u.layout(app.search); - -}());
D admin/js/modules/tags.js

@@ -1,43 +0,0 @@

-(function(){ - 'use strict'; - var app = window.LS || (window.LS = {}); - var u = LS.utils; - - // Tags Module - app.tags = {vm: {}}; - app.tags.vm.init = function(){ - var vm= this; - vm.id = m.route.param("id"); - vm.limit = m.route.param("limit") || 10; - vm.page = m.route.param("page") || 1; - vm.page -= 1; // pages are 0-based - vm.baseurl = "/tags/"+vm.id+"/"; - vm.offset = vm.page * vm.limit; - vm.total= 0; - vm.execTime = 0; - vm.docs = Doc.getByTag(vm.id, vm.offset, vm.limit).then(function(docs){ - vm.total = docs.total; - vm.execTime = (docs["execution_time"]*1000).toFixed(0); - return docs; - }, vm.flashError); - }; - app.tags.main = function(){ - var vm = app.tags.vm; - var docs = vm.docs(); - var obj = {}; - obj.querydata = vm; - obj.title = m("h2", ["Tag: ", m("em", docs.tags)]); - obj.subtitle = m("p", [m("strong",docs.total), " results, ("+vm.execTime+" ms)"]); - obj.items = docs.results; - obj.items.forEach(function(item){ - item.content = m("ul", [ - m("li", [m("strong", "Created: "), u.date(item.created)]), - m("li", [m("strong", "Modified: "), u.date(item.modified)]), - ]); - }); - return m.component(app.doclist, obj); - }; - - u.layout(app.tags); - -}());
M admin/js/utils.jsadmin/js/utils.js

@@ -3,7 +3,6 @@ 'use strict';

var app = window.LS || (window.LS = {}); var u = app.utils = {}; - // http://byronsalau.com/blog/how-to-create-a-guid-uuid-in-javascript/ u.guid = function(){ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {

@@ -28,6 +27,23 @@ return u.fixHeadings($content, maxheading);

} }; + u.setContentType = function(doc, contentType){ + var type = ""; + var subtype = ""; + if (doc.tags && doc.tags.length > 0) { + doc.tags.forEach(function(tag){ + var t = tag.match(/^\$type:(.+)/); + var s = tag.match(/^\$subtype:(.+)/); + if (t) type = t[1]; + if (s) subtype = s[1]; + }); + contentType = type+"/"+subtype; + } + return function(xhr) { + xhr.setRequestHeader("Content-Type", contentType); + }; + }; + u.markdown = function(s) { var hs = new marked.Renderer(); var md = new marked.Renderer();

@@ -67,8 +83,10 @@ };

/** * mod object: - * @property vm a view-model (with init function) - * @property main the main view to load + * @param {Object} mod + * @param {Object} mod.vm + * @param {Function} mod.vm.init + * @param {Function} mod.main */ u.layout = function(mod) { mod.controller = mod.controller || function(args){

@@ -97,194 +115,141 @@ ]);

}; }; + u.flash = function(){ + if (LS.flash()){ + return m(".row.alert.alert-dismissible.alert-"+LS.flash().type, [ + m("button.close[data-dismiss='alert']", m.trust("&times;")), + LS.flash().content]); + } else { + return ""; + } + }; + + u.date = function(date) { + return (date) ? new Date(Date.parse(date)).toUTCString() : "n/a"; + }; + + + u.showModal = function(sel){ + return function(){ + $(sel).modal(); + }; + }; + + + /* Component Factories */ + + /** + * @param {Object} obj + * @param {string} obj.docid + * @param {string} obj.id + * @param {Function} obj.onSuccess + * @param {Function} obj.onFailure + */ + u.uploader = function(obj){ + var modalId = "#upload-"+obj.id+"-modal"; + var instance = m.component(app.uploader, obj); + instance.show = function() { + return u.showModal(modalId); + }; + return instance; + }; + + /** + * Creates a Panel component. + * @param {Object} obj + * @param {string} obj.title + * @param {string} obj.footer + * @param {string} obj.content + */ u.panel = function(obj){ - var panel = { - controller: function(args){ - return { - title: args.title, - footer: args.footer, - content: args.content, - }; - }, - view: function(ctrl){ - var title = ""; - var footer = ""; - if (ctrl.title){ - title = m(".panel-heading", [ - m("h2.panel-title", [ctrl.title]) - ]); - } - if (ctrl.footer){ - footer = m(".panel-footer", ctrl.footer); - } - return m(".panel.panel-default", [ - title, - m(".panel-body", [ - ctrl.content - ]), - footer - ]); - } - }; - return m.component(panel, obj); + return m.component(app.widgets.panel, obj); }; - + /** - * - total - * - limit - * - offset - * - baseurl + * @typedef {Object} PaginatorConfig + * @prop {string} baseurl + * @prop {int} total + * @prop {int} limit + * @prop {int} offset + * + * Creates a Paginator component. + * @param {PaginatorConfig} obj */ u.paginator = function(obj) { - var max_page = Math.min(14, Math.ceil(obj.total/obj.limit)-1); - var c_page = Math.ceil(obj.offset/obj.limit); - var page = function(n, sign, disabled){ - var klass; - if (disabled) { - klass = "disabled"; - } else { - klass = (n === c_page) ? "active" : "inactive"; - } - var first = (n === 0); - var last = (n == max_page); - var offset = obj.limit * n; - sign = sign || n+1; - return m("li", {class: klass}, - [m("a", { - href: obj.baseurl +(n+1), // assuming 10 elements per page //+"/"+obj.limit, - config: m.route - }, [m.trust(sign)] - )] - ); - }; - var pages = []; - var prev; - var next; - for (var i=0; i<=max_page; i++){ - var p; - switch(i){ - case c_page-1: - prev = page(i, "&laquo;"); - break; - case c_page+1: - next = page(i, "&raquo;"); - break; - } - if (c_page === 0){ - prev = page(0, "&laquo;", true); - } - if (c_page === max_page){ - next = page(max_page, "&raquo;", true); - } - pages.push(page(i)); - } - pages.unshift(prev); - pages.push(next); - return m("nav", [m("ul.pagination", pages)]); + return m.component(app.widgets.paginator, obj); }; - + + /** + * @typedef {Object} DropdownLink + * @prop {string} path + * @prop {string} title + * + * Creates a Dropdown component. + * @param {Object} obj + * @param {string} obj.icon + * @param {string} obj.title + * @param {string} obj.active + * @param {array.DropdownLink} obj.links + */ u.dropdown = function(obj) { - var el = "li.dropdown"; - var icon = (obj.icon) ? m("i.fa."+obj.icon) : ""; - if (obj.active.length > 0) { - el += "."+obj.active; - } - return m(el, [ - m("a.dropdown-toggle[href='#'][data-toggle='dropdown'][role='button'][aria-expanded='false']", - [icon, m("span", " "+obj.title+" "), m("span.caret")]), - m("ul.dropdown-menu[role='menu']", - obj.links.map(function(e){ - return m("li", - [m("a", {href: e.path, config: m.route}, m.trust(e.title))]);})) - ]); + return m.component(app.widgets.dropdown, obj); }; - u.taglink = function(tag) { - var color = /^\$/.test(tag) ? "warning" : "primary"; - return m("span.tag-label.label.label-"+color, - [m("i.fa.fa-tag"), " ", m("a", {href: "/tags/"+tag, config:m.route}, tag)]); + /** + * Creates a TagLink component. + * @param {Object} obj + * @param {string} obj.name + */ + u.taglink = function(obj){ + return m.component(app.widgets.taglink, obj); }; - u.tagbutton = function(tag, n) { - return m("a", - {href: "/tags/"+tag, config:m.route}, - [m("i.fa.fa-tag"), " "+tag+" ", m("span.badge", n)]); + /** + * Creates a DocLink component. + * @param {Object} obj + * @param {string} obj.id + */ + u.doclink = function(obj) { + return m.component(app.widgets.doclink, obj); }; - u.doclink = function(id) { - return m("a", {href: "/document/view/"+id, config: m.route}, id); + /** + * Creates a TagButton component. + * @param {Object} obj + * @param {string} obj.name + * @param {int} obj.n + */ + u.tagbutton = function(obj) { + return m.component(app.widgets.tagbutton, obj); }; - u.date = function(date) { - return (date) ? new Date(Date.parse(date)).toUTCString() : "n/a"; + /** + * @typedef {Object} ToolbarLink + * @prop {Function} action + * @prop {Function} config + * @prop {string} title + * @prop {string} icon + * + * Creates a ToolBar component. + * @param {Object} obj + * @param {array.<ToolbarLink>} obj.links + */ + u.toolbar = function(obj) { + return m.component(app.widgets.toolbar, obj); }; - u.toolbar = function(obj){ - return m("nav.toolbar.btn-group[role='group'][aria-label='...'].pull-right", obj.links.map(function(l){ - return m("a.btn.btn-default", {onclick:l.action, config: l.config}, [m("i.fa.fa-"+l.icon), " "+l.title]); - }) - ); - }; - - u.flash = function(){ - if (LS.flash()){ - return m(".row.alert.alert-dismissible.alert-"+LS.flash().type, [ - m("button.close[data-dismiss='alert']", m.trust("&times;")), - LS.flash().content]); - } else { - return ""; - } - }; - - u.showModal = function(sel){ - return function(){ - $(sel).modal(); - }; - }; - /** - * obj: - * - id - * - content - * - title - * - action - * - actionText + * Creates a Modal component. + * @param {Object} obj + * @param {string} obj.id + * @param {string} obj.content + * @param {string} obj.title + * @param {Function} obj.action + * @param {string} obj.actionText */ u.modal = function(obj) { - return m(".modal.fade", - {id: obj.id, tabindex: "-1", role: "dialog"}, - [ - m(".modal-dialog", [ - m(".modal-content", [ - m(".modal-header", [ - m("button", {type: "button", class: "close", "data-dismiss": "modal"}, - [m.trust("&times;")]), - m("h4.modal-title", obj.title) - ]), - m(".modal-body", [obj.content]), - m(".modal-footer", [ - m("button.btn.btn-default[data-dismiss='modal']", "Close"), - m("button.btn.btn-primary[data-dismiss='modal']", {onclick: obj.action}, obj.actionText) - ]) - ]) - ]) - ]); + return m.component(app.widgets.modal, obj); }; - u.setContentType = function(doc, contentType){ - var type = ""; - var subtype = ""; - if (doc.tags && doc.tags.length > 0) { - doc.tags.forEach(function(tag){ - var t = tag.match(/^\$type:(.+)/); - var s = tag.match(/^\$subtype:(.+)/); - if (t) type = t[1]; - if (s) subtype = s[1]; - }); - contentType = type+"/"+subtype; - } - return function(xhr) { - xhr.setRequestHeader("Content-Type", contentType); - }; - }; -}());+}());