From 31c28be57a53bc543e34bdf113ecd64e8ee11ed1 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 28 Nov 2022 14:08:20 +0000 Subject: [PATCH] Converted md settings to localstorage, added preview resize --- app/Config/setting-defaults.php | 2 - .../Controllers/UserPreferencesController.php | 21 ------ resources/js/components/markdown-editor.js | 50 +++++++++------ resources/js/markdown/editor.js | 4 +- resources/js/markdown/settings.js | 64 +++++++++++++------ resources/sass/_forms.scss | 22 ++++--- .../pages/parts/markdown-editor.blade.php | 23 ++++--- tests/User/UserPreferencesTest.php | 18 ------ 8 files changed, 102 insertions(+), 102 deletions(-) diff --git a/app/Config/setting-defaults.php b/app/Config/setting-defaults.php index 4e7ba2fcb..5e1e4348a 100644 --- a/app/Config/setting-defaults.php +++ b/app/Config/setting-defaults.php @@ -29,8 +29,6 @@ return [ 'ui-shortcuts' => '{}', 'ui-shortcuts-enabled' => false, 'dark-mode-enabled' => env('APP_DEFAULT_DARK_MODE', false), - 'md-show-preview' => true, - 'md-scroll-sync' => true, 'bookshelves_view_type' => env('APP_VIEWS_BOOKSHELVES', 'grid'), 'bookshelf_view_type' => env('APP_VIEWS_BOOKSHELF', 'grid'), 'books_view_type' => env('APP_VIEWS_BOOKS', 'grid'), diff --git a/app/Http/Controllers/UserPreferencesController.php b/app/Http/Controllers/UserPreferencesController.php index 11ff338f3..560dd1632 100644 --- a/app/Http/Controllers/UserPreferencesController.php +++ b/app/Http/Controllers/UserPreferencesController.php @@ -139,25 +139,4 @@ class UserPreferencesController extends Controller setting()->putForCurrentUser('code-language-favourites', implode(',', $currentFavorites)); return response('', 204); } - - /** - * Update a boolean user preference setting. - */ - public function updateBooleanPreference(Request $request) - { - $allowedKeys = ['md-scroll-sync', 'md-show-preview']; - $validated = $this->validate($request, [ - 'name' => ['required', 'string'], - 'value' => ['required'], - ]); - - if (!in_array($validated['name'], $allowedKeys)) { - return response('Invalid boolean preference', 500); - } - - $value = $validated['value'] === 'true' ? 'true' : 'false'; - setting()->putForCurrentUser($validated['name'], $value); - - return response('', 204); - } } diff --git a/resources/js/components/markdown-editor.js b/resources/js/components/markdown-editor.js index 373fedf48..4c3de91f6 100644 --- a/resources/js/components/markdown-editor.js +++ b/resources/js/components/markdown-editor.js @@ -14,7 +14,11 @@ export class MarkdownEditor extends Component { this.display = this.$refs.display; this.input = this.$refs.input; - this.settingContainer = this.$refs.settingContainer; + this.divider = this.$refs.divider; + this.displayWrap = this.$refs.displayWrap; + + const settingContainer = this.$refs.settingContainer; + const settingInputs = settingContainer.querySelectorAll('input[type="checkbox"]'); this.editor = null; initEditor({ @@ -23,11 +27,11 @@ export class MarkdownEditor extends Component { displayEl: this.display, inputEl: this.input, drawioUrl: this.getDrawioUrl(), + settingInputs: Array.from(settingInputs), text: { serverUploadLimit: this.serverUploadLimitText, imageUploadError: this.imageUploadErrorText, }, - settings: this.loadSettings(), }).then(editor => { this.editor = editor; this.setupListeners(); @@ -76,30 +80,40 @@ export class MarkdownEditor extends Component { toolbarLabel.closest('.markdown-editor-wrap').classList.add('active'); }); - // Setting changes - this.settingContainer.addEventListener('change', e => { - const actualInput = e.target.parentNode.querySelector('input[type="hidden"]'); - const name = actualInput.getAttribute('name'); - const value = actualInput.getAttribute('value'); - window.$http.patch('/preferences/update-boolean', {name, value}); - this.editor.settings.set(name, value === 'true'); - }); - // Refresh CodeMirror on container resize const resizeDebounced = debounce(() => this.editor.cm.refresh(), 100, false); const observer = new ResizeObserver(resizeDebounced); observer.observe(this.elem); + + this.handleDividerDrag(); } - loadSettings() { - const settings = {}; - const inputs = this.settingContainer.querySelectorAll('input[type="hidden"]'); + handleDividerDrag() { + this.divider.addEventListener('pointerdown', event => { + const wrapRect = this.elem.getBoundingClientRect(); + const moveListener = (event) => { + const xRel = event.pageX - wrapRect.left; + const xPct = Math.min(Math.max(20, Math.floor((xRel / wrapRect.width) * 100)), 80); + this.displayWrap.style.flexBasis = `${100-xPct}%`; + this.editor.settings.set('editorWidth', xPct); + }; + const upListener = (event) => { + window.removeEventListener('pointermove', moveListener); + window.removeEventListener('pointerup', upListener); + this.display.style.pointerEvents = null; + document.body.style.userSelect = null; + this.editor.cm.refresh(); + }; - for (const input of inputs) { - settings[input.getAttribute('name')] = input.value === 'true'; + this.display.style.pointerEvents = 'none'; + document.body.style.userSelect = 'none'; + window.addEventListener('pointermove', moveListener); + window.addEventListener('pointerup', upListener); + }); + const widthSetting = this.editor.settings.get('editorWidth'); + if (widthSetting) { + this.displayWrap.style.flexBasis = `${100-widthSetting}%`; } - - return settings; } scrollToTextIfNeeded() { diff --git a/resources/js/markdown/editor.js b/resources/js/markdown/editor.js index f2e34b9f0..1cf4cef2b 100644 --- a/resources/js/markdown/editor.js +++ b/resources/js/markdown/editor.js @@ -19,7 +19,7 @@ export async function init(config) { const editor = { config, markdown: new Markdown(), - settings: new Settings(config.settings), + settings: new Settings(config.settingInputs), }; editor.actions = new Actions(editor); @@ -39,8 +39,8 @@ export async function init(config) { * @property {Element} displayEl * @property {HTMLTextAreaElement} inputEl * @property {String} drawioUrl + * @property {HTMLInputElement[]} settingInputs * @property {Object} text - * @property {Object} settings */ /** diff --git a/resources/js/markdown/settings.js b/resources/js/markdown/settings.js index 6dd142210..62aab82e9 100644 --- a/resources/js/markdown/settings.js +++ b/resources/js/markdown/settings.js @@ -1,40 +1,62 @@ -import {kebabToCamel} from "../services/text"; - - export class Settings { - constructor(initialSettings) { - this.settingMap = {}; + constructor(settingInputs) { + this.settingMap = { + scrollSync: true, + showPreview: true, + editorWidth: 50, + }; this.changeListeners = {}; - this.merge(initialSettings); + this.loadFromLocalStorage(); + this.applyToInputs(settingInputs); + this.listenToInputChanges(settingInputs); + } + + applyToInputs(inputs) { + for (const input of inputs) { + const name = input.getAttribute('name').replace('md-', ''); + input.checked = this.settingMap[name]; + } + } + + listenToInputChanges(inputs) { + for (const input of inputs) { + input.addEventListener('change', event => { + const name = input.getAttribute('name').replace('md-', ''); + this.set(name, input.checked); + }); + } + } + + loadFromLocalStorage() { + const lsValString = window.localStorage.getItem('md-editor-settings'); + if (!lsValString) { + return; + } + + const lsVals = JSON.parse(lsValString); + for (const [key, value] of Object.entries(lsVals)) { + if (value !== null && this.settingMap[key] !== undefined) { + this.settingMap[key] = value; + } + } } set(key, value) { - key = this.normaliseKey(key); this.settingMap[key] = value; + window.localStorage.setItem('md-editor-settings', JSON.stringify(this.settingMap)); 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); - } + return this.settingMap[key] || null; } onChange(key, callback) { - key = this.normaliseKey(key); - const listeners = this.changeListeners[this.normaliseKey(key)] || []; + const listeners = this.changeListeners[key] || []; listeners.push(callback); - this.changeListeners[this.normaliseKey(key)] = listeners; - } - - normaliseKey(key) { - return kebabToCamel(key.replace('md-', '')); + this.changeListeners[key] = listeners; } } \ No newline at end of file diff --git a/resources/sass/_forms.scss b/resources/sass/_forms.scss index e4331a03f..ef14f6221 100644 --- a/resources/sass/_forms.scss +++ b/resources/sass/_forms.scss @@ -60,10 +60,6 @@ outline: 0; } } - .markdown-display, .markdown-editor-wrap { - flex: 1; - position: relative; - } &.fullscreen { position: fixed; top: 0; @@ -74,17 +70,22 @@ } .markdown-editor-wrap { - display: flex; - flex-direction: column; border-top: 1px solid #DDD; border-bottom: 1px solid #DDD; @include lightDark(border-color, #ddd, #000); - width: 50%; + position: relative; + flex: 1; +} +.markdown-editor-wrap + .markdown-editor-wrap { + flex-basis: 50%; + flex-shrink: 0; + flex-grow: 0; } -.markdown-editor-wrap + .markdown-editor-wrap { - border-inline-start: 1px solid; - @include lightDark(border-color, #ddd, #000); +.markdown-panel-divider { + width: 2px; + @include lightDark(background-color, #ddd, #000); + cursor: col-resize; } @include smaller-than($m) { @@ -95,6 +96,7 @@ width: 100%; max-width: 100%; flex-grow: 1; + flex-basis: auto !important; } .editor-toolbar-label { float: none !important; diff --git a/resources/views/pages/parts/markdown-editor.blade.php b/resources/views/pages/parts/markdown-editor.blade.php index f90a2f54c..fd8a20a04 100644 --- a/resources/views/pages/parts/markdown-editor.blade.php +++ b/resources/views/pages/parts/markdown-editor.blade.php @@ -5,7 +5,7 @@ option:markdown-editor:server-upload-limit-text="{{ trans('errors.server_upload_limit') }}" class="flex-fill flex code-fill"> -
+
{{ trans('entities.pages_md_editor') }} @@ -20,11 +20,11 @@
@@ -40,14 +40,17 @@
-
getForCurrentUser('md-show-preview')) style="display: none;" @endif> -
-
{{ trans('entities.pages_md_preview') }}
+
diff --git a/tests/User/UserPreferencesTest.php b/tests/User/UserPreferencesTest.php index c8f4d2754..03dad7990 100644 --- a/tests/User/UserPreferencesTest.php +++ b/tests/User/UserPreferencesTest.php @@ -191,22 +191,4 @@ class UserPreferencesTest extends TestCase $resp = $this->get($page->getUrl('/edit')); $resp->assertSee('option:code-editor:favourites="javascript,ruby"', false); } - - public function test_update_boolean() - { - $editor = $this->getEditor(); - - $this->assertTrue(setting()->getUser($editor, 'md-show-preview')); - - $resp = $this->actingAs($editor)->patch('/preferences/update-boolean', ['name' => 'md-show-preview', 'value' => 'false']); - $resp->assertStatus(204); - - $this->assertFalse(setting()->getUser($editor, 'md-show-preview')); - } - - public function test_update_boolean_rejects_unfamiliar_key() - { - $resp = $this->asEditor()->patch('/preferences/update-boolean', ['name' => 'md-donkey-show', 'value' => 'false']); - $resp->assertStatus(500); - } }