300 lines
10 KiB
Text
300 lines
10 KiB
Text
<% #listItem %>
|
||
<tr>
|
||
<td class='uk-text-right'><% story.comments count %></td>
|
||
<td class='uk-width-1-2 av-overflow'>
|
||
<a href='<% story.href %>' title='<% ngettext '{0} request' '{0} requests' <% story.requests %> %>' data-uk-tooltip="{pos: 'top-left'}">
|
||
<% story.abstract %>
|
||
</a>
|
||
</td>
|
||
<td class='uk-text-truncate'><% story.creator %></td>
|
||
<td class='uk-text-truncate' title='<% story.modified short %>' data-uk-tooltip="{pos: 'top-left'}">
|
||
<% story.modified text %>
|
||
</td>
|
||
<td>
|
||
<% //if <% story.status %> is closed then "<i class='uk-text-muted uk-icon-lock'></i>" %>
|
||
<% //if <% story.status %> is public then "<i class='uk-text-muted uk-icon-unlock'></i>" %>
|
||
<% if <% story.status %> is shared then "<i class='uk-text-muted uk-icon-group'></i>" %>
|
||
<% if <% story.status %> is open then "<i class='uk-text-muted uk-icon-globe'></i>" %>
|
||
<% if <% story.tags count %> is 0 then '' else <% story.tags prefix="<i class='uk-text-muted uk-icon-tags' data-uk-tooltip title='" suffix="'></i>" %> %>
|
||
</td>
|
||
<td class='uk-text-nowrap'>
|
||
<% story.link status <% if <% story.status %> is closed then lock else unlock prefix="<i class='uk-icon-" suffix="'></i>" %> %>
|
||
<% story.link mode <% if <% story.mode %> is hidden then eye-slash else eye prefix="<i class='uk-icon-" suffix="'></i>" %> %>
|
||
</td>
|
||
<td class='uk-text-nowrap uk-text-right'>
|
||
<a href='javascript:' data-value="<% story.macro %>" data-text='<% gettext "Press CTRL & C to copy to clipboard." %>' class='av-clipboard-copy'><i class='uk-icon-clipboard'></i></a>
|
||
<% story.link delete "<i class='uk-icon-trash-o'></i>" %>
|
||
<% story.link edit "<i class='uk-icon-pencil'></i>" %>
|
||
</td>
|
||
</tr>
|
||
|
||
<% #links %>
|
||
<% if <% story.commentMode %> is open then
|
||
<% if <% site.commentMode %> is enabled then
|
||
<% story.skin Story#links %>
|
||
else
|
||
<% story.skin Story#permalink %>
|
||
%>
|
||
else
|
||
<% story.skin Story#permalink %>
|
||
%>
|
||
|
||
<% #tagged %>
|
||
<% story.skin Story#preview %>
|
||
|
||
<% #search %>
|
||
<dt><% this.abstract | this.link %></dt>
|
||
<dd>Posted <% this.created text %> by <% this.creator %>.</dd>
|
||
|
||
<% #update %>
|
||
<dt><% gettext '{0} {1} {2} {3} on {4} // e.g. “{3 days ago} {admin} {posted} {a story} on {Antville}”' <% story.site.modified text prefix=<strong> suffix=</strong> %> <% story.modifier %> <% if <% story.created %> is <% story.modified %> then <% gettext 'posted // has posted' %> else <% gettext 'updated // has updated' %> %><% if <% story.type %> is Story then <% gettext 'a story // accusative' %> else <% gettext 'a comment // accusative' %> %> <% story.site.title | story.site.link %> %></dt>
|
||
<dd><% story.abstract | story.link class='av-concealed-link' %></dd>
|
||
|
||
<% #embed %>
|
||
<div class='uk-margin-bottom'>
|
||
<div class='uk-panel uk-panel-box'>
|
||
<% story.title prefix="<h2 class='uk-panel-title'>" suffix=</h2> %>
|
||
<% story.text | story.format %>
|
||
</div>
|
||
<% story.source %>
|
||
</div>
|
||
|
||
<% #editor %>
|
||
<script>
|
||
// Load additonal scripts and styles for UIkit’s HTML editor plugin
|
||
document.addEventListener("alpine:init", () => {
|
||
const _form = document.querySelector('#av-story-form');
|
||
|
||
if (!_form) return;
|
||
|
||
const link = document.createElement('link');
|
||
link.rel = 'stylesheet';
|
||
link.href = '<% root.static ../../styles/editor.min.css %>';
|
||
link.onload = loadScripts;
|
||
document.head.appendChild(link);
|
||
|
||
function loadScripts() {
|
||
const script = document.createElement('script');
|
||
script.src = '<% root.static ../../scripts/editor.min.js %>';
|
||
script.onload = setupEditor;
|
||
document.head.appendChild(script);
|
||
}
|
||
|
||
function setupEditor() {
|
||
UIkit.plugin('htmleditor', 'antville', {
|
||
init: function(editor) {
|
||
const addAction = function(name, replace, mode) {
|
||
editor.off('action.' + name);
|
||
|
||
editor.on('action.' + name, function() {
|
||
editor[mode === 'replaceLine' ? mode : 'replaceSelection'](replace);
|
||
});
|
||
};
|
||
|
||
editor.addButtons({
|
||
file: {
|
||
title: "<% gettext 'File' %>",
|
||
label: '<i class="uk-icon-file-o"></i>'
|
||
},
|
||
|
||
poll: {
|
||
title: "<% gettext 'Poll' %>",
|
||
label: '<i class="uk-icon-bar-chart"></i>'
|
||
}
|
||
});
|
||
|
||
addAction('file', "\<% file '$1' %\>");
|
||
addAction('image', "\<% image '$1' %\>");
|
||
addAction('link', "\<% link 'https://' '$1' %\>");
|
||
addAction('poll', "\<% poll '$1' %\>");
|
||
}
|
||
});
|
||
|
||
const _url = '<% site.href backup.js %>';
|
||
|
||
let _editor;
|
||
let _lastKeyPressed;
|
||
let _submitElement;
|
||
|
||
const enableOnBeforeUnload = function() {
|
||
window.onbeforeunload = function(event) {
|
||
return event.returnValue = "<% gettext 'You are going to discard unsaved content.' %>";
|
||
};
|
||
};
|
||
|
||
const disableOnBeforeUnload = function() {
|
||
window.onbeforeunload = null;
|
||
};
|
||
|
||
const update = function() {
|
||
if (!window.onbeforeunload) enableOnBeforeUnload();
|
||
_lastKeyPressed = new Date();
|
||
};
|
||
|
||
const scheduler = function() {
|
||
const now = new Date();
|
||
|
||
if (_lastKeyPressed && (now - _lastKeyPressed > 1000)) {
|
||
const data = {};
|
||
|
||
_form.querySelectorAll('input, select, textarea').forEach(function(element) {
|
||
if (element.id) data[element.id] = element.value.trim();
|
||
});
|
||
|
||
if (_editor) {
|
||
data.text = _editor.editor.getValue().trim();
|
||
}
|
||
|
||
Antville.http('post', _url, { data: data });
|
||
_lastKeyPressed = null;
|
||
}
|
||
|
||
setTimeout(scheduler, 100);
|
||
};
|
||
|
||
// TODO: This would probably be better done server-side
|
||
const parse = function(data) {
|
||
const html = document.createElement('div');
|
||
const assets = [];
|
||
|
||
html.innerHTML = data;
|
||
|
||
html.querySelectorAll('[src]').forEach(function(element) {
|
||
const url = element.src;
|
||
let re = new RegExp('\.(?:gif|jpg|jpeg|png)$', 'i');
|
||
|
||
if (url.match(re)) assets.push(url);
|
||
|
||
re = new RegExp('^.+://[^/]*(youtube)[^/]+/embed/([^/?]+)', 'i');
|
||
let match = url.match(re);
|
||
|
||
if (match) {
|
||
const input = document.createElement('input');
|
||
|
||
input.hidden = true;
|
||
input.name = 'og:video';
|
||
input.value = 'https://' + match[1] + '.com/v/' + match[2];
|
||
|
||
_form.appendChild(input);
|
||
assets.push('https://img.' + match[1] + '.com/vi/' + match[2] + '/0.jpg');
|
||
}
|
||
});
|
||
|
||
if (!assets.length) resubmit();
|
||
|
||
assets.forEach(function(url, index) {
|
||
const img = new Image;
|
||
|
||
img.src = url;
|
||
|
||
img.onload = function() {
|
||
const input = document.createElement('input');
|
||
|
||
input.hidden = true;
|
||
input.name = 'og:image';
|
||
input.value = url;
|
||
|
||
_form.appendChild(input);
|
||
assets.shift();
|
||
|
||
if (!assets.length) resubmit();
|
||
};
|
||
|
||
img.onerror = function() {
|
||
assets.shift();
|
||
if (!assets.length) resubmit();
|
||
};
|
||
});
|
||
};
|
||
|
||
const resubmit = function() {
|
||
// Turn the clicked (or default) submit button into a hidden value to be sure it is submitted again
|
||
const submit = _submitElement || _form.find(':submit:first');
|
||
const input = document.createElement('input');
|
||
|
||
_submitElement = null;
|
||
input.hidden = true;
|
||
input.name = submit.name;
|
||
input.value = submit.value;
|
||
|
||
_form.appendChild(input);
|
||
_form.submit();
|
||
};
|
||
|
||
// UIkit’s editor requires jQuery
|
||
_editor = UIkit.htmleditor(document.querySelector('#text'), {
|
||
mode: 'tab',
|
||
markdown: true,
|
||
lblPreview: '<% gettext Preview %>',
|
||
lblCodeview: '<% gettext HTML %>',
|
||
lblMarkedview: '<% gettext Markdown %>',
|
||
toolbar: ['bold', 'italic', 'strike', 'link', 'image', 'file', 'poll', 'blockquote', 'listUl', 'listOl']
|
||
});
|
||
|
||
_editor.editor.on('keyup', update);
|
||
|
||
_editor.htmleditor.on('click', '.uk-htmleditor-button-code, .uk-htmleditor-button-preview', function(event) {
|
||
if (event.currentTarget.classList.contains('uk-htmleditor-button-preview')) {
|
||
// TODO: Still a good idea to render the macros via AJAX call?
|
||
const content = _editor.editor.getValue();
|
||
|
||
if (!content) return;
|
||
|
||
Antville.http('post', '<% stories.href render.json %>', {
|
||
data: { content: content },
|
||
callback: function(data, error) {
|
||
if (error) return;
|
||
document.querySelector('.uk-htmleditor-preview div').innerHTML = data;
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
_form.addEventListener('keyup', update);
|
||
|
||
_form.querySelectorAll('.uk-button').forEach(function(element) {
|
||
element.addEventListener('click', disableOnBeforeUnload);
|
||
});
|
||
|
||
_form.addEventListener('submit', function(event) {
|
||
event.preventDefault();
|
||
|
||
const form = event.currentTarget;
|
||
const submit = form.querySelector('[type=submit]');
|
||
|
||
if (form.dataset.isProcessed) return;
|
||
|
||
form.dataset.isProcessed = true;
|
||
_submitElement = submit;
|
||
|
||
const elementList = form.querySelectorAll('button, input, select, textarea');
|
||
|
||
const buffer = Array.prototype.slice.call(elementList).map(function(element) {
|
||
return element.value;
|
||
});
|
||
|
||
const raw = encodeURIComponent(buffer.join(' '));
|
||
|
||
Antville.http('post', '<% stories.href render.json %>', {
|
||
data: { content: buffer.join(' ') },
|
||
callback: parse
|
||
});
|
||
});
|
||
|
||
// Restore backup text if available and if the editor refers to a new story
|
||
if (location.href.match('/create$') && '<% session.backup %>') {
|
||
Antville.http('GET', _url, {
|
||
callback: function(json, error) {
|
||
const data = JSON.parse(json);
|
||
Object.keys(data).forEach(function(key) {
|
||
document.querySelector('#' + key).value = data[key];
|
||
});
|
||
_editor && _editor.editor.setValue(data.text);
|
||
}
|
||
});
|
||
}
|
||
|
||
scheduler();
|
||
}
|
||
});
|
||
</script>
|