mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Connected md editor settings to logic for functionality
This commit is contained in:
parent
9fd5190c70
commit
ec3713bc74
@ -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');
|
||||||
|
@ -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) => {
|
||||||
|
@ -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() {
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
40
resources/js/markdown/settings.js
Normal file
40
resources/js/markdown/settings.js
Normal 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-', ''));
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
|
||||||
}
|
|
19
resources/js/services/text.js
Normal file
19
resources/js/services/text.js
Normal 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());
|
||||||
|
}
|
@ -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"',
|
||||||
|
@ -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);
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user