diff --git a/templates/bare.tmpl b/templates/bare.tmpl index cd0fa8c..c2fef0f 100644 --- a/templates/bare.tmpl +++ b/templates/bare.tmpl @@ -1,266 +1,266 @@ {{define "pad"}}<!DOCTYPE HTML> <html> <head> <title>{{if .Editing}}Editing {{if .Post.Title}}{{.Post.Title}}{{else}}{{.Post.Id}}{{end}}{{else}}New Post{{end}} — {{.SiteName}}</title> <link rel="stylesheet" type="text/css" href="/css/write.css" /> {{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}} <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="google" value="notranslate"> </head> <body id="pad" class="light"> <div id="overlay"></div> - <textarea id="writer" placeholder="Write..." class="{{.Post.Font}}" autofocus>{{if .Post.Title}}# {{.Post.Title}} + <textarea dir="auto" id="writer" placeholder="Write..." class="{{.Post.Font}}" autofocus>{{if .Post.Title}}# {{.Post.Title}} {{end}}{{.Post.Content}}</textarea> <div class="alert success hidden" id="edited-elsewhere">This post has been updated elsewhere since you last published! <a href="#" id="erase-edit">Delete draft and reload</a>.</div> <header id="tools"> <div id="clip"> {{if not .SingleUser}}<h1>{{if .Chorus}}<a href="/" title="Home">{{else}}<a href="/me/c/" title="View blogs">{{end}}{{.SiteName}}</a></h1>{{end}} <nav id="target" {{if .SingleUser}}style="margin-left:0"{{end}}><ul> <li>{{if .Blogs}}<a href="{{$c := index .Blogs 0}}{{$c.CanonicalURL}}">My Posts</a>{{else}}<a>Draft</a>{{end}}</li> </ul></nav> <span id="wc" class="hidden if-room room-4">0 words</span> </div> <noscript style="margin-left: 2em;"><strong>NOTE</strong>: for now, you'll need JavaScript enabled to post.</noscript> <div id="belt"> {{if .Editing}}<div class="tool hidden if-room"><a href="{{if .EditCollection}}{{.EditCollection.CanonicalURL}}{{.Post.Slug}}/edit/meta{{else}}/{{if .SingleUser}}d/{{end}}{{.Post.Id}}/meta{{end}}" title="Edit post metadata" id="edit-meta"><img class="ic-24dp" src="/img/ic_info_dark@2x.png" /></a></div>{{end}} <div class="tool"><button title="Publish your writing" id="publish" style="font-weight: bold">Post</button></div> </div> </header> <script src="/js/h.js"></script> <script> var $writer = H.getEl('writer'); var $btnPublish = H.getEl('publish'); var $btnEraseEdit = H.getEl('edited-elsewhere'); var $wc = H.getEl("wc"); var updateWordCount = function() { var words = 0; var val = $writer.el.value.trim(); if (val != '') { words = $writer.el.value.trim().replace(/\s+/gi, ' ').split(' ').length; } $wc.el.innerText = words + " word" + (words != 1 ? "s" : ""); }; var setButtonStates = function() { if (!canPublish) { $btnPublish.el.className = 'disabled'; return; } if ($writer.el.value.length === 0 || (draftDoc != 'lastDoc' && $writer.el.value == origDoc)) { $btnPublish.el.className = 'disabled'; } else { $btnPublish.el.className = ''; } }; {{if .Post.Id}}var draftDoc = 'draft{{.Post.Id}}'; var origDoc = '{{.Post.Content}}';{{else}}var draftDoc = 'lastDoc';{{end}} var updatedStr = '{{.Post.Updated8601}}'; var updated = null; if (updatedStr != '') { updated = new Date(updatedStr); } var ok = H.load($writer, draftDoc, true, updated); if (!ok) { // Show "edited elsewhere" warning $btnEraseEdit.el.classList.remove('hidden'); } var defaultTimeSet = false; updateWordCount(); var typingTimer; var doneTypingInterval = 200; var posts; {{if and .Post.Id (not .Post.Slug)}} var token = null; var curPostIdx; posts = JSON.parse(H.get('posts', '[]')); for (var i=0; i<posts.length; i++) { if (posts[i].id == "{{.Post.Id}}") { token = posts[i].token; break; } } var canPublish = token != null; {{else}}var canPublish = true;{{end}} var publishing = false; var justPublished = false; var publish = function(content, font) { {{if and (and .Post.Id (not .Post.Slug)) (not .User)}} if (!token) { alert("You don't have permission to update this post."); return; } {{end}} publishing = true; $btnPublish.el.textContent = 'Posting...'; $btnPublish.el.disabled = true; var http = new XMLHttpRequest(); var post = H.getTitleStrict(content); var params = { body: post.content, title: post.title, font: font }; {{ if .Post.Slug }} var url = "/api/collections/{{.EditCollection.Alias}}/posts/{{.Post.Id}}"; {{ else if .Post.Id }} var url = "/api/posts/{{.Post.Id}}"; if (typeof token === 'undefined' || !token) { token = ""; } params.token = token; {{ else }} var lang = navigator.languages ? navigator.languages[0] : (navigator.language || navigator.userLanguage); lang = lang.substring(0, 2); params.lang = lang; var url = "/api/posts"; var postTarget = '{{if .Blogs}}{{$c := index .Blogs 0}}{{$c.Alias}}{{else}}anonymous{{end}}'; if (postTarget != 'anonymous') { url = "/api/collections/" + postTarget + "/posts"; } {{ end }} http.open("POST", url, true); // Send the proper header information along with the request http.setRequestHeader("Content-type", "application/json"); http.onreadystatechange = function() { if (http.readyState == 4) { publishing = false; if (http.status == 200 || http.status == 201) { data = JSON.parse(http.responseText); id = data.data.id; nextURL = '{{if .SingleUser}}/d{{end}}/'+id; localStorage.setItem('draft'+id+'-published', new Date().toISOString()); {{ if not .Post.Id }} // Post created if (postTarget != 'anonymous') { nextURL = {{if not .SingleUser}}'/'+postTarget+{{end}}'/'+data.data.slug; } editToken = data.data.token; {{ if not .User }}if (postTarget == 'anonymous') { // Save the data var posts = JSON.parse(H.get('posts', '[]')); {{if .Post.Id}}var newPost = H.createPost("{{.Post.Id}}", token, content); for (var i=0; i<posts.length; i++) { if (posts[i].id == "{{.Post.Id}}") { posts[i].title = newPost.title; posts[i].summary = newPost.summary; break; } } nextURL = "/pad/posts";{{else}}posts.push(H.createPost(id, editToken, content));{{end}} H.set('posts', JSON.stringify(posts)); } {{ end }} {{ end }} justPublished = true; if (draftDoc != 'lastDoc') { H.remove(draftDoc); {{if .Editing}}H.remove('draft{{.Post.Id}}font');{{end}} } else { H.set(draftDoc, ''); } {{if .EditCollection}} window.location = '{{.EditCollection.CanonicalURL}}{{.Post.Slug}}'; {{else}} window.location = nextURL; {{end}} } else { $btnPublish.el.textContent = 'Post'; alert("Failed to post. Please try again."); } } } http.send(JSON.stringify(params)); }; setButtonStates(); $writer.on('keyup input', function() { setButtonStates(); clearTimeout(typingTimer); typingTimer = setTimeout(doneTyping, doneTypingInterval); }, false); $writer.on('keydown', function(e) { clearTimeout(typingTimer); if (e.keyCode == 13 && (e.metaKey || e.ctrlKey)) { $btnPublish.el.click(); } }); $btnPublish.on('click', function(e) { e.preventDefault(); if (!publishing && $writer.el.value) { var content = $writer.el.value; publish(content, selectedFont); } }); H.getEl('erase-edit').on('click', function(e) { e.preventDefault(); H.remove(draftDoc); H.remove(draftDoc+'-published'); justPublished = true; // Block auto-save location.reload(); }); WebFontConfig = { custom: { families: [ 'Lora:400,700:latin' ], urls: [ '/css/fonts.css' ] } }; var selectedFont = H.get('{{if .Editing}}draft{{.Post.Id}}font{{else}}padFont{{end}}', '{{.Post.Font}}'); var doneTyping = function() { if (draftDoc == 'lastDoc' || $writer.el.value != origDoc) { H.save($writer, draftDoc); if (!defaultTimeSet) { var lastLocalPublishStr = localStorage.getItem(draftDoc+'-published'); if (lastLocalPublishStr == null || lastLocalPublishStr == '') { localStorage.setItem(draftDoc+'-published', updatedStr); } defaultTimeSet = true; } updateWordCount(); } }; window.addEventListener('beforeunload', function(e) { if (draftDoc != 'lastDoc' && $writer.el.value == origDoc) { H.remove(draftDoc); H.remove(draftDoc+'-published'); } else if (!justPublished) { doneTyping(); } }); try { (function() { var wf=document.createElement('script'); wf.src = '/js/webfont.js'; wf.type='text/javascript'; wf.async='true'; var s=document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(wf, s); })(); } catch (e) { // whatevs } </script> </body> </html>{{end}} diff --git a/templates/pad.tmpl b/templates/pad.tmpl index eb1afba..2580dce 100644 --- a/templates/pad.tmpl +++ b/templates/pad.tmpl @@ -1,421 +1,421 @@ {{define "pad"}}<!DOCTYPE HTML> <html> <head> <title>{{if .Editing}}Editing {{if .Post.Title}}{{.Post.Title}}{{else}}{{.Post.Id}}{{end}}{{else}}New Post{{end}} — {{.SiteName}}</title> <link rel="stylesheet" type="text/css" href="/css/write.css" /> {{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}} <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="google" value="notranslate"> </head> <body id="pad" class="light"> <div id="overlay"></div> - <textarea id="writer" placeholder="Write..." class="{{.Post.Font}}" autofocus>{{if .Post.Title}}# {{.Post.Title}} + <textarea dir="auto" id="writer" placeholder="Write..." class="{{.Post.Font}}" autofocus>{{if .Post.Title}}# {{.Post.Title}} {{end}}{{.Post.Content}}</textarea> <div class="alert success hidden" id="edited-elsewhere">This post has been updated elsewhere since you last published! <a href="#" id="erase-edit">Delete draft and reload</a>.</div> <header id="tools"> <div id="clip"> {{if not .SingleUser}}<h1><a href="/me/c/" title="View blogs"><img class="ic-24dp" src="/img/ic_blogs_dark@2x.png" /></a></h1>{{end}} <nav id="target" {{if .SingleUser}}style="margin-left:0"{{end}}><ul> {{if .Editing}}<li>{{if .EditCollection}}<a href="{{.EditCollection.CanonicalURL}}">{{.EditCollection.Title}}</a>{{else}}<a>Draft</a>{{end}}</li> {{else}}<li class="has-submenu"><a href="#" id="publish-to" onclick="return false"><span id="target-name">Draft</span> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /></a> <ul> <li class="menu-heading">Publish to...</li> {{if .Blogs}}{{range $idx, $el := .Blogs}} <li class="target{{if eq $idx 0}} selected{{end}}" id="blog-{{$el.Alias}}"><a href="#{{$el.Alias}}"><i class="material-icons md-18">public</i> {{if $el.Title}}{{$el.Title}}{{else}}{{$el.Alias}}{{end}}</a></li> {{end}}{{end}} <li class="target" id="blog-anonymous"><a href="#anonymous"><i class="material-icons md-18">description</i> <em>Draft</em></a></li> <li id="user-separator" class="separator"><hr /></li> {{ if .SingleUser }} <li><a href="/"><i class="material-icons md-18">launch</i> View Blog</a></li> <li><a href="/me/c/{{.Username}}"><i class="material-icons md-18">palette</i> Customize</a></li> <li><a href="/me/c/{{.Username}}/stats"><i class="material-icons md-18">trending_up</i> Stats</a></li> {{ else }} <li><a href="/me/c/"><i class="material-icons md-18">library_books</i> View Blogs</a></li> {{ end }} <li><a href="/me/posts/"><i class="material-icons md-18">view_list</i> View Drafts</a></li> <li><a href="/me/logout"><i class="material-icons md-18">power_settings_new</i> Log out</a></li> </ul> </li>{{end}} </ul></nav> <nav id="font-picker" class="if-room room-3 hidden" style="margin-left:-1em"><ul> <li class="has-submenu"><a href="#" id="" onclick="return false"><img class="ic-24dp" src="/img/ic_font_dark@2x.png" /> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /></a> <ul style="text-align: center"> <li class="menu-heading">Font</li> <li class="selected"><a class="font norm" href="#norm">Serif</a></li> <li><a class="font sans" href="#sans">Sans-serif</a></li> <li><a class="font wrap" href="#wrap">Monospace</a></li> </ul> </li> </ul></nav> <span id="wc" class="hidden if-room room-4">0 words</span> </div> <noscript style="margin-left: 2em;"><strong>NOTE</strong>: for now, you'll need JavaScript enabled to post.</noscript> <div id="belt"> {{if .Editing}}<div class="tool hidden if-room"><a href="{{if .EditCollection}}{{.EditCollection.CanonicalURL}}{{.Post.Slug}}/edit/meta{{else}}/{{if .SingleUser}}d/{{end}}{{.Post.Id}}/meta{{end}}" title="Edit post metadata" id="edit-meta"><img class="ic-24dp" src="/img/ic_info_dark@2x.png" /></a></div>{{end}} <div class="tool hidden if-room room-2"><a href="#theme" title="Toggle theme" id="toggle-theme"><img class="ic-24dp" src="/img/ic_brightness_dark@2x.png" /></a></div> <div class="tool if-room room-1"><a href="{{if not .User}}/pad/posts{{else}}/me/posts/{{end}}" title="View posts" id="view-posts"><img class="ic-24dp" src="/img/ic_list_dark@2x.png" /></a></div> <div class="tool"><a href="#publish" title="Publish" id="publish"><img class="ic-24dp" src="/img/ic_send_dark@2x.png" /></a></div> </div> </header> <script src="/js/h.js"></script> <script type="text/javascript" src="/js/menu.js"></script> <script> function toggleTheme() { if (document.body.classList.contains('light')) { setTheme('dark'); } else { setTheme('light'); } H.set('padTheme', newTheme); } function setTheme(newTheme) { document.body.classList.remove('light'); document.body.classList.remove('dark'); document.body.classList.add(newTheme); var btns = Array.prototype.slice.call(document.getElementById('tools').querySelectorAll('a img')); if (newTheme == 'light') { // check if current theme is dark otherwise we'll get `_dark_dark@2x.png` if (H.get('padTheme', 'auto') == 'dark'){ for (var i=0; i<btns.length; i++) { btns[i].src = btns[i].src.replace('@2x.png', '_dark@2x.png'); } } } else { for (var i=0; i<btns.length; i++) { btns[i].src = btns[i].src.replace('_dark@2x.png', '@2x.png'); } } H.set('padTheme', newTheme); } if (H.get('padTheme', 'auto') == 'light') { setTheme('light'); } else if (H.get('padTheme', 'auto') == 'dark') { setTheme('dark'); } else { const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches if (isDarkMode) { setTheme('dark'); } else { setTheme('light'); } } var $writer = H.getEl('writer'); var $btnPublish = H.getEl('publish'); var $btnEraseEdit = H.getEl('edited-elsewhere'); var $wc = H.getEl("wc"); var updateWordCount = function() { var words = 0; var val = $writer.el.value.trim(); if (val != '') { words = $writer.el.value.trim().replace(/\s+/gi, ' ').split(' ').length; } $wc.el.innerText = words + " word" + (words != 1 ? "s" : ""); }; var setButtonStates = function() { if (!canPublish) { $btnPublish.el.className = 'disabled'; return; } if ($writer.el.value.length === 0 || (draftDoc != 'lastDoc' && $writer.el.value == origDoc)) { $btnPublish.el.className = 'disabled'; } else { $btnPublish.el.className = ''; } }; {{if .Post.Id}}var draftDoc = 'draft{{.Post.Id}}'; var origDoc = '{{.Post.Content}}';{{else}}var draftDoc = 'lastDoc';{{end}} var updatedStr = '{{.Post.Updated8601}}'; var updated = null; if (updatedStr != '') { updated = new Date(updatedStr); } var ok = H.load($writer, draftDoc, true, updated); if (!ok) { // Show "edited elsewhere" warning $btnEraseEdit.el.classList.remove('hidden'); } var defaultTimeSet = false; updateWordCount(); var typingTimer; var doneTypingInterval = 200; var posts; {{if and .Post.Id (not .Post.Slug)}} var token = null; var curPostIdx; posts = JSON.parse(H.get('posts', '[]')); for (var i=0; i<posts.length; i++) { if (posts[i].id == "{{.Post.Id}}") { token = posts[i].token; break; } } var canPublish = token != null; {{else}}var canPublish = true;{{end}} var publishing = false; var justPublished = false; var silenced = {{.Silenced}}; var publish = function(content, font) { if (silenced === true) { alert("Your account is silenced, so you can't publish or update posts."); return; } {{if and (and .Post.Id (not .Post.Slug)) (not .User)}} if (!token) { alert("You don't have permission to update this post."); return; } if ($btnPublish.el.className == 'disabled') { return; } {{end}} $btnPublish.el.children[0].textContent = 'more_horiz'; publishing = true; var xpostTarg = H.get('crosspostTarget', '[]'); var http = new XMLHttpRequest(); var post = H.getTitleStrict(content); var params = { body: post.content, title: post.title, font: font }; {{ if .Post.Slug }} var url = "/api/collections/{{.EditCollection.Alias}}/posts/{{.Post.Id}}"; {{ else if .Post.Id }} var url = "/api/posts/{{.Post.Id}}"; if (typeof token === 'undefined' || !token) { token = ""; } params.token = token; {{ else }} var lang = navigator.languages ? navigator.languages[0] : (navigator.language || navigator.userLanguage); lang = lang.substring(0, 2); params.lang = lang; var url = "/api/posts"; var postTarget = H.get('postTarget', 'anonymous'); if (postTarget != 'anonymous') { url = "/api/collections/" + postTarget + "/posts"; } params.crosspost = JSON.parse(xpostTarg); {{ end }} http.open("POST", url, true); // Send the proper header information along with the request http.setRequestHeader("Content-type", "application/json"); http.onreadystatechange = function() { if (http.readyState == 4) { publishing = false; if (http.status == 200 || http.status == 201) { data = JSON.parse(http.responseText); id = data.data.id; nextURL = '{{if .SingleUser}}/d{{end}}/'+id; localStorage.setItem('draft'+id+'-published', new Date().toISOString()); {{ if not .Post.Id }} // Post created if (postTarget != 'anonymous') { nextURL = {{if not .SingleUser}}'/'+postTarget+{{end}}'/'+data.data.slug; } editToken = data.data.token; {{ if not .User }}if (postTarget == 'anonymous') { // Save the data var posts = JSON.parse(H.get('posts', '[]')); {{if .Post.Id}}var newPost = H.createPost("{{.Post.Id}}", token, content); for (var i=0; i<posts.length; i++) { if (posts[i].id == "{{.Post.Id}}") { posts[i].title = newPost.title; posts[i].summary = newPost.summary; break; } } nextURL = "/pad/posts";{{else}}posts.push(H.createPost(id, editToken, content));{{end}} H.set('posts', JSON.stringify(posts)); } {{ end }} {{ end }} justPublished = true; if (draftDoc != 'lastDoc') { H.remove(draftDoc); {{if .Editing}}H.remove('draft{{.Post.Id}}font');{{end}} } else { H.set(draftDoc, ''); } {{if .EditCollection}} window.location = '{{.EditCollection.CanonicalURL}}{{.Post.Slug}}'; {{else}} window.location = nextURL; {{end}} } else { $btnPublish.el.children[0].textContent = 'send'; alert("Failed to post. Please try again."); } } } http.send(JSON.stringify(params)); }; setButtonStates(); $writer.on('keyup input', function() { setButtonStates(); clearTimeout(typingTimer); typingTimer = setTimeout(doneTyping, doneTypingInterval); }, false); $writer.on('keydown', function(e) { clearTimeout(typingTimer); if (e.keyCode == 13 && (e.metaKey || e.ctrlKey)) { $btnPublish.el.click(); } }); $btnPublish.on('click', function(e) { e.preventDefault(); if (!publishing && $writer.el.value) { var content = $writer.el.value; publish(content, selectedFont); } }); H.getEl('erase-edit').on('click', function(e) { e.preventDefault(); H.remove(draftDoc); H.remove(draftDoc+'-published'); justPublished = true; // Block auto-save location.reload(); }); H.getEl('toggle-theme').on('click', function(e) { e.preventDefault(); var newTheme = 'light'; if (document.body.className == 'light') { newTheme = 'dark'; } toggleTheme(); }); var targets = document.querySelectorAll('#target li.target a'); for (var i=0; i<targets.length; i++) { targets[i].addEventListener('click', function(e) { e.preventDefault(); var targetName = this.href.substring(this.href.indexOf('#')+1); H.set('postTarget', targetName); document.querySelector('#target li.target.selected').classList.remove('selected'); this.parentElement.classList.add('selected'); var newText = this.innerText.split(' '); newText.shift(); document.getElementById('target-name').innerText = newText.join(' '); }); } var postTarget = H.get('postTarget', '{{if .Blogs}}{{$blog := index .Blogs 0}}{{$blog.Alias}}{{else}}anonymous{{end}}'); if (location.hash != '') { postTarget = location.hash.substring(1); // TODO: pushState to /pad (or whatever the URL is) so we live on a clean URL location.hash = ''; } var pte = document.querySelector('#target li.target#blog-'+postTarget+' a'); if (pte != null) { pte.click(); } else { postTarget = 'anonymous'; H.set('postTarget', postTarget); } var sansLoaded = false; WebFontConfig = { custom: { families: [ 'Lora:400,700:latin' ], urls: [ '/css/fonts.css' ] } }; var loadSans = function() { if (sansLoaded) return; sansLoaded = true; WebFontConfig.custom.families.push('Open+Sans:400,700:latin'); try { (function() { var wf=document.createElement('script'); wf.src = '/js/webfont.js'; wf.type='text/javascript'; wf.async='true'; var s=document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(wf, s); })(); } catch (e) {} }; var fonts = document.querySelectorAll('nav#font-picker a.font'); for (var i=0; i<fonts.length; i++) { fonts[i].addEventListener('click', function(e) { e.preventDefault(); selectedFont = this.href.substring(this.href.indexOf('#')+1); $writer.el.className = selectedFont; document.querySelector('nav#font-picker li.selected').classList.remove('selected'); this.parentElement.classList.add('selected'); H.set('{{if .Editing}}draft{{.Post.Id}}font{{else}}padFont{{end}}', selectedFont); if (selectedFont == 'sans') { loadSans(); } }); } var selectedFont = H.get('{{if .Editing}}draft{{.Post.Id}}font{{else}}padFont{{end}}', '{{.Post.Font}}'); var sfe = document.querySelector('nav#font-picker a.font.'+selectedFont); if (sfe != null) { sfe.click(); } var doneTyping = function() { if (draftDoc == 'lastDoc' || $writer.el.value != origDoc) { H.save($writer, draftDoc); if (!defaultTimeSet) { var lastLocalPublishStr = localStorage.getItem(draftDoc+'-published'); if (lastLocalPublishStr == null || lastLocalPublishStr == '') { localStorage.setItem(draftDoc+'-published', updatedStr); } defaultTimeSet = true; } updateWordCount(); } }; window.addEventListener('beforeunload', function(e) { if (draftDoc != 'lastDoc' && $writer.el.value == origDoc) { H.remove(draftDoc); H.remove(draftDoc+'-published'); } else if (!justPublished) { doneTyping(); } }); try { (function() { var wf=document.createElement('script'); wf.src = '/js/webfont.js'; wf.type='text/javascript'; wf.async='true'; var s=document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(wf, s); })(); } catch (e) { // whatevs } </script> <link href="/css/icons.css" rel="stylesheet"> </body> </html>{{end}}