text: {
serverUploadLimit: this.serverUploadLimitText,
imageUploadError: this.imageUploadErrorText,
- }
+ },
+ settings: this.loadSettings(),
}).then(editor => {
this.editor = editor;
this.setupListeners();
const name = actualInput.getAttribute('name');
const value = actualInput.getAttribute('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
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() {
const queryParams = (new URL(window.location)).searchParams;
const scrollText = queryParams.get('content-text');
// Handle scroll to sync display view
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
cm.on('paste', (cm, event) => {
} else {
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() {
import {Markdown} from "./markdown";
import {Display} from "./display";
import {Actions} from "./actions";
+import {Settings} from "./settings";
import {listen} from "./common-events";
import {init as initCodemirror} from "./codemirror";
const editor = {
config,
markdown: new Markdown(),
+ settings: new Settings(config.settings),
};
editor.actions = new Actions(editor);
* @property {HTMLTextAreaElement} inputEl
* @property {String} drawioUrl
* @property {Object<String, String>} text
+ * @property {Object<String, any>} settings
*/
/**
* @property {Markdown} markdown
* @property {Actions} actions
* @property {CodeMirror} cm
+ * @property {Settings} settings
*/
\ No newline at end of file
--- /dev/null
+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-', ''));
+ }
+}
\ No newline at end of file
+import {kebabToCamel, camelToKebab} from "./text";
+
/**
* 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.
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.
* @param {Element|Document} parentElement
export function firstOnElement(element, name) {
const elComponents = elementComponentMap.get(element) || {};
return elComponents[name] || null;
-}
-
-function camelToKebab(camelStr) {
- return camelStr.replace(/[A-Z]/g, (str, offset) => (offset > 0 ? '-' : '') + str.toLowerCase());
}
\ No newline at end of file
--- /dev/null
+/**
+ * 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());
+}
\ No newline at end of file
'pages_md_insert_image' => 'Insert Image',
'pages_md_insert_link' => 'Insert Entity Link',
'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_move' => 'Move Page',
'pages_move_success' => 'Page moved to ":parentName"',
border-bottom: 1px solid #DDD;
@include lightDark(border-color, #ddd, #000);
width: 50%;
- max-width: 50%;
}
.markdown-editor-wrap + .markdown-editor-wrap {
max-width: 100%;
flex-grow: 1;
}
- #markdown-editor .editor-toolbar {
- padding: 0;
- }
- #markdown-editor .editor-toolbar > * {
- padding: $-xs $-s;
- }
.editor-toolbar-label {
float: none !important;
@include lightDark(border-color, #DDD, #555);
<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 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>
<hr class="m-none">
<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 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-label text-mono px-m py-xs">{{ trans('entities.pages_md_preview') }}</div>
</div>