mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-23 20:18:00 +01:00
revert split editor styling, update markdown toolbar buttons' behavior
This commit is contained in:
parent
b8aa9b2c9c
commit
f61cc6d0be
7 changed files with 90 additions and 156 deletions
|
@ -226,46 +226,3 @@ a.block__header--single-item, .block__header a {
|
||||||
.block__content--top-border {
|
.block__content--top-border {
|
||||||
border-top: $border;
|
border-top: $border;
|
||||||
}
|
}
|
||||||
|
|
||||||
.block__column__header {
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: $block_header_sub_height;
|
|
||||||
margin-bottom: $header_field_spacing;
|
|
||||||
border-bottom: 1px solid;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: $min_px_width_for_desktop_layout) {
|
|
||||||
.block--split {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row nowrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block__column--full, .block__column--half {
|
|
||||||
flex-grow: 0;
|
|
||||||
flex-shrink: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block__column--full {
|
|
||||||
flex-basis: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block__column--half {
|
|
||||||
flex-basis: 50%;
|
|
||||||
max-width: 50%;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
padding-right: $block-spacing;
|
|
||||||
}
|
|
||||||
&:last-child {
|
|
||||||
padding-left: $block-spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex__grow {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ span.communication__sender__stats,
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.communication-edit__wrap {
|
.communication-edit__tab {
|
||||||
padding-bottom: 12px;
|
padding-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ span.communication__sender__stats,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.hyphenate-breaks {
|
.hyphenate-breaks{
|
||||||
hyphens: auto;
|
hyphens: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* Markdown toolbar
|
* Markdown toolbar
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { $, $$, showEl } from './utils/dom';
|
import { $, $$ } from './utils/dom';
|
||||||
|
|
||||||
const markdownSyntax = {
|
const markdownSyntax = {
|
||||||
bold: {
|
bold: {
|
||||||
|
@ -22,8 +22,15 @@ const markdownSyntax = {
|
||||||
options: { prefix: '||', shortcutKey: 's' }
|
options: { prefix: '||', shortcutKey: 's' }
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
action: wrapSelection,
|
action: wrapSelectionOrLines,
|
||||||
options: { prefix: '`', shortcutKey: 'e' }
|
options: {
|
||||||
|
prefix: '`',
|
||||||
|
suffix: '`',
|
||||||
|
prefixMultiline: '```\n',
|
||||||
|
suffixMultiline: '\n```',
|
||||||
|
singleWrap: true,
|
||||||
|
shortcutKey: 'e'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
strike: {
|
strike: {
|
||||||
action: wrapSelection,
|
action: wrapSelection,
|
||||||
|
@ -62,22 +69,31 @@ function getSelections(textarea, linesOnly = false) {
|
||||||
trailingSpace = '',
|
trailingSpace = '',
|
||||||
caret;
|
caret;
|
||||||
|
|
||||||
if (linesOnly) {
|
const processLinesOnly = linesOnly instanceof RegExp ? linesOnly.test(selection) : linesOnly;
|
||||||
|
if (processLinesOnly) {
|
||||||
|
const explorer = /\n/g;
|
||||||
let startNewlineIndex = 0,
|
let startNewlineIndex = 0,
|
||||||
endNewlineIndex = textarea.value.length;
|
endNewlineIndex = textarea.value.length;
|
||||||
const explorer = /\n/g;
|
|
||||||
while (explorer.exec(textarea.value)) {
|
while (explorer.exec(textarea.value)) {
|
||||||
const { lastIndex } = explorer;
|
const { lastIndex } = explorer;
|
||||||
if (lastIndex < selectionStart) {
|
if (lastIndex <= selectionStart) {
|
||||||
startNewlineIndex = lastIndex + 1;
|
startNewlineIndex = lastIndex;
|
||||||
}
|
}
|
||||||
else if (lastIndex > selectionEnd) {
|
else if (lastIndex > selectionEnd) {
|
||||||
endNewlineIndex = lastIndex;
|
endNewlineIndex = lastIndex - 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectionStart = startNewlineIndex;
|
selectionStart = startNewlineIndex;
|
||||||
|
const startRemovedValue = textarea.value.substring(selectionStart);
|
||||||
|
const startsWithBlankString = startRemovedValue.match(/^[\s\n]+/);
|
||||||
|
if (startsWithBlankString) {
|
||||||
|
// Offset the selection start to the first non-blank line's first non-blank character, since
|
||||||
|
// Some browsers treat selection up to the start of the line as including the end of the
|
||||||
|
// previous line
|
||||||
|
selectionStart += startsWithBlankString[0].length;
|
||||||
|
}
|
||||||
selectionEnd = endNewlineIndex;
|
selectionEnd = endNewlineIndex;
|
||||||
selection = textarea.value.substring(selectionStart, selectionEnd);
|
selection = textarea.value.substring(selectionStart, selectionEnd);
|
||||||
}
|
}
|
||||||
|
@ -98,6 +114,7 @@ function getSelections(textarea, linesOnly = false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
processLinesOnly,
|
||||||
selectedText: selection,
|
selectedText: selection,
|
||||||
beforeSelection: textarea.value.substring(0, selectionStart) + leadingSpace,
|
beforeSelection: textarea.value.substring(0, selectionStart) + leadingSpace,
|
||||||
afterSelection: trailingSpace + textarea.value.substring(selectionEnd)
|
afterSelection: trailingSpace + textarea.value.substring(selectionEnd)
|
||||||
|
@ -105,11 +122,11 @@ function getSelections(textarea, linesOnly = false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function transformSelection(textarea, transformer, eachLine) {
|
function transformSelection(textarea, transformer, eachLine) {
|
||||||
const { selectedText, beforeSelection, afterSelection } = getSelections(textarea, eachLine),
|
const { selectedText, beforeSelection, afterSelection, processLinesOnly } = getSelections(textarea, eachLine),
|
||||||
// For long comments, record scrollbar position to restore it later
|
// For long comments, record scrollbar position to restore it later
|
||||||
{ scrollTop } = textarea;
|
{ scrollTop } = textarea;
|
||||||
|
|
||||||
const { newText, caretOffset } = transformer(selectedText);
|
const { newText, caretOffset } = transformer(selectedText, processLinesOnly);
|
||||||
|
|
||||||
textarea.value = beforeSelection + newText + afterSelection;
|
textarea.value = beforeSelection + newText + afterSelection;
|
||||||
|
|
||||||
|
@ -120,7 +137,8 @@ function transformSelection(textarea, transformer, eachLine) {
|
||||||
textarea.selectionStart = newSelectionStart;
|
textarea.selectionStart = newSelectionStart;
|
||||||
textarea.selectionEnd = newSelectionStart;
|
textarea.selectionEnd = newSelectionStart;
|
||||||
textarea.scrollTop = scrollTop;
|
textarea.scrollTop = scrollTop;
|
||||||
textarea.dispatchEvent(new Event('keydown'));
|
// Needed for automatic textarea resizing
|
||||||
|
textarea.dispatchEvent(new Event('change'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertLink(textarea, options) {
|
function insertLink(textarea, options) {
|
||||||
|
@ -150,28 +168,31 @@ function wrapSelection(textarea, options) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newText = prefix + newText + suffix
|
||||||
|
|
||||||
return {
|
return {
|
||||||
newText: prefix + newText + suffix,
|
newText,
|
||||||
caretOffset: emptyText ? prefix.length : 0
|
caretOffset: emptyText ? prefix.length : newText.length
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrapLines(textarea, options) {
|
function wrapLines(textarea, options, eachLine = true) {
|
||||||
transformSelection(textarea, (selectedText) => {
|
transformSelection(textarea, (selectedText, processLinesOnly) => {
|
||||||
const { text = selectedText, prefix = '', suffix = '' } = options,
|
const { text = selectedText, singleWrap = false } = options,
|
||||||
|
prefix = (processLinesOnly && options.prefixMultiline) || options.prefix || '',
|
||||||
|
suffix = (processLinesOnly && options.suffixMultiline) || options.suffix || '',
|
||||||
emptyText = text === '';
|
emptyText = text === '';
|
||||||
let newText = prefix;
|
let newText = singleWrap
|
||||||
|
? prefix + text.trim() + suffix
|
||||||
|
: text.split(/\n/g).map(line => prefix + line.trim() + suffix).join('\n');
|
||||||
|
|
||||||
if (!emptyText) {
|
return { newText, caretOffset: emptyText ? prefix.length : newText.length };
|
||||||
newText = text.split(/\n/g).map(line => prefix + line.trim() + suffix).join('\n');
|
}, eachLine);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
newText += suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { newText, caretOffset: newText.length };
|
function wrapSelectionOrLines(textarea, options) {
|
||||||
});
|
wrapLines(textarea, options, /\n/);
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeSelection(textarea, options) {
|
function escapeSelection(textarea, options) {
|
||||||
|
@ -224,15 +245,6 @@ function setupToolbar() {
|
||||||
$$('.js-toolbar-input').forEach(textarea => {
|
$$('.js-toolbar-input').forEach(textarea => {
|
||||||
textarea.addEventListener('keydown', shortcutHandler);
|
textarea.addEventListener('keydown', shortcutHandler);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Transform non-JS basic editor to two-column layout with preview
|
|
||||||
$$('.js-preview-input-wrapper').forEach(wrapper => {
|
|
||||||
wrapper.classList.remove('block__column--full');
|
|
||||||
wrapper.classList.add('block__column--half');
|
|
||||||
});
|
|
||||||
$$('.js-preview-output-wrapper').forEach(wrapper => {
|
|
||||||
showEl(wrapper);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { setupToolbar };
|
export { setupToolbar };
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
import { fetchJson } from './utils/requests';
|
import { fetchJson } from './utils/requests';
|
||||||
import { filterNode } from './imagesclientside';
|
import { filterNode } from './imagesclientside';
|
||||||
import { debounce } from './utils/events.js';
|
|
||||||
import { hideEl, showEl } from './utils/dom.js';
|
import { hideEl, showEl } from './utils/dom.js';
|
||||||
|
|
||||||
function handleError(response) {
|
function handleError(response) {
|
||||||
|
@ -37,39 +36,21 @@ function commentReply(user, url, textarea, quote) {
|
||||||
textarea.focus();
|
textarea.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function getPreview(body, anonymous, previewLoading, previewIdle, previewContent) {
|
||||||
* Stores the abort controller for the current preview request
|
|
||||||
* @type {null|AbortController}
|
|
||||||
*/
|
|
||||||
let previewAbortController = null;
|
|
||||||
|
|
||||||
function getPreview(body, anonymous, previewLoading, previewContent) {
|
|
||||||
const path = '/posts/preview';
|
const path = '/posts/preview';
|
||||||
|
|
||||||
if (typeof body !== 'string') return;
|
if (typeof body !== 'string') return;
|
||||||
|
|
||||||
const trimmedBody = body.trim();
|
|
||||||
if (trimmedBody.length < 1) {
|
|
||||||
previewContent.innerHTML = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
showEl(previewLoading);
|
showEl(previewLoading);
|
||||||
|
hideEl(previewIdle);
|
||||||
|
|
||||||
// Abort previous requests if it exists
|
fetchJson('POST', path, { body, anonymous })
|
||||||
if (previewAbortController) previewAbortController.abort();
|
|
||||||
previewAbortController = new AbortController();
|
|
||||||
|
|
||||||
fetchJson('POST', path, { body, anonymous }, previewAbortController.signal)
|
|
||||||
.then(handleError)
|
.then(handleError)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
previewContent.innerHTML = data;
|
previewContent.innerHTML = data;
|
||||||
filterNode(previewContent);
|
filterNode(previewContent);
|
||||||
showEl(previewContent);
|
showEl(previewIdle);
|
||||||
hideEl(previewLoading);
|
hideEl(previewLoading);
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
previewAbortController = null;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,34 +80,41 @@ function setupPreviews() {
|
||||||
textarea = document.querySelector('.js-preview-description');
|
textarea = document.querySelector('.js-preview-description');
|
||||||
}
|
}
|
||||||
|
|
||||||
const previewLoading = document.querySelector('.communication-preview__loading');
|
const previewButton = document.querySelector('a[data-click-tab="preview"]');
|
||||||
const previewContent = document.querySelector('.communication-preview__content');
|
const previewLoading = document.querySelector('.js-preview-loading');
|
||||||
|
const previewIdle = document.querySelector('.js-preview-idle');
|
||||||
|
const previewContent = document.querySelector('.js-preview-content');
|
||||||
const previewAnon = document.querySelector('.js-preview-anonymous') || false;
|
const previewAnon = document.querySelector('.js-preview-anonymous') || false;
|
||||||
|
|
||||||
if (!textarea || !previewContent) {
|
if (!textarea || !previewContent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getCacheKey = () => {
|
||||||
|
return (previewAnon && previewAnon.checked ? 'anon;' : '') + textarea.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const previewedTextAttribute = 'data-previewed-text';
|
||||||
const updatePreview = () => {
|
const updatePreview = () => {
|
||||||
getPreview(textarea.value, previewAnon && previewAnon.checked, previewLoading, previewContent);
|
const cachedValue = getCacheKey()
|
||||||
|
if (previewContent.getAttribute(previewedTextAttribute) === cachedValue) return;
|
||||||
|
previewContent.setAttribute(previewedTextAttribute, cachedValue);
|
||||||
|
|
||||||
|
getPreview(textarea.value, previewAnon && previewAnon.checked, previewLoading, previewIdle, previewContent);
|
||||||
};
|
};
|
||||||
|
|
||||||
const debouncedUpdater = debounce(500, () => {
|
previewButton.addEventListener('click', updatePreview);
|
||||||
if (previewContent.previewedText === textarea.value) return;
|
|
||||||
previewContent.previewedText = textarea.value;
|
|
||||||
|
|
||||||
updatePreview();
|
|
||||||
});
|
|
||||||
|
|
||||||
textarea.addEventListener('keydown', debouncedUpdater);
|
|
||||||
textarea.addEventListener('focus', debouncedUpdater);
|
|
||||||
textarea.addEventListener('change', resizeTextarea);
|
textarea.addEventListener('change', resizeTextarea);
|
||||||
textarea.addEventListener('keyup', resizeTextarea);
|
textarea.addEventListener('keyup', resizeTextarea);
|
||||||
|
|
||||||
// Fire handler if textarea contains text on page load (e.g. editing)
|
// Fire handler for automatic resizing if textarea contains text on page load (e.g. editing)
|
||||||
if (textarea.value) textarea.dispatchEvent(new Event('keydown'));
|
if (textarea.value) textarea.dispatchEvent(new Event('change'));
|
||||||
|
|
||||||
previewAnon && previewAnon.addEventListener('click', updatePreview);
|
previewAnon && previewAnon.addEventListener('click', () => {
|
||||||
|
if (previewContent.classList.contains('hidden')) return;
|
||||||
|
|
||||||
|
updatePreview();
|
||||||
|
});
|
||||||
|
|
||||||
document.addEventListener('click', event => {
|
document.addEventListener('click', event => {
|
||||||
if (event.target && event.target.closest('.post-reply')) {
|
if (event.target && event.target.closest('.post-reply')) {
|
||||||
|
|
|
@ -22,22 +22,3 @@ export function delegate(node, event, selectors) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs the provided `func` if it hasn't been called for at least `time` ms
|
|
||||||
* @template {(...any[]) => any} T
|
|
||||||
* @param {number} time
|
|
||||||
* @param {T} func
|
|
||||||
* @return {T}
|
|
||||||
*/
|
|
||||||
export function debounce(time, func) {
|
|
||||||
let timerId = null;
|
|
||||||
|
|
||||||
return function(...args) {
|
|
||||||
// Cancels the setTimeout method execution
|
|
||||||
timerId && clearTimeout(timerId);
|
|
||||||
|
|
||||||
// Executes the func after delay time.
|
|
||||||
timerId = setTimeout(() => func(...args), time);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* Request Utils
|
* Request Utils
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function fetchJson(verb, endpoint, body, signal) {
|
function fetchJson(verb, endpoint, body) {
|
||||||
const data = {
|
const data = {
|
||||||
method: verb,
|
method: verb,
|
||||||
credentials: 'same-origin',
|
credentials: 'same-origin',
|
||||||
|
@ -11,7 +11,6 @@ function fetchJson(verb, endpoint, body, signal) {
|
||||||
'x-csrf-token': window.booru.csrfToken,
|
'x-csrf-token': window.booru.csrfToken,
|
||||||
'x-requested-with': 'xmlhttprequest'
|
'x-requested-with': 'xmlhttprequest'
|
||||||
},
|
},
|
||||||
signal,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (body) {
|
if (body) {
|
||||||
|
|
|
@ -3,12 +3,17 @@
|
||||||
- action_icon = assigns[:action_icon] || 'edit'
|
- action_icon = assigns[:action_icon] || 'edit'
|
||||||
- field_name = assigns[:name] || :body
|
- field_name = assigns[:name] || :body
|
||||||
- field_placeholder = assigns[:placeholder] || "Your message"
|
- field_placeholder = assigns[:placeholder] || "Your message"
|
||||||
.block__content.block--split
|
.block__header.block__header--js-tabbed
|
||||||
.block__column--full.js-preview-input-wrapper
|
a.selected href="#" data-click-tab="write"
|
||||||
.block__column__header
|
|
||||||
i.fa> class="fa-#{action_icon}"
|
i.fa> class="fa-#{action_icon}"
|
||||||
= action_text
|
= action_text
|
||||||
|
|
||||||
|
a href="#" data-click-tab="preview"
|
||||||
|
i.fa.fa-cog.fa-fw.fa-spin.js-preview-loading.hidden> title=raw('Loading preview…')
|
||||||
|
i.fa.fa-eye.fa-fw.js-preview-idle>
|
||||||
|
| Preview
|
||||||
|
|
||||||
|
.block__tab.communication-edit__tab.selected.js-preview-input-wrapper data-tab="write"
|
||||||
= render PhilomenaWeb.MarkdownView, "_help.html", conn: @conn
|
= render PhilomenaWeb.MarkdownView, "_help.html", conn: @conn
|
||||||
= render PhilomenaWeb.MarkdownView, "_toolbar.html", conn: @conn
|
= render PhilomenaWeb.MarkdownView, "_toolbar.html", conn: @conn
|
||||||
|
|
||||||
|
@ -16,12 +21,4 @@
|
||||||
= textarea form, field_name, class: "input input--wide input--text input--resize-vertical js-toolbar-input js-preview-input", placeholder: field_placeholder, required: true
|
= textarea form, field_name, class: "input input--wide input--text input--resize-vertical js-toolbar-input js-preview-input", placeholder: field_placeholder, required: true
|
||||||
= error_tag form, field_name
|
= error_tag form, field_name
|
||||||
|
|
||||||
.block__column--half.hidden.communication-preview.js-preview-output-wrapper
|
.block__tab.communication-edit__tab.hidden.js-preview-content data-tab="preview"
|
||||||
.block__column__header.flex.flex--spaced-out
|
|
||||||
span
|
|
||||||
i.fa.fa-eye>
|
|
||||||
' Preview
|
|
||||||
span.communication-preview__loading.hidden
|
|
||||||
i.fa.fa-spin.fa-fw.fa-cog> title=raw('Loading preview…')
|
|
||||||
|
|
||||||
.communication-preview__content
|
|
||||||
|
|
Loading…
Reference in a new issue