revert split editor styling, update markdown toolbar buttons' behavior

This commit is contained in:
SeinopSys 2021-09-29 20:00:46 +02:00
parent b8aa9b2c9c
commit f61cc6d0be
No known key found for this signature in database
GPG key ID: 9BFB053C1BA6C5C4
7 changed files with 90 additions and 156 deletions

View file

@ -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;
}
}
}

View file

@ -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;
} }

View file

@ -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 };

View file

@ -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')) {

View file

@ -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);
};
}

View file

@ -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) {

View file

@ -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&hellip;')
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&hellip;')
.communication-preview__content