Bläddra i källkod

WIP: implement WYSIWYG editor w/ prosemirror

pull/383/head
Rob Loranger 4 år sedan
förälder
incheckning
ee712bbfaa
Ingen känd nyckel hittad för denna signaturen i databasen GPG-nyckel ID: D6F1633A4F0903B8
6 ändrade filer med 760 tillägg och 1297 borttagningar
  1. +2
    -1147
      prose/dist/prose.bundle.js
  2. +1
    -150
      prose/prose.js
  3. +365
    -0
      static/css/prose.css
  4. +14
    -0
      static/js/h.js
  5. +2
    -0
      static/js/prose.bundle.js
  6. +376
    -0
      templates/wysiwyg.tmpl

+ 2
- 1147
prose/dist/prose.bundle.js
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


+ 1
- 150
prose/prose.js Visa fil

@@ -9,161 +9,12 @@
// destroy() { this.textarea.remove() }
// }

import {Schema} from "prosemirror-model"
import {EditorView} from "prosemirror-view"
import {EditorState, Plugin} from "prosemirror-state"
import {defaultMarkdownParser,
import {schema, defaultMarkdownParser,
defaultMarkdownSerializer} from "prosemirror-markdown"
import {exampleSetup} from "prosemirror-example-setup"

// TODO: maybe don't need to use our own schema but waiting to figure out
// line break issues
const schema = new Schema({
nodes: {
doc: {
content: "block+"
},

paragraph: {
content: "inline*",
group: "block",
parseDOM: [{tag: "p"}],
toDOM() { return ["p", 0] }
},

blockquote: {
content: "block+",
group: "block",
parseDOM: [{tag: "blockquote"}],
toDOM() { return ["blockquote", 0] }
},

horizontal_rule: {
group: "block",
parseDOM: [{tag: "hr"}],
toDOM() { return ["div", ["hr"]] }
},

heading: {
attrs: {level: {default: 1}},
content: "inline*",
group: "block",
defining: true,
parseDOM: [{tag: "h1", attrs: {level: 1}},
{tag: "h2", attrs: {level: 2}},
{tag: "h3", attrs: {level: 3}},
{tag: "h4", attrs: {level: 4}},
{tag: "h5", attrs: {level: 5}},
{tag: "h6", attrs: {level: 6}}],
toDOM(node) { return ["h" + node.attrs.level, 0] }
},

code_block: {
content: "text*",
group: "block",
code: true,
defining: true,
marks: "",
attrs: {params: {default: ""}},
parseDOM: [{tag: "pre", preserveWhitespace: "full", getAttrs: node => (
{params: node.getAttribute("data-params") || ""}
)}],
toDOM(node) { return ["pre", node.attrs.params ? {"data-params": node.attrs.params} : {}, ["code", 0]] }
},

ordered_list: {
content: "list_item+",
group: "block",
attrs: {order: {default: 1}, tight: {default: false}},
parseDOM: [{tag: "ol", getAttrs(dom) {
return {order: dom.hasAttribute("start") ? +dom.getAttribute("start") : 1,
tight: dom.hasAttribute("data-tight")}
}}],
toDOM(node) {
return ["ol", {start: node.attrs.order == 1 ? null : node.attrs.order,
"data-tight": node.attrs.tight ? "true" : null}, 0]
}
},

bullet_list: {
content: "list_item+",
group: "block",
attrs: {tight: {default: false}},
parseDOM: [{tag: "ul", getAttrs: dom => ({tight: dom.hasAttribute("data-tight")})}],
toDOM(node) { return ["ul", {"data-tight": node.attrs.tight ? "true" : null}, 0] }
},

list_item: {
content: "paragraph block*",
defining: true,
parseDOM: [{tag: "li"}],
toDOM() { return ["li", 0] }
},

text: {
group: "inline"
},

image: {
inline: true,
attrs: {
src: {},
alt: {default: null},
title: {default: null}
},
group: "inline",
draggable: true,
parseDOM: [{tag: "img[src]", getAttrs(dom) {
return {
src: dom.getAttribute("src"),
title: dom.getAttribute("title"),
alt: dom.getAttribute("alt")
}
}}],
toDOM(node) { return ["img", node.attrs] }
},

hard_break: {
inline: true,
group: "inline",
selectable: false,
parseDOM: [{tag: "br"}],
toDOM() { return ["br"] }
}
},

marks: {
em: {
parseDOM: [{tag: "i"}, {tag: "em"},
{style: "font-style", getAttrs: value => value == "italic" && null}],
toDOM() { return ["em"] }
},

strong: {
parseDOM: [{tag: "b"}, {tag: "strong"},
{style: "font-weight", getAttrs: value => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null}],
toDOM() { return ["strong"] }
},

link: {
attrs: {
href: {},
title: {default: null}
},
inclusive: false,
parseDOM: [{tag: "a[href]", getAttrs(dom) {
return {href: dom.getAttribute("href"), title: dom.getAttribute("title")}
}}],
toDOM(node) { return ["a", node.attrs] }
},

code: {
parseDOM: [{tag: "code"}],
toDOM() { return ["code"] }
}
}
})

class ProseMirrorView {
constructor(target, content) {
this.view = new EditorView(target, {


+ 365
- 0
static/css/prose.css Visa fil

@@ -0,0 +1,365 @@
.ProseMirror {
position: relative;
}

.ProseMirror {
word-wrap: break-word;
white-space: pre-wrap;
-webkit-font-variant-ligatures: none;
font-variant-ligatures: none;
}

.ProseMirror pre {
white-space: pre-wrap;
}

.ProseMirror li {
position: relative;
}

.ProseMirror-hideselection *::selection { background: transparent; }
.ProseMirror-hideselection *::-moz-selection { background: transparent; }
.ProseMirror-hideselection { caret-color: transparent; }

.ProseMirror-selectednode {
outline: 2px solid #8cf;
}

/* Make sure li selections wrap around markers */

li.ProseMirror-selectednode {
outline: none;
}

li.ProseMirror-selectednode:after {
content: "";
position: absolute;
left: -32px;
right: -2px; top: -2px; bottom: -2px;
border: 2px solid #8cf;
pointer-events: none;
}
.ProseMirror-textblock-dropdown {
min-width: 3em;
}

.ProseMirror-menu {
margin: 0 -4px;
line-height: 1;
}

.ProseMirror-tooltip .ProseMirror-menu {
width: -webkit-fit-content;
width: fit-content;
white-space: pre;
}

.ProseMirror-menuitem {
margin-right: 3px;
display: inline-block;
}

.ProseMirror-menuseparator {
border-right: 1px solid #ddd;
margin-right: 3px;
}

.ProseMirror-menu-dropdown, .ProseMirror-menu-dropdown-menu {
font-size: 90%;
white-space: nowrap;
}

.ProseMirror-menu-dropdown {
vertical-align: 1px;
cursor: pointer;
position: relative;
padding-right: 15px;
}

.ProseMirror-menu-dropdown-wrap {
padding: 1px 0 1px 4px;
display: inline-block;
position: relative;
}

.ProseMirror-menu-dropdown:after {
content: "";
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid currentColor;
opacity: .6;
position: absolute;
right: 4px;
top: calc(50% - 2px);
}

.ProseMirror-menu-dropdown-menu, .ProseMirror-menu-submenu {
position: absolute;
background: white;
color: #666;
border: 1px solid #aaa;
padding: 2px;
}

.ProseMirror-menu-dropdown-menu {
z-index: 15;
min-width: 6em;
}

.ProseMirror-menu-dropdown-item {
cursor: pointer;
padding: 2px 8px 2px 4px;
}

.ProseMirror-menu-dropdown-item:hover {
background: #f2f2f2;
}

.ProseMirror-menu-submenu-wrap {
position: relative;
margin-right: -4px;
}

.ProseMirror-menu-submenu-label:after {
content: "";
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
border-left: 4px solid currentColor;
opacity: .6;
position: absolute;
right: 4px;
top: calc(50% - 4px);
}

.ProseMirror-menu-submenu {
display: none;
min-width: 4em;
left: 100%;
top: -3px;
}

.ProseMirror-menu-active {
background: #eee;
border-radius: 4px;
}

.ProseMirror-menu-active {
background: #eee;
border-radius: 4px;
}

.ProseMirror-menu-disabled {
opacity: .3;
}

.ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu, .ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu {
display: block;
}

.ProseMirror-menubar {
border-top-left-radius: inherit;
border-top-right-radius: inherit;
position: relative;
min-height: 1em;
color: #666;
padding: 1px 6px;
top: 0; left: 0; right: 0;
border-bottom: 1px solid silver;
background: white;
z-index: 10;
-moz-box-sizing: border-box;
box-sizing: border-box;
overflow: visible;
}

.ProseMirror-icon {
display: inline-block;
line-height: .8;
vertical-align: -2px; /* Compensate for padding */
padding: 2px 8px;
cursor: pointer;
}

.ProseMirror-menu-disabled.ProseMirror-icon {
cursor: default;
}

.ProseMirror-icon svg {
fill: currentColor;
height: 1em;
}

.ProseMirror-icon span {
vertical-align: text-top;
}
.ProseMirror-gapcursor {
display: none;
pointer-events: none;
position: absolute;
}

.ProseMirror-gapcursor:after {
content: "";
display: block;
position: absolute;
top: -2px;
width: 20px;
border-top: 1px solid black;
animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;
}

@keyframes ProseMirror-cursor-blink {
to {
visibility: hidden;
}
}

.ProseMirror-focused .ProseMirror-gapcursor {
display: block;
}
/* Add space around the hr to make clicking it easier */

.ProseMirror-example-setup-style hr {
padding: 2px 10px;
border: none;
margin: 1em 0;
}

.ProseMirror-example-setup-style hr:after {
content: "";
display: block;
height: 1px;
background-color: silver;
line-height: 2px;
}

.ProseMirror ul, .ProseMirror ol {
padding-left: 30px;
}

.ProseMirror blockquote {
padding-left: 1em;
border-left: 3px solid #eee;
margin-left: 0; margin-right: 0;
}

.ProseMirror-example-setup-style img {
cursor: default;
}

.ProseMirror-prompt {
background: white;
padding: 5px 10px 5px 15px;
border: 1px solid silver;
position: fixed;
border-radius: 3px;
z-index: 11;
box-shadow: -.5px 2px 5px rgba(0, 0, 0, .2);
}

.ProseMirror-prompt h5 {
margin: 0;
font-weight: normal;
font-size: 100%;
color: #444;
}

.ProseMirror-prompt input[type="text"],
.ProseMirror-prompt textarea {
background: #eee;
border: none;
outline: none;
}

.ProseMirror-prompt input[type="text"] {
padding: 0 4px;
}

.ProseMirror-prompt-close {
position: absolute;
left: 2px; top: 1px;
color: #666;
border: none; background: transparent; padding: 0;
}

.ProseMirror-prompt-close:after {
content: "✕";
font-size: 12px;
}

.ProseMirror-invalid {
background: #ffc;
border: 1px solid #cc7;
border-radius: 4px;
padding: 5px 10px;
position: absolute;
min-width: 10em;
}

.ProseMirror-prompt-buttons {
margin-top: 5px;
display: none;
}
#editor, .editor {
background: white;
color: black;
background-clip: padding-box;
border-radius: 4px;
border: 2px solid rgba(0, 0, 0, 0.2);
padding: 5px 0;
margin: 4em auto 23px auto;
}

.ProseMirror p:first-child,
.ProseMirror h1:first-child,
.ProseMirror h2:first-child,
.ProseMirror h3:first-child,
.ProseMirror h4:first-child,
.ProseMirror h5:first-child,
.ProseMirror h6:first-child {
margin-top: 10px;
}

.ProseMirror {
padding: 4px 8px 4px 14px;
line-height: 1.2;
outline: none;
}

.ProseMirror p { margin-bottom: 1em }

.ProseMirror { height: 120px; overflow-y: auto; box-sizing: border-box; -moz-box-sizing: border-box }
textarea { width: 100%; height: 123px; border: 1px solid silver; box-sizing: border-box; -moz-box-sizing: border-box; padding: 3px 10px;
border: none; outline: none; font-family: inherit; font-size: inherit }
.ProseMirror-menubar-wrapper, #markdown textarea { display: block; margin-bottom: 4px }

@media all and (min-width: 50em) {
#editor {
margin-left: 10%;
margin-right: 10%;
}
}
@media all and (min-width: 60em) {
#editor {
margin-left: 15%;
margin-right: 15%;
}
}
@media all and (min-width: 70em) {
#editor {
margin-left: 20%;
margin-right: 20%;
}
}
@media all and (min-width: 85em) {
#editor {
margin-left: 25%;
margin-right: 25%;
}
}
@media all and (min-width: 105em) {
#editor {
margin-left: 30%;
margin-right: 30%;
}
}

+ 14
- 0
static/js/h.js Visa fil

@@ -110,6 +110,9 @@ Element.prototype.show = function() {


var H = {
getQEl: function(elementQuery) {
return new Element(document.querySelector(elementQuery));
},
getEl: function(elementId) {
return new Element(document.getElementById(elementId));
},
@@ -124,6 +127,17 @@ var H = {
}
$el.el.value = val;
},
saveText: function($el, key) {
localStorage.setItem(key, $el.el.innerText);
},
loadText: function($el, key, onlyLoadPopulated) {
var val = localStorage.getItem(key);
if (onlyLoadPopulated && val == null) {
// Do nothing
return;
}
$el.el.innerText = val;
},
set: function(key, value) {
localStorage.setItem(key, value);
},


+ 2
- 0
static/js/prose.bundle.js
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


+ 376
- 0
templates/wysiwyg.tmpl Visa fil

@@ -0,0 +1,376 @@
{{define "pad"}}<!DOCTYPE HTML>
<html>
<head>

<title>{{if .Editing}}Editing {{if .Post.Title}}{{.Post.Title}}{{else}}{{.Post.Id}}{{end}}{{else}}New Post{{end}} &mdash; {{.SiteName}}</title>
<link rel="stylesheet" type="text/css" href="/css/write.css" />
<link rel="stylesheet" type="text/css" href="/css/prose.css" />
<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>
<!-- <div style="text-align: center"> -->
<!-- <label style="border-right: 1px solid silver"> -->
<!-- Markdown <input type=radio name=inputformat value=markdown checked>&nbsp;</label> -->
<!-- <label>&nbsp;<input type=radio name=inputformat value=prosemirror> WYSIWYM</label> -->
<!-- </div> -->
<div id="editor" style="margin-bottom: 0"></div>

<div style="display: none"><textarea id="content">{{if .Post.Content }}{{.Post.Content}}{{end}}</textarea></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><a id="publish-to"><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><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/prose.bundle.js"></script>
<script src="/js/h.js"></script>
<script>
function toggleTheme() {
var btns = Array.prototype.slice.call(document.getElementById('tools').querySelectorAll('a img'));
var newTheme = '';
if (document.body.classList.contains('light')) {
newTheme = 'dark';
document.body.className = document.body.className.replace(/(?:^|\s)light(?!\S)/g, newTheme);
for (var i=0; i<btns.length; i++) {
btns[i].src = btns[i].src.replace('_dark@2x.png', '@2x.png');
}
} else {
TextnewTheme = 'light';
document.body.className = document.body.className.replace(/(?:^|\s)dark(?!\S)/g, newTheme);
for (var i=0; i<btns.length; i++) {
btns[i].src = btns[i].src.replace('@2x.png', '_dark@2x.png');
}
}
H.set('padTheme', newTheme);
}
if (H.get('padTheme', 'light') != 'light') {
toggleTheme();
}
var $writer = H.getQEl('div.ProseMirror');
var $content = H.getEl('content');
var $btnPublish = H.getEl('publish');
var $wc = H.getEl("wc");
var updateWordCount = function() {
var words = 0;
var val = $content.el.innerText.trim();
if (val != '') {
words = $content.el.innerText.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 ($content.el.innerText.length === 0 || (draftDoc != 'lastDoc' && $content.el.innerText == 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}}
H.loadText($content, draftDoc, true);
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 lang = navigator.languages ? navigator.languages[0] : (navigator.language || navigator.userLanguage);
lang = lang.substring(0, 2);
var post = H.getTitleStrict(content);

var params = {
body: post.content,
title: post.title,
font: font,
lang: lang
};
{{ 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 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;

{{ 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 && $content.el.innerText) {
var content = $content.el.innerText;
publish(content, selectedFont);
}
});

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);
// TODO: don't change classes on the editor window
//$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' || $content.el.innerText != origDoc) {
H.saveText($content, draftDoc);
updateWordCount();
}
};
window.addEventListener('beforeunload', function(e) {
if (draftDoc != 'lastDoc' && $content.el.innerText == origDoc) {
H.remove(draftDoc);
} 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}}

Laddar…
Avbryt
Spara