Connected md editor settings to logic for functionality

This commit is contained in:
Dan Brown 2022-11-28 12:12:36 +00:00
parent 9fd5190c70
commit ec3713bc74
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
10 changed files with 99 additions and 28 deletions

View File

@ -26,7 +26,8 @@ export class MarkdownEditor extends Component {
text: { text: {
serverUploadLimit: this.serverUploadLimitText, serverUploadLimit: this.serverUploadLimitText,
imageUploadError: this.imageUploadErrorText, imageUploadError: this.imageUploadErrorText,
} },
settings: this.loadSettings(),
}).then(editor => { }).then(editor => {
this.editor = editor; this.editor = editor;
this.setupListeners(); this.setupListeners();
@ -81,7 +82,7 @@ export class MarkdownEditor extends Component {
const name = actualInput.getAttribute('name'); const name = actualInput.getAttribute('name');
const value = actualInput.getAttribute('value'); const value = actualInput.getAttribute('value');
window.$http.patch('/preferences/update-boolean', {name, value}); window.$http.patch('/preferences/update-boolean', {name, value});
// TODO - Update state locally this.editor.settings.set(name, value === 'true');
}); });
// Refresh CodeMirror on container resize // Refresh CodeMirror on container resize
@ -90,6 +91,17 @@ export class MarkdownEditor extends Component {
observer.observe(this.elem); observer.observe(this.elem);
} }
loadSettings() {
const settings = {};
const inputs = this.settingContainer.querySelectorAll('input[type="hidden"]');
for (const input of inputs) {
settings[input.getAttribute('name')] = input.value === 'true';
}
return settings;
}
scrollToTextIfNeeded() { scrollToTextIfNeeded() {
const queryParams = (new URL(window.location)).searchParams; const queryParams = (new URL(window.location)).searchParams;
const scrollText = queryParams.get('content-text'); const scrollText = queryParams.get('content-text');

View File

@ -24,7 +24,13 @@ export async function init(editor) {
// Handle scroll to sync display view // Handle scroll to sync display view
const onScrollDebounced = debounce(editor.actions.syncDisplayPosition.bind(editor.actions), 100, false); const onScrollDebounced = debounce(editor.actions.syncDisplayPosition.bind(editor.actions), 100, false);
cm.on('scroll', instance => onScrollDebounced(instance)); let syncActive = editor.settings.get('scrollSync');
editor.settings.onChange('scrollSync', val => syncActive = val);
cm.on('scroll', instance => {
if (syncActive) {
onScrollDebounced(instance);
}
});
// Handle image paste // Handle image paste
cm.on('paste', (cm, event) => { cm.on('paste', (cm, event) => {

View File

@ -17,6 +17,14 @@ export class Display {
} else { } else {
this.container.addEventListener('load', this.onLoad.bind(this)); this.container.addEventListener('load', this.onLoad.bind(this));
} }
this.updateVisibility(editor.settings.get('showPreview'));
editor.settings.onChange('showPreview', show => this.updateVisibility(show));
}
updateVisibility(show) {
const wrap = this.container.closest('.markdown-editor-wrap');
wrap.style.display = show ? null : 'none';
} }
onLoad() { onLoad() {

View File

@ -1,6 +1,7 @@
import {Markdown} from "./markdown"; import {Markdown} from "./markdown";
import {Display} from "./display"; import {Display} from "./display";
import {Actions} from "./actions"; import {Actions} from "./actions";
import {Settings} from "./settings";
import {listen} from "./common-events"; import {listen} from "./common-events";
import {init as initCodemirror} from "./codemirror"; import {init as initCodemirror} from "./codemirror";
@ -18,6 +19,7 @@ export async function init(config) {
const editor = { const editor = {
config, config,
markdown: new Markdown(), markdown: new Markdown(),
settings: new Settings(config.settings),
}; };
editor.actions = new Actions(editor); editor.actions = new Actions(editor);
@ -38,6 +40,7 @@ export async function init(config) {
* @property {HTMLTextAreaElement} inputEl * @property {HTMLTextAreaElement} inputEl
* @property {String} drawioUrl * @property {String} drawioUrl
* @property {Object<String, String>} text * @property {Object<String, String>} text
* @property {Object<String, any>} settings
*/ */
/** /**
@ -47,4 +50,5 @@ export async function init(config) {
* @property {Markdown} markdown * @property {Markdown} markdown
* @property {Actions} actions * @property {Actions} actions
* @property {CodeMirror} cm * @property {CodeMirror} cm
* @property {Settings} settings
*/ */

View File

@ -0,0 +1,40 @@
import {kebabToCamel} from "../services/text";
export class Settings {
constructor(initialSettings) {
this.settingMap = {};
this.changeListeners = {};
this.merge(initialSettings);
}
set(key, value) {
key = this.normaliseKey(key);
this.settingMap[key] = value;
for (const listener of (this.changeListeners[key] || [])) {
listener(value);
}
}
get(key) {
return this.settingMap[this.normaliseKey(key)] || null;
}
merge(settings) {
for (const [key, value] of Object.entries(settings)) {
this.set(key, value);
}
}
onChange(key, callback) {
key = this.normaliseKey(key);
const listeners = this.changeListeners[this.normaliseKey(key)] || [];
listeners.push(callback);
this.changeListeners[this.normaliseKey(key)] = listeners;
}
normaliseKey(key) {
return kebabToCamel(key.replace('md-', ''));
}
}

View File

@ -1,3 +1,5 @@
import {kebabToCamel, camelToKebab} from "./text";
/** /**
* A mapping of active components keyed by name, with values being arrays of component * A mapping of active components keyed by name, with values being arrays of component
* instances since there can be multiple components of the same type. * instances since there can be multiple components of the same type.
@ -107,17 +109,6 @@ function parseOpts(name, element) {
return opts; return opts;
} }
/**
* Convert a kebab-case string to camelCase
* @param {String} kebab
* @returns {string}
*/
function kebabToCamel(kebab) {
const ucFirst = (word) => word.slice(0,1).toUpperCase() + word.slice(1);
const words = kebab.split('-');
return words[0] + words.slice(1).map(ucFirst).join('');
}
/** /**
* Initialize all components found within the given element. * Initialize all components found within the given element.
* @param {Element|Document} parentElement * @param {Element|Document} parentElement
@ -172,7 +163,3 @@ export function firstOnElement(element, name) {
const elComponents = elementComponentMap.get(element) || {}; const elComponents = elementComponentMap.get(element) || {};
return elComponents[name] || null; return elComponents[name] || null;
} }
function camelToKebab(camelStr) {
return camelStr.replace(/[A-Z]/g, (str, offset) => (offset > 0 ? '-' : '') + str.toLowerCase());
}

View File

@ -0,0 +1,19 @@
/**
* Convert a kebab-case string to camelCase
* @param {String} kebab
* @returns {string}
*/
export function kebabToCamel(kebab) {
const ucFirst = (word) => word.slice(0,1).toUpperCase() + word.slice(1);
const words = kebab.split('-');
return words[0] + words.slice(1).map(ucFirst).join('');
}
/**
* Convert a camelCase string to a kebab-case string.
* @param {String} camelStr
* @returns {String}
*/
export function camelToKebab(camelStr) {
return camelStr.replace(/[A-Z]/g, (str, offset) => (offset > 0 ? '-' : '') + str.toLowerCase());
}

View File

@ -224,6 +224,8 @@ return [
'pages_md_insert_image' => 'Insert Image', 'pages_md_insert_image' => 'Insert Image',
'pages_md_insert_link' => 'Insert Entity Link', 'pages_md_insert_link' => 'Insert Entity Link',
'pages_md_insert_drawing' => 'Insert Drawing', 'pages_md_insert_drawing' => 'Insert Drawing',
'pages_md_show_preview' => 'Show preview',
'pages_md_sync_scroll' => 'Sync preview scroll',
'pages_not_in_chapter' => 'Page is not in a chapter', 'pages_not_in_chapter' => 'Page is not in a chapter',
'pages_move' => 'Move Page', 'pages_move' => 'Move Page',
'pages_move_success' => 'Page moved to ":parentName"', 'pages_move_success' => 'Page moved to ":parentName"',

View File

@ -80,7 +80,6 @@
border-bottom: 1px solid #DDD; border-bottom: 1px solid #DDD;
@include lightDark(border-color, #ddd, #000); @include lightDark(border-color, #ddd, #000);
width: 50%; width: 50%;
max-width: 50%;
} }
.markdown-editor-wrap + .markdown-editor-wrap { .markdown-editor-wrap + .markdown-editor-wrap {
@ -97,12 +96,6 @@
max-width: 100%; max-width: 100%;
flex-grow: 1; flex-grow: 1;
} }
#markdown-editor .editor-toolbar {
padding: 0;
}
#markdown-editor .editor-toolbar > * {
padding: $-xs $-s;
}
.editor-toolbar-label { .editor-toolbar-label {
float: none !important; float: none !important;
@include lightDark(border-color, #DDD, #555); @include lightDark(border-color, #DDD, #555);

View File

@ -20,11 +20,11 @@
<button refs="dropdown@toggle" class="text-button" type="button" title="{{ trans('common.more') }}">@icon('more')</button> <button refs="dropdown@toggle" class="text-button" type="button" title="{{ trans('common.more') }}">@icon('more')</button>
<div refs="dropdown@menu markdown-editor@setting-container" class="dropdown-menu" role="menu"> <div refs="dropdown@menu markdown-editor@setting-container" class="dropdown-menu" role="menu">
<div class="px-m"> <div class="px-m">
@include('form.toggle-switch', ['name' => 'md-show-preview', 'label' => 'Show preview', 'value' => setting()->getForCurrentUser('md-show-preview')]) @include('form.toggle-switch', ['name' => 'md-show-preview', 'label' => trans('entities.pages_md_show_preview'), 'value' => setting()->getForCurrentUser('md-show-preview')])
</div> </div>
<hr class="m-none"> <hr class="m-none">
<div class="px-m"> <div class="px-m">
@include('form.toggle-switch', ['name' => 'md-scroll-sync', 'label' => 'Sync preview scroll', 'value' => setting()->getForCurrentUser('md-scroll-sync')]) @include('form.toggle-switch', ['name' => 'md-scroll-sync', 'label' => trans('entities.pages_md_sync_scroll'), 'value' => setting()->getForCurrentUser('md-scroll-sync')])
</div> </div>
</div> </div>
</div> </div>
@ -40,7 +40,7 @@
</div> </div>
<div class="markdown-editor-wrap"> <div class="markdown-editor-wrap" @if(!setting()->getForCurrentUser('md-show-preview')) style="display: none;" @endif>
<div class="editor-toolbar"> <div class="editor-toolbar">
<div class="editor-toolbar-label text-mono px-m py-xs">{{ trans('entities.pages_md_preview') }}</div> <div class="editor-toolbar-label text-mono px-m py-xs">{{ trans('entities.pages_md_preview') }}</div>
</div> </div>