diff --git a/cethttp/cethttp.go b/cethttp/cethttp.go index ce39549..bf83606 100644 --- a/cethttp/cethttp.go +++ b/cethttp/cethttp.go @@ -1,145 +1,41 @@ package cethttp import ( - "fmt" + "embed" + "io/fs" "log" "net/http" - "strconv" "sync" "github.com/gorilla/mux" - "github.com/senaduka/cetvorke/database" ) +//go:embed static/* +var staticFiles embed.FS + func StartServer(waitgroup *sync.WaitGroup) { defer waitgroup.Done() r := mux.NewRouter() + // Serve embedded static files + staticFS, err := fs.Sub(staticFiles, "static") + if err != nil { + log.Fatal("error:", err) + } + r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.FS(staticFS)))) + r.HandleFunc("/p/{postID}/{slug}", postHandler) r.HandleFunc("/", indexHandler) r.HandleFunc("/a/", archiveHandler) r.HandleFunc("/y/{year}/", yearHandler) r.HandleFunc("/y/{year}/m/{month}/", monthHandler) + r.HandleFunc("/editor/e/{postID}", editHandler) + r.HandleFunc("/editor/n/", newHandler) + r.HandeeFunc("/editor", editorHandler) - err := http.ListenAndServe(":8018", r) + err = http.ListenAndServe(":8018", r) if err != nil { log.Fatal("error:", err) } } - -func indexHandler(w http.ResponseWriter, r *http.Request) { - links, err := database.GetRecentLinks() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - response := "

IslamBosna

\n

Najnoviji članci

\n" - - for _, link := range links { - response += "

" + link.HTMLLink() + "

\n" - } - - response = html5Page(response) - - w.Write([]byte(response)) -} - -func postHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - fmt.Println(vars) - postID, err := strconv.Atoi(vars["postID"]) - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - post, err := database.GetPost(postID) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - response := post.HTMLPage() - - response = html5Page(response) - - w.Write([]byte(response)) -} - -func archiveHandler(w http.ResponseWriter, r *http.Request) { - links, title, err := database.GetAllYearLinks() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - response := "

" + title + "

\n" - - for _, link := range links { - response += "

" + link.HTMLArchiveLink() + "

\n" - } - - response = html5Page(response) - - w.Write([]byte(response)) -} - -func yearHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - year, err := strconv.Atoi(vars["year"]) - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - links, title, err := database.GetMonthLinksByYear(year) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - response := "

" + title + "

\n" - - for _, link := range links { - response += "

" + link.HTMLArchiveMonthLink() + "

\n" - } - - response = html5Page(response) - - w.Write([]byte(response)) -} - -func monthHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - year, err := strconv.Atoi(vars["year"]) - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - month, err := strconv.Atoi(vars["month"]) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - links, title, err := database.GetLinksByMonth(year, month) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - response := "

" + title + "

\n" - - for _, link := range links { - response += "

" + link.HTMLLink() + "

\n" - } - - response = html5Page(response) - w.Write([]byte(response)) -} diff --git a/cethttp/editor.go b/cethttp/editor.go new file mode 100644 index 0000000..8e10285 --- /dev/null +++ b/cethttp/editor.go @@ -0,0 +1,16 @@ +package cethttp + +const editorPage = ` +
+ + +
+ +` diff --git a/cethttp/editor_handlers.go b/cethttp/editor_handlers.go new file mode 100644 index 0000000..058794d --- /dev/null +++ b/cethttp/editor_handlers.go @@ -0,0 +1 @@ +package cethttp diff --git a/cethttp/reader_handlers.go b/cethttp/reader_handlers.go new file mode 100644 index 0000000..44254c1 --- /dev/null +++ b/cethttp/reader_handlers.go @@ -0,0 +1,126 @@ +package cethttp + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/gorilla/mux" + "github.com/senaduka/cetvorke/database" +) + +func indexHandler(w http.ResponseWriter, r *http.Request) { + links, err := database.GetRecentLinks() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + response := "

IslamBosna

\n

Najnoviji članci

\n" + + for _, link := range links { + response += "

" + link.HTMLLink() + "

\n" + } + + response = html5Page(response) + + w.Write([]byte(response)) +} + +func postHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + fmt.Println(vars) + postID, err := strconv.Atoi(vars["postID"]) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + post, err := database.GetPost(postID) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + response := post.HTMLPage() + + response = html5Page(response) + + w.Write([]byte(response)) +} + +func archiveHandler(w http.ResponseWriter, r *http.Request) { + links, title, err := database.GetAllYearLinks() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + response := "

" + title + "

\n" + + for _, link := range links { + response += "

" + link.HTMLArchiveLink() + "

\n" + } + + response = html5Page(response) + + w.Write([]byte(response)) +} + +func yearHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + year, err := strconv.Atoi(vars["year"]) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + links, title, err := database.GetMonthLinksByYear(year) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + response := "

" + title + "

\n" + + for _, link := range links { + response += "

" + link.HTMLArchiveMonthLink() + "

\n" + } + + response = html5Page(response) + + w.Write([]byte(response)) +} + +func monthHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + year, err := strconv.Atoi(vars["year"]) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + month, err := strconv.Atoi(vars["month"]) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + links, title, err := database.GetLinksByMonth(year, month) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + response := "

" + title + "

\n" + + for _, link := range links { + response += "

" + link.HTMLLink() + "

\n" + } + + response = html5Page(response) + w.Write([]byte(response)) +} diff --git a/cethttp/static/markdown.js b/cethttp/static/markdown.js new file mode 100644 index 0000000..321254a --- /dev/null +++ b/cethttp/static/markdown.js @@ -0,0 +1,78 @@ +/*! Licensed under MIT, https://github.com/sofish/pen */ +(function(root) { + + // only works with Pen + if(!root.Pen) return; + + // markdown covertor obj + var covertor = { + keymap: { '96': '`', '62': '>', '49': '1', '46': '.', '45': '-', '42': '*', '35': '#'}, + stack : [] + }; + + // return valid markdown syntax + covertor.valid = function(str) { + var len = str.length; + + if(str.match(/[#]{1,6}/)) { + return ['h' + len, len]; + } else if(str === '```') { + return ['pre', len]; + } else if(str === '>') { + return ['blockquote', len]; + } else if(str === '1.') { + return ['insertorderedlist', len]; + } else if(str === '-' || str === '*') { + return ['insertunorderedlist', len]; + } else if(str.match(/(?:\.|\*|\-){3,}/)) { + return ['inserthorizontalrule', len]; + } + }; + + // parse command + covertor.parse = function(e) { + var code = e.keyCode || e.which; + + // when `space` is pressed + if (code === 32) { + var markdownSyntax = this.stack.join(''); + // reset stack + this.stack = []; + + var cmd = this.valid(markdownSyntax); + if (cmd) { + // prevents leading space after executing command + e.preventDefault(); + return cmd; + } + } + + // make cmd + if(this.keymap[code]) this.stack.push(this.keymap[code]); + + return false; + }; + + // exec command + covertor.action = function(pen, cmd) { + + // only apply effect at line start + if(pen.selection.focusOffset > cmd[1]) return; + + var node = pen.selection.focusNode; + node.textContent = node.textContent.slice(cmd[1]); + pen.execCommand(cmd[0]); + }; + + // init covertor + covertor.init = function(pen) { + pen.on('keypress', function(e) { + var cmd = covertor.parse(e); + if(cmd) return covertor.action(pen, cmd); + }); + }; + + // append to Pen + root.Pen.prototype.markdown = covertor; + +}(window)); diff --git a/cethttp/static/pen.css b/cethttp/static/pen.css new file mode 100644 index 0000000..77a91aa --- /dev/null +++ b/cethttp/static/pen.css @@ -0,0 +1,175 @@ +/*! Licensed under MIT, https://github.com/sofish/pen */ + +/* basic reset */ +.pen, .pen-menu, .pen-input, .pen textarea{font:400 1.16em/1.45 Palatino, Optima, Georgia, serif;color:#331;} +.pen:focus{outline:none;} +.pen fieldset, img {border: 0;} +.pen blockquote{padding-left:10px;margin-left:-14px;border-left:4px solid #1abf89;} +.pen a{color:#1abf89;} +.pen del{text-decoration:line-through;} +.pen sub, .pen sup {font-size:75%;position:relative;vertical-align:text-top;} +:root .pen sub, :root .pen sup{vertical-align:baseline; /* for ie9 and other mordern browsers */} +.pen sup {top:-0.5em;} +.pen sub {bottom:-0.25em;} +.pen hr{border:none;border-bottom:1px solid #cfcfcf;margin-bottom:25px;*color:pink;*filter:chroma(color=pink);height:10px;*margin:-7px 0 15px;} +.pen small{font-size:0.8em;color:#888;} +.pen em, .pen b, .pen strong{font-weight:700;} +.pen pre{white-space:pre-wrap;padding:0.85em;background:#f8f8f8;} + +/* block-level element margin */ +.pen p, .pen pre, .pen ul, .pen ol, .pen dl, .pen form, .pen table, .pen blockquote{margin-bottom:16px;} + +/* headers */ +.pen h1, .pen h2, .pen h3, .pen h4, .pen h5, .pen h6{margin-bottom:16px;font-weight:700;line-height:1.2;} +.pen h1{font-size:2em;} +.pen h2{font-size:1.8em;} +.pen h3{font-size:1.6em;} +.pen h4{font-size:1.4em;} +.pen h5, .pen h6{font-size:1.2em;} + +/* list */ +.pen ul, .pen ol{margin-left:1.2em;} +.pen ul, .pen-ul{list-style:disc;} +.pen ol, .pen-ol{list-style:decimal;} +.pen li ul, .pen li ol, .pen-ul ul, .pen-ul ol, .pen-ol ul, .pen-ol ol{margin:0 2em 0 1.2em;} +.pen li ul, .pen-ul ul, .pen-ol ul{list-style: circle;} + +/* pen menu */ +.pen-menu [class^="icon-"], .pen-menu [class*=" icon-"] { /* reset to avoid conflicts with Bootstrap */ + background: transparent; + background-image: none; +} +.pen-menu { min-width: 320px; } +.pen-menu, .pen-input{font-size:14px;line-height:1;} +.pen-menu{white-space:nowrap;box-shadow:1px 2px 3px -2px #222;background:#333;background-image:linear-gradient(to bottom, #222, #333);opacity:0.9;position:fixed;height:36px;border:1px solid #333;border-radius:3px;display:none;z-index:1000;} +.pen-menu:after {top:100%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none;} +.pen-menu:after {border-color:rgba(51, 51, 51, 0);border-top-color:#333;border-width:6px;left:50%;margin-left:-6px;} +.pen-menu-below:after {top: -11px; display:block; -moz-transform: rotate(180deg); -webkit-transform: rotate(180deg); -ms-transform: rotate(180deg); -o-transform: rotate(180deg); transform: rotate(180deg);} +.pen-icon{font:normal 900 16px/40px Georgia serif;min-width:20px;display:inline-block;padding:0 10px;height:36px;overflow:hidden;color:#fff;text-align:center;cursor:pointer;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none;} +.pen-icon:first-of-type{border-top-left-radius:3px;border-bottom-left-radius:3px;} +.pen-icon:last-of-type{border-top-right-radius:3px;border-bottom-right-radius:3px;} +.pen-icon:hover{background:#000;} +.pen-icon.active{color:#1abf89;background:#000;box-shadow:inset 2px 2px 4px #000;} +.pen-input{position:absolute;width:100%;left:0;top:0;height:36px;line-height:20px;background:#333;color:#fff;border:none;text-align:center;display:none;font-family:arial, sans-serif;} +.pen-input:focus{outline:none;} + +.pen-textarea{display:block;background:#f8f8f8;padding:20px;} +.pen textarea{font-size:14px;border:none;background:none;width:100%;_height:200px;min-height:200px;resize:none;} + +@font-face { + font-family: 'pen'; + src: url('font/fontello.eot?370dad08'); + src: url('font/fontello.eot?370dad08#iefix') format('embedded-opentype'), + url('font/fontello.woff?370dad08') format('woff'), + url('font/fontello.ttf?370dad08') format('truetype'), + url('font/fontello.svg?370dad08#fontello') format('svg'); + font-weight: normal; + font-style: normal; +} + +.pen-menu [class^="icon-"]:before, .pen-menu [class*=" icon-"]:before { + font-family: "pen"; + font-style: normal; + font-weight: normal; + speak: none; + display: inline-block; + text-decoration: inherit; + width: 1em; + margin-right: .2em; + text-align: center; + font-variant: normal; + text-transform: none; + line-height: 1em; + margin-left: .2em; +} +.pen-menu .icon-location:before { content: '\e815'; } /* '' */ +.pen-menu .icon-fit:before { content: '\e80f'; } /* '' */ +.pen-menu .icon-bold:before { content: '\e805'; } /* '' */ +.pen-menu .icon-italic:before { content: '\e806'; } /* '' */ +.pen-menu .icon-justifyleft:before { content: '\e80a'; } /* '' */ +.pen-menu .icon-justifycenter:before { content: '\e80b'; } /* '' */ +.pen-menu .icon-justifyright:before { content: '\e80c'; } /* '' */ +.pen-menu .icon-justifyfull:before { content: '\e80d'; } /* '' */ +.pen-menu .icon-outdent:before { content: '\e800'; } /* '' */ +.pen-menu .icon-indent:before { content: '\e801'; } /* '' */ +.pen-menu .icon-mode:before { content: '\e813'; } /* '' */ +.pen-menu .icon-fullscreen:before { content: '\e80e'; } /* '' */ +.pen-menu .icon-insertunorderedlist:before { content: '\e802'; } /* '' */ +.pen-menu .icon-insertorderedlist:before { content: '\e803'; } /* '' */ +.pen-menu .icon-strikethrough:before { content: '\e807'; } /* '' */ +.pen-menu .icon-underline:before { content: '\e804'; } /* '' */ +.pen-menu .icon-blockquote:before { content: '\e814'; } /* '' */ +.pen-menu .icon-undo:before { content: '\e817'; } /* '' */ +.pen-menu .icon-code:before { content: '\e816'; } /* '' */ +.pen-menu .icon-pre:before { content: '\e816'; } /* '' */ +.pen-menu .icon-unlink:before { content: '\e811'; } /* '' */ +.pen-menu .icon-superscript:before { content: '\e808'; } /* '' */ +.pen-menu .icon-subscript:before { content: '\e809'; } /* '' */ +.pen-menu .icon-inserthorizontalrule:before { content: '\e818'; } /* '' */ +.pen-menu .icon-pin:before { content: '\e812'; } /* '' */ +.pen-menu .icon-createlink:before { content: '\e810'; } /* '' */ +.pen-menu .icon-h1:before { content: 'H1'; } +.pen-menu .icon-h2:before { content: 'H2'; } +.pen-menu .icon-h3:before { content: 'H3'; } +.pen-menu .icon-h4:before { content: 'H4'; } +.pen-menu .icon-h5:before { content: 'H5'; } +.pen-menu .icon-h6:before { content: 'H6'; } +.pen-menu .icon-p:before { content: 'P'; } +.pen-menu .icon-insertimage:before { width:1.8em;margin:0;position:relative;top:-2px;content:'IMG';font-size:12px;border:1px solid #fff;padding:2px;border-radius:2px; } +.pen { + position: relative; +} +.pen.hinted h1:before, +.pen.hinted h2:before, +.pen.hinted h3:before, +.pen.hinted h4:before, +.pen.hinted h5:before, +.pen.hinted h6:before, +.pen.hinted blockquote:before, +.pen.hinted hr:before { + color: #eee; + position: absolute; + right: 100%; + white-space: nowrap; + padding-right: 10px; +} +.pen.hinted blockquote { border-left: 0; margin-left: 0; padding-left: 0; } +.pen.hinted blockquote:before { + color: #1abf89; + content: ">"; + font-weight: bold; + vertical-align: center; +} +.pen.hinted h1:before { content: "#";} +.pen.hinted h2:before { content: "##";} +.pen.hinted h3:before { content: "###";} +.pen.hinted h4:before { content: "####";} +.pen.hinted h5:before { content: "#####";} +.pen.hinted h6:before { content: "######";} +.pen.hinted hr:before { content: "﹘﹘﹘"; line-height: 1.2; vertical-align: bottom; } + +.pen.hinted pre:before, .pen.hinted pre:after { + content: "```"; + display: block; + color: #ccc; +} + +.pen.hinted ul { list-style: none; } +.pen.hinted ul li:before { + content: "*"; + color: #999; + line-height: 1; + vertical-align: bottom; + margin-left: -1.2em; + display: inline-block; + width: 1.2em; +} + +.pen.hinted b:before, .pen.hinted b:after { content: "**"; color: #eee; font-weight: normal; } +.pen.hinted i:before, .pen.hinted i:after { content: "*"; color: #eee; } + +.pen.hinted a { text-decoration: none; } +.pen.hinted a:before {content: "["; color: #ddd; } +.pen.hinted a:after { content: "](" attr(href) ")"; color: #ddd; } + +.pen-placeholder:after { position: absolute; top: 0; left: 0; content: attr(data-placeholder); color: #999; cursor: text; } diff --git a/cethttp/static/pen.js b/cethttp/static/pen.js new file mode 100644 index 0000000..3dbdd76 --- /dev/null +++ b/cethttp/static/pen.js @@ -0,0 +1,856 @@ +/*! Licensed under MIT, https://github.com/sofish/pen */ +(function(root, doc) { + + var Pen, debugMode, selection, utils = {}; + var toString = Object.prototype.toString; + var slice = Array.prototype.slice; + + // allow command list + var commandsReg = { + block: /^(?:p|h[1-6]|blockquote|pre)$/, + inline: /^(?:bold|italic|underline|insertorderedlist|insertunorderedlist|indent|outdent)$/, + source: /^(?:createlink|unlink)$/, + insert: /^(?:inserthorizontalrule|insertimage|insert)$/, + wrap: /^(?:code)$/ + }; + + var lineBreakReg = /^(?:blockquote|pre|div)$/i; + + var effectNodeReg = /(?:[pubia]|h[1-6]|blockquote|[uo]l|li)/i; + + var strReg = { + whiteSpace: /(^\s+)|(\s+$)/g, + mailTo: /^(?!mailto:|.+\/|.+#|.+\?)(.*@.*\..+)$/, + http: /^(?!\w+?:\/\/|mailto:|\/|\.\/|\?|#)(.*)$/ + }; + + var autoLinkReg = { + url: /((https?|ftp):\/\/|www\.)[^\s<]{3,}/gi, + prefix: /^(?:https?|ftp):\/\//i, + notLink: /^(?:img|a|input|audio|video|source|code|pre|script|head|title|style)$/i, + maxLength: 100 + }; + + // type detect + utils.is = function(obj, type) { + return toString.call(obj).slice(8, -1) === type; + }; + + utils.forEach = function(obj, iterator, arrayLike) { + if (!obj) return; + if (arrayLike == null) arrayLike = utils.is(obj, 'Array'); + if (arrayLike) { + for (var i = 0, l = obj.length; i < l; i++) iterator(obj[i], i, obj); + } else { + for (var key in obj) { + if (obj.hasOwnProperty(key)) iterator(obj[key], key, obj); + } + } + }; + + // copy props from a obj + utils.copy = function(defaults, source) { + utils.forEach(source, function (value, key) { + defaults[key] = utils.is(value, 'Object') ? utils.copy({}, value) : + utils.is(value, 'Array') ? utils.copy([], value) : value; + }); + return defaults; + }; + + // log + utils.log = function(message, force) { + if (debugMode || force) + console.log('%cPEN DEBUGGER: %c' + message, 'font-family:arial,sans-serif;color:#1abf89;line-height:2em;', 'font-family:cursor,monospace;color:#333;'); + }; + + utils.delayExec = function (fn) { + var timer = null; + return function (delay) { + clearTimeout(timer); + timer = setTimeout(function() { + fn(); + }, delay || 1); + }; + }; + + // merge: make it easy to have a fallback + utils.merge = function(config) { + + // default settings + var defaults = { + class: 'pen', + debug: false, + toolbar: null, // custom toolbar + stay: config.stay || !config.debug, + stayMsg: 'Are you going to leave here?', + textarea: '', + list: [ + 'blockquote', 'h2', 'h3', 'p', 'code', 'insertorderedlist', 'insertunorderedlist', 'inserthorizontalrule', + 'indent', 'outdent', 'bold', 'italic', 'underline', 'createlink', 'insertimage' + ], + titles: {}, + cleanAttrs: ['id', 'class', 'style', 'name'], + cleanTags: ['script'], + linksInNewWindow: false + }; + + // user-friendly config + if (config.nodeType === 1) { + defaults.editor = config; + } else if (config.match && config.match(/^#[\S]+$/)) { + defaults.editor = doc.getElementById(config.slice(1)); + } else { + defaults = utils.copy(defaults, config); + } + + return defaults; + }; + + function commandOverall(ctx, cmd, val) { + var message = ' to exec 「' + cmd + '」 command' + (val ? (' with value: ' + val) : ''); + + try { + doc.execCommand(cmd, false, val); + } catch(err) { + // TODO: there's an error when insert a image to document, but not a bug + return utils.log('fail' + message, true); + } + + utils.log('success' + message); + } + + function commandInsert(ctx, name, val) { + var node = getNode(ctx); + if (!node) return; + ctx._range.selectNode(node); + ctx._range.collapse(false); + + // hide menu when a image was inserted + if(name === 'insertimage' && ctx._menu) toggleNode(ctx._menu, true); + + return commandOverall(ctx, name, val); + } + + function commandBlock(ctx, name) { + var list = effectNode(ctx, getNode(ctx), true); + if (list.indexOf(name) !== -1) name = 'p'; + return commandOverall(ctx, 'formatblock', name); + } + + function commandWrap(ctx, tag, value) { + value = '<' + tag + '>' + (value||selection.toString()) + ''; + return commandOverall(ctx, 'insertHTML', value); + } + + function commandLink(ctx, tag, value) { + if (ctx.config.linksInNewWindow) { + value = '< a href="' + value + '" target="_blank">' + (selection.toString()) + ''; + return commandOverall(ctx, 'insertHTML', value); + } else { + return commandOverall(ctx, tag, value); + } + } + + function initToolbar(ctx) { + var icons = '', inputStr = ''; + + ctx._toolbar = ctx.config.toolbar; + if (!ctx._toolbar) { + var toolList = ctx.config.list; + utils.forEach(toolList, function (name) { + var klass = 'pen-icon icon-' + name; + var title = ctx.config.titles[name] || ''; + icons += ''; + }, true); + if (toolList.indexOf('createlink') >= 0 || toolList.indexOf('insertimage') >= 0) + icons += inputStr; + } else if (ctx._toolbar.querySelectorAll('[data-action=createlink]').length || + ctx._toolbar.querySelectorAll('[data-action=insertimage]').length) { + icons += inputStr; + } + + if (icons) { + ctx._menu = doc.createElement('div'); + ctx._menu.setAttribute('class', ctx.config.class + '-menu pen-menu'); + ctx._menu.innerHTML = icons; + ctx._inputBar = ctx._menu.querySelector('input'); + toggleNode(ctx._menu, true); + doc.body.appendChild(ctx._menu); + } + if (ctx._toolbar && ctx._inputBar) toggleNode(ctx._inputBar); + } + + function initEvents(ctx) { + var toolbar = ctx._toolbar || ctx._menu, editor = ctx.config.editor; + + var toggleMenu = utils.delayExec(function() { + ctx.highlight().menu(); + }); + var outsideClick = function() {}; + + function updateStatus(delay) { + ctx._range = ctx.getRange(); + toggleMenu(delay); + } + + if (ctx._menu) { + var setpos = function() { + if (ctx._menu.style.display === 'block') ctx.menu(); + }; + + // change menu offset when window resize / scroll + addListener(ctx, root, 'resize', setpos); + addListener(ctx, root, 'scroll', setpos); + + // toggle toolbar on mouse select + var selecting = false; + addListener(ctx, editor, 'mousedown', function() { + selecting = true; + }); + addListener(ctx, editor, 'mouseleave', function() { + if (selecting) updateStatus(800); + selecting = false; + }); + addListener(ctx, editor, 'mouseup', function() { + if (selecting) updateStatus(100); + selecting = false; + }); + // Hide menu when focusing outside of editor + outsideClick = function(e) { + if (ctx._menu && !containsNode(editor, e.target) && !containsNode(ctx._menu, e.target)) { + removeListener(ctx, doc, 'click', outsideClick); + toggleMenu(100); + } + }; + } else { + addListener(ctx, editor, 'click', function() { + updateStatus(0); + }); + } + + addListener(ctx, editor, 'keyup', function(e) { + if (e.which === 8 && ctx.isEmpty()) return lineBreak(ctx, true); + // toggle toolbar on key select + if (e.which !== 13 || e.shiftKey) return updateStatus(400); + var node = getNode(ctx, true); + if (!node || !node.nextSibling || !lineBreakReg.test(node.nodeName)) return; + if (node.nodeName !== node.nextSibling.nodeName) return; + // hack for webkit, make 'enter' behavior like as firefox. + if (node.lastChild.nodeName !== 'BR') node.appendChild(doc.createElement('br')); + utils.forEach(node.nextSibling.childNodes, function(child) { + if (child) node.appendChild(child); + }, true); + node.parentNode.removeChild(node.nextSibling); + focusNode(ctx, node.lastChild, ctx.getRange()); + }); + + // check line break + addListener(ctx, editor, 'keydown', function(e) { + editor.classList.remove('pen-placeholder'); + if (e.which !== 13 || e.shiftKey) return; + var node = getNode(ctx, true); + if (!node || !lineBreakReg.test(node.nodeName)) return; + var lastChild = node.lastChild; + if (!lastChild || !lastChild.previousSibling) return; + if (lastChild.previousSibling.textContent || lastChild.textContent) return; + // quit block mode for 2 'enter' + e.preventDefault(); + var p = doc.createElement('p'); + p.innerHTML = '
'; + node.removeChild(lastChild); + if (!node.nextSibling) node.parentNode.appendChild(p); + else node.parentNode.insertBefore(p, node.nextSibling); + focusNode(ctx, p, ctx.getRange()); + }); + + var menuApply = function(action, value) { + ctx.execCommand(action, value); + ctx._range = ctx.getRange(); + ctx.highlight().menu(); + }; + + // toggle toolbar on key select + addListener(ctx, toolbar, 'click', function(e) { + var node = e.target, action; + + while (node !== toolbar && !(action = node.getAttribute('data-action'))) { + node = node.parentNode; + } + + if (!action) return; + if (!/(?:createlink)|(?:insertimage)/.test(action)) return menuApply(action); + if (!ctx._inputBar) return; + + // create link + var input = ctx._inputBar; + if (toolbar === ctx._menu) toggleNode(input); + else { + ctx._inputActive = true; + ctx.menu(); + } + if (ctx._menu.style.display === 'none') return; + + setTimeout(function() { input.focus(); }, 400); + var createlink = function() { + var inputValue = input.value; + + if (!inputValue) action = 'unlink'; + else { + inputValue = input.value + .replace(strReg.whiteSpace, '') + .replace(strReg.mailTo, 'mailto:$1') + .replace(strReg.http, 'http://$1'); + } + menuApply(action, inputValue); + if (toolbar === ctx._menu) toggleNode(input, false); + else toggleNode(ctx._menu, true); + }; + + input.onkeypress = function(e) { + if (e.which === 13) return createlink(); + }; + + }); + + // listen for placeholder + addListener(ctx, editor, 'focus', function() { + if (ctx.isEmpty()) lineBreak(ctx, true); + addListener(ctx, doc, 'click', outsideClick); + }); + + addListener(ctx, editor, 'blur', function() { + checkPlaceholder(ctx); + ctx.checkContentChange(); + }); + + // listen for paste and clear style + addListener(ctx, editor, 'paste', function() { + setTimeout(function() { + ctx.cleanContent(); + }); + }); + } + + function addListener(ctx, target, type, listener) { + if (ctx._events.hasOwnProperty(type)) { + ctx._events[type].push(listener); + } else { + ctx._eventTargets = ctx._eventTargets || []; + ctx._eventsCache = ctx._eventsCache || []; + var index = ctx._eventTargets.indexOf(target); + if (index < 0) index = ctx._eventTargets.push(target) - 1; + ctx._eventsCache[index] = ctx._eventsCache[index] || {}; + ctx._eventsCache[index][type] = ctx._eventsCache[index][type] || []; + ctx._eventsCache[index][type].push(listener); + + target.addEventListener(type, listener, false); + } + return ctx; + } + + // trigger local events + function triggerListener(ctx, type) { + if (!ctx._events.hasOwnProperty(type)) return; + var args = slice.call(arguments, 2); + utils.forEach(ctx._events[type], function (listener) { + listener.apply(ctx, args); + }); + } + + function removeListener(ctx, target, type, listener) { + var events = ctx._events[type]; + if (!events) { + var _index = ctx._eventTargets.indexOf(target); + if (_index >= 0) events = ctx._eventsCache[_index][type]; + } + if (!events) return ctx; + var index = events.indexOf(listener); + if (index >= 0) events.splice(index, 1); + target.removeEventListener(type, listener, false); + return ctx; + } + + function removeAllListeners(ctx) { + utils.forEach(this._events, function (events) { + events.length = 0; + }, false); + if (!ctx._eventsCache) return ctx; + utils.forEach(ctx._eventsCache, function (events, index) { + var target = ctx._eventTargets[index]; + utils.forEach(events, function (listeners, type) { + utils.forEach(listeners, function (listener) { + target.removeEventListener(type, listener, false); + }, true); + }, false); + }, true); + ctx._eventTargets = []; + ctx._eventsCache = []; + return ctx; + } + + function checkPlaceholder(ctx) { + ctx.config.editor.classList[ctx.isEmpty() ? 'add' : 'remove']('pen-placeholder'); + } + + function trim(str) { + return (str || '').replace(/^\s+|\s+$/g, ''); + } + + // node.contains is not implemented in IE10/IE11 + function containsNode(parent, child) { + if (parent === child) return true; + child = child.parentNode; + while (child) { + if (child === parent) return true; + child = child.parentNode; + } + return false; + } + + function getNode(ctx, byRoot) { + var node, root = ctx.config.editor; + ctx._range = ctx._range || ctx.getRange(); + node = ctx._range.commonAncestorContainer; + if (!node || node === root) return null; + while (node && (node.nodeType !== 1) && (node.parentNode !== root)) node = node.parentNode; + while (node && byRoot && (node.parentNode !== root)) node = node.parentNode; + return containsNode(root, node) ? node : null; + } + + // node effects + function effectNode(ctx, el, returnAsNodeName) { + var nodes = []; + el = el || ctx.config.editor; + while (el && el !== ctx.config.editor) { + if (el.nodeName.match(effectNodeReg)) { + nodes.push(returnAsNodeName ? el.nodeName.toLowerCase() : el); + } + el = el.parentNode; + } + return nodes; + } + + // breakout from node + function lineBreak(ctx, empty) { + var range = ctx._range = ctx.getRange(), node = doc.createElement('p'); + if (empty) ctx.config.editor.innerHTML = ''; + node.innerHTML = '
'; + range.insertNode(node); + focusNode(ctx, node.childNodes[0], range); + } + + function focusNode(ctx, node, range) { + range.setStartAfter(node); + range.setEndBefore(node); + range.collapse(false); + ctx.setRange(range); + } + + function autoLink(node) { + if (node.nodeType === 1) { + if (autoLinkReg.notLink.test(node.tagName)) return; + utils.forEach(node.childNodes, function (child) { + autoLink(child); + }, true); + } else if (node.nodeType === 3) { + var result = urlToLink(node.nodeValue || ''); + if (!result.links) return; + var frag = doc.createDocumentFragment(), + div = doc.createElement('div'); + div.innerHTML = result.text; + while (div.childNodes.length) frag.appendChild(div.childNodes[0]); + node.parentNode.replaceChild(frag, node); + } + } + + function urlToLink(str) { + var count = 0; + str = str.replace(autoLinkReg.url, function(url) { + var realUrl = url, displayUrl = url; + count++; + if (url.length > autoLinkReg.maxLength) displayUrl = url.slice(0, autoLinkReg.maxLength) + '...'; + // Add http prefix if necessary + if (!autoLinkReg.prefix.test(realUrl)) realUrl = 'http://' + realUrl; + return '' + displayUrl + ''; + }); + return {links: count, text: str}; + } + + function toggleNode(node, hide) { + node.style.display = hide ? 'none' : 'block'; + } + + Pen = function(config) { + + if (!config) throw new Error('Can\'t find config'); + + debugMode = config.debug; + + // merge user config + var defaults = utils.merge(config); + + var editor = defaults.editor; + + if (!editor || editor.nodeType !== 1) throw new Error('Can\'t find editor'); + + // set default class + editor.classList.add(defaults.class); + + // set contenteditable + editor.setAttribute('contenteditable', 'true'); + + // assign config + this.config = defaults; + + // set placeholder + if (defaults.placeholder) editor.setAttribute('data-placeholder', defaults.placeholder); + checkPlaceholder(this); + + // save the selection obj + this.selection = selection; + + // define local events + this._events = {change: []}; + + // enable toolbar + initToolbar(this); + + // init events + initEvents(this); + + // to check content change + this._prevContent = this.getContent(); + + // enable markdown covert + if (this.markdown) this.markdown.init(this); + + // stay on the page + if (this.config.stay) this.stay(this.config); + + if(this.config.input) { + this.addOnSubmitListener(this.config.input); + } + }; + + Pen.prototype.on = function(type, listener) { + addListener(this, this.config.editor, type, listener); + return this; + }; + + Pen.prototype.addOnSubmitListener = function(inputElement) { + var form = inputElement.form; + var me = this; + form.addEventListener("submit", function() { + inputElement.value = me.config.saveAsMarkdown ? me.toMd(me.config.editor.innerHTML) : me.config.editor.innerHTML; + }); + }; + + Pen.prototype.isEmpty = function(node) { + node = node || this.config.editor; + return !(node.querySelector('img')) && !(node.querySelector('blockquote')) && + !(node.querySelector('li')) && !trim(node.textContent); + }; + + Pen.prototype.getContent = function() { + return this.isEmpty() ? '' : trim(this.config.editor.innerHTML); + }; + + Pen.prototype.setContent = function(html) { + this.config.editor.innerHTML = html; + this.cleanContent(); + return this; + }; + + Pen.prototype.checkContentChange = function () { + var prevContent = this._prevContent, currentContent = this.getContent(); + if (prevContent === currentContent) return; + this._prevContent = currentContent; + triggerListener(this, 'change', currentContent, prevContent); + }; + + Pen.prototype.getRange = function() { + var editor = this.config.editor, range = selection.rangeCount && selection.getRangeAt(0); + if (!range) range = doc.createRange(); + if (!containsNode(editor, range.commonAncestorContainer)) { + range.selectNodeContents(editor); + range.collapse(false); + } + return range; + }; + + Pen.prototype.setRange = function(range) { + range = range || this._range; + if (!range) { + range = this.getRange(); + range.collapse(false); // set to end + } + try { + selection.removeAllRanges(); + selection.addRange(range); + } catch (e) {/* IE throws error sometimes*/} + return this; + }; + + Pen.prototype.focus = function(focusStart) { + if (!focusStart) this.setRange(); + this.config.editor.focus(); + return this; + }; + + Pen.prototype.execCommand = function(name, value) { + name = name.toLowerCase(); + this.setRange(); + + if (commandsReg.block.test(name)) { + commandBlock(this, name); + } else if (commandsReg.inline.test(name)) { + commandOverall(this, name, value); + } else if (commandsReg.source.test(name)) { + commandLink(this, name, value); + } else if (commandsReg.insert.test(name)) { + commandInsert(this, name, value); + } else if (commandsReg.wrap.test(name)) { + commandWrap(this, name, value); + } else { + utils.log('can not find command function for name: ' + name + (value ? (', value: ' + value) : ''), true); + } + if (name === 'indent') this.checkContentChange(); + else this.cleanContent({cleanAttrs: ['style']}); + }; + + // remove attrs and tags + // pen.cleanContent({cleanAttrs: ['style'], cleanTags: ['id']}) + Pen.prototype.cleanContent = function(options) { + var editor = this.config.editor; + + if (!options) options = this.config; + utils.forEach(options.cleanAttrs, function (attr) { + utils.forEach(editor.querySelectorAll('[' + attr + ']'), function(item) { + item.removeAttribute(attr); + }, true); + }, true); + utils.forEach(options.cleanTags, function (tag) { + utils.forEach(editor.querySelectorAll(tag), function(item) { + item.parentNode.removeChild(item); + }, true); + }, true); + + checkPlaceholder(this); + this.checkContentChange(); + return this; + }; + + // auto link content, return content + Pen.prototype.autoLink = function() { + autoLink(this.config.editor); + return this.getContent(); + }; + + // highlight menu + Pen.prototype.highlight = function() { + var toolbar = this._toolbar || this._menu + , node = getNode(this); + // remove all highlights + utils.forEach(toolbar.querySelectorAll('.active'), function(el) { + el.classList.remove('active'); + }, true); + + if (!node) return this; + + var effects = effectNode(this, node) + , inputBar = this._inputBar + , highlight; + + if (inputBar && toolbar === this._menu) { + // display link input if createlink enabled + inputBar.style.display = 'none'; + // reset link input value + inputBar.value = ''; + } + + highlight = function(str) { + if (!str) return; + var el = toolbar.querySelector('[data-action=' + str + ']'); + return el && el.classList.add('active'); + }; + utils.forEach(effects, function(item) { + var tag = item.nodeName.toLowerCase(); + switch(tag) { + case 'a': + if (inputBar) inputBar.value = item.getAttribute('href'); + tag = 'createlink'; + break; + case 'img': + if (inputBar) inputBar.value = item.getAttribute('src'); + tag = 'insertimage'; + break; + case 'i': + tag = 'italic'; + break; + case 'u': + tag = 'underline'; + break; + case 'b': + tag = 'bold'; + break; + case 'pre': + case 'code': + tag = 'code'; + break; + case 'ul': + tag = 'insertunorderedlist'; + break; + case 'ol': + tag = 'insertorderedlist'; + break; + case 'li': + tag = 'indent'; + break; + } + highlight(tag); + }, true); + + return this; + }; + + // show menu + Pen.prototype.menu = function() { + if (!this._menu) return this; + if (selection.isCollapsed) { + this._menu.style.display = 'none'; //hide menu + this._inputActive = false; + return this; + } + if (this._toolbar) { + if (!this._inputBar || !this._inputActive) return this; + } + var offset = this._range.getBoundingClientRect() + , menuPadding = 10 + , top = offset.top - menuPadding + , left = offset.left + (offset.width / 2) + , menu = this._menu + , menuOffset = {x: 0, y: 0} + , stylesheet = this._stylesheet; + + // fixes some browser double click visual discontinuity + // if the offset has no width or height it should not be used + if (offset.width === 0 && offset.height === 0) return this; + + // store the stylesheet used for positioning the menu horizontally + if (this._stylesheet === undefined) { + var style = document.createElement("style"); + document.head.appendChild(style); + this._stylesheet = stylesheet = style.sheet; + } + // display block to caculate its width & height + menu.style.display = 'block'; + + menuOffset.x = left - (menu.clientWidth / 2); + menuOffset.y = top - menu.clientHeight; + + // check to see if menu has over-extended its bounding box. if it has, + // 1) apply a new class if overflowed on top; + // 2) apply a new rule if overflowed on the left + if (stylesheet.cssRules.length > 0) { + stylesheet.deleteRule(0); + } + if (menuOffset.x < 0) { + menuOffset.x = 0; + stylesheet.insertRule('.pen-menu:after {left: ' + left + 'px;}', 0); + } else { + stylesheet.insertRule('.pen-menu:after {left: 50%; }', 0); + } + if (menuOffset.y < 0) { + menu.classList.add('pen-menu-below'); + menuOffset.y = offset.top + offset.height + menuPadding; + } else { + menu.classList.remove('pen-menu-below'); + } + + menu.style.top = menuOffset.y + 'px'; + menu.style.left = menuOffset.x + 'px'; + return this; + }; + + Pen.prototype.stay = function(config) { + var ctx = this; + if (!window.onbeforeunload) { + window.onbeforeunload = function() { + if (!ctx._isDestroyed) return config.stayMsg; + }; + } + }; + + Pen.prototype.destroy = function(isAJoke) { + var destroy = isAJoke ? false : true + , attr = isAJoke ? 'setAttribute' : 'removeAttribute'; + + if (!isAJoke) { + removeAllListeners(this); + try { + selection.removeAllRanges(); + if (this._menu) this._menu.parentNode.removeChild(this._menu); + } catch (e) {/* IE throws error sometimes*/} + } else { + initToolbar(this); + initEvents(this); + } + this._isDestroyed = destroy; + this.config.editor[attr]('contenteditable', ''); + + return this; + }; + + Pen.prototype.rebuild = function() { + return this.destroy('it\'s a joke'); + }; + + // a fallback for old browers + root.Pen = function(config) { + if (!config) return utils.log('can\'t find config', true); + + var defaults = utils.merge(config) + , klass = defaults.editor.getAttribute('class'); + + klass = klass ? klass.replace(/\bpen\b/g, '') + ' pen-textarea ' + defaults.class : 'pen pen-textarea'; + defaults.editor.setAttribute('class', klass); + defaults.editor.innerHTML = defaults.textarea; + return defaults.editor; + }; + + // export content as markdown + var regs = { + a: [/]*href=["']([^"]+|[^']+)\b[^>]*>(.*?)<\/a>/ig, '[$2]($1)'], + img: [/]*src=["']([^\"+|[^']+)[^>]*>/ig, '![]($1)'], + b: [/]*>(.*?)<\/b>/ig, '**$1**'], + i: [/]*>(.*?)<\/i>/ig, '***$1***'], + h: [/]*>(.*?)<\/h\1>/ig, function(a, b, c) { + return '\n' + ('######'.slice(0, b)) + ' ' + c + '\n'; + }], + li: [/<(li)\b[^>]*>(.*?)<\/\1>/ig, '* $2\n'], + blockquote: [/<(blockquote)\b[^>]*>(.*?)<\/\1>/ig, '\n> $2\n'], + pre: [/]*>(.*?)<\/pre>/ig, '\n```\n$1\n```\n'], + code: [/]*>(.*?)<\/code>/ig, '\n`\n$1\n`\n'], + p: [/]*>(.*?)<\/p>/ig, '\n$1\n'], + hr: [/]*>/ig, '\n---\n'] + }; + + Pen.prototype.toMd = function() { + var html = this.getContent() + .replace(/\n+/g, '') // remove line break + .replace(/<([uo])l\b[^>]*>(.*?)<\/\1l>/ig, '$2'); // remove ul/ol + + for(var p in regs) { + if (regs.hasOwnProperty(p)) + html = html.replace.apply(html, regs[p]); + } + return html.replace(/\*{5}/g, '**'); + }; + + // make it accessible + if (doc.getSelection) { + selection = doc.getSelection(); + root.Pen = Pen; + } + +}(window, document)); diff --git a/cethttp/template.go b/cethttp/template.go index c3f96cd..2318d3d 100644 --- a/cethttp/template.go +++ b/cethttp/template.go @@ -51,7 +51,9 @@ const html5Template = ` color: #e6fbfb; } - + + + diff --git a/cetvorke b/cetvorke index f498f61..f00b687 100755 Binary files a/cetvorke and b/cetvorke differ diff --git a/database/post.go b/database/post.go index 01eaa83..1c4fd38 100644 --- a/database/post.go +++ b/database/post.go @@ -33,7 +33,7 @@ func (p *Post) HTMLPage() string { func GetPost(id int) (*Post, error) { db := GetDB() post := &Post{} - err := db.Get(post, "SELECT id, post_title, date, markdown_content, gemtext_content, title_slug, year, month, day FROM posts WHERE id = ?", id) + err := db.Get(post, "SELECT id, post_title, date, markdown_content, gemtext_content, title_slug, year, month, day FROM posts WHERE id = ? and gemtext_content is not null", id) if err != nil { return nil, err }