Redesign WYSIWYG handling to allow any editor (quill, tiny, etc. etc.)

This commit is contained in:
the-djmaze 2024-01-30 18:16:39 +01:00
parent e6db60e76c
commit 9eb5646aec
12 changed files with 180 additions and 51 deletions

View file

@ -725,20 +725,12 @@ export const
WYSIWYGS = ko.observableArray();
WYSIWYGS.push(['Squire', (owner, container, onReady)=>{
let squire = new SquireUI(container);
setTimeout(()=>onReady(squire), 1);
/*
squire.on('blur', () => owner.blurTrigger());
squire.on('focus', () => clearTimeout(owner.blurTimer));
squire.on('mode', () => {
owner.blurTrigger();
owner.onModeChange?.(!owner.isPlain());
});
*/
}]);
WYSIWYGS.push({
name: 'Squire',
construct: (owner, container, onReady) => onReady(new SquireUI(container))
});
rl.registerWYSIWYG = (name, construct) => WYSIWYGS.push([name, construct]);
rl.registerWYSIWYG = (name, construct) => WYSIWYGS.push({name, construct});
export class HtmlEditor {
/**
@ -747,7 +739,7 @@ export class HtmlEditor {
* @param {Function=} onReady
* @param {Function=} onModeChange
*/
constructor(element, onBlur = null, onReady = null, onModeChange = null) {
constructor(element, onReady = null, onModeChange = null, onBlur = null) {
this.blurTimer = 0;
this.onBlur = onBlur;
@ -757,10 +749,10 @@ export class HtmlEditor {
onReady = onReady ? [onReady] : [];
this.onReady = fn => onReady.push(fn);
// TODO: make 'which' user configurable
// const which = 'CKEditor4',
// wysiwyg = WYSIWYGS.find(item => which == item[0]) || WYSIWYGS.find(item => 'Squire' == item[0]);
const wysiwyg = WYSIWYGS.find(item => 'Squire' == item[0]);
wysiwyg[1](this, element, editor => {
const which = SettingsUserStore.editorWysiwyg(),
wysiwyg = WYSIWYGS.find(item => which == item.name) || WYSIWYGS.find(item => 'Squire' == item.name);
// const wysiwyg = WYSIWYGS.find(item => 'Squire' == item.name);
wysiwyg.construct(this, element, editor => setTimeout(()=>{
this.editor = editor;
editor.on('blur', () => this.blurTrigger());
editor.on('focus', () => clearTimeout(this.blurTimer));
@ -770,7 +762,7 @@ export class HtmlEditor {
});
this.onReady = fn => fn();
onReady.forEach(fn => fn());
});
},1));
}
}
@ -826,8 +818,8 @@ export class HtmlEditor {
let result = '';
if (this.editor) {
try {
if (this.isPlain() && this.editor.plugins.plain && this.editor.__plain) {
result = this.editor.__plain.getRawData();
if (this.isPlain()) {
result = this.editor.getPlainData();
} else {
result = this.editor.getData();
}
@ -862,8 +854,8 @@ export class HtmlEditor {
this.clearCachedSignature();
try {
editor.setMode(mode);
if (this.isPlain() && editor.plugins.plain && editor.__plain) {
editor.__plain.setRawData(data);
if (this.isPlain()) {
editor.setPlainData(data);
} else {
editor.setData(data);
}
@ -883,16 +875,8 @@ export class HtmlEditor {
this.onReady(() => this.editor.focus());
}
hasFocus() {
try {
return !!this.editor?.focusManager.hasFocus;
} catch (e) {
return false;
}
}
blur() {
this.onReady(() => this.editor.focusManager.blur(true));
this.onReady(() => this.editor.blur());
}
clear() {

View file

@ -318,11 +318,6 @@ class SquireUI
wysiwyg.className = 'squire-wysiwyg';
wysiwyg.dir = 'auto';
this.mode = ''; // 'plain' | 'wysiwyg'
this.__plain = {
getRawData: () => this.plain.value,
setRawData: plain => this.plain.value = plain
};
this.container = container;
this.squire = squire;
this.plain = plain;
@ -530,15 +525,6 @@ class SquireUI
console.dir({select:e.range});
});
*/
// CKEditor gimmicks used by HtmlEditor
this.plugins = {
plain: true
};
this.focusManager = {
hasFocus: () => squire._isFocused,
blur: () => squire.blur()
};
}
doAction(name) {
@ -576,7 +562,6 @@ class SquireUI
this.modeSelect.selectedIndex = 'plain' == this.mode ? 1 : 0;
}
// CKeditor gimmicks used by HtmlEditor
on(type, fn) {
if ('mode' == type) {
this.onModeChange = fn;
@ -643,6 +628,18 @@ class SquireUI
squire.setSelection( range );
}
getPlainData() {
return this.plain.value;
}
setPlainData(text) {
this.plain.value = text;
}
blur() {
this.squire.blur();
}
focus() {
if ('plain' == this.mode) {
this.plain.focus();

View file

@ -91,7 +91,7 @@ Object.assign(ko.bindingHandlers, {
};
if (ko.isObservable(fValue)) {
editor = new HtmlEditor(element, fUpdateKoValue, fOnReady, fUpdateKoValue);
editor = new HtmlEditor(element, fOnReady, fUpdateKoValue, fUpdateKoValue);
fValue.__fetchEditorValue = fUpdateKoValue;

View file

@ -5,6 +5,7 @@ import { SaveSettingStatus } from 'Common/Enums';
import { LayoutSideView, LayoutBottomView } from 'Common/EnumsUser';
import { setRefreshFoldersInterval } from 'Common/Folders';
import { Settings, SettingsGet } from 'Common/Globals';
import { WYSIWYGS } from 'Common/Html';
import { isArray } from 'Common/Utils';
import { addSubscribablesTo, addComputablesTo } from 'External/ko';
import { i18n, translateTrigger, translatorReload, convertLangName } from 'Common/Translator';
@ -44,7 +45,7 @@ export class UserSettingsGeneral extends AbstractViewSettings {
['useThreads',
// These use addSetting()
'layout', 'messageReadDelay', 'messagesPerPage', 'checkMailInterval',
'editorDefaultType', 'msgDefaultAction', 'maxBlockquotesLevel',
'editorDefaultType', 'editorWysiwyg', 'msgDefaultAction', 'maxBlockquotesLevel',
// These are in addSettings()
'requestReadReceipt', 'requestDsn', 'requireTLS', 'pgpSign', 'pgpEncrypt',
'viewHTML', 'viewImages', 'viewImagesWhitelist', 'removeColors', 'allowStyles', 'allowDraftAutosave',
@ -59,6 +60,8 @@ export class UserSettingsGeneral extends AbstractViewSettings {
this.identities = IdentityUserStore;
this.wysiwygs = WYSIWYGS;
addComputablesTo(this, {
languageFullName: () => convertLangName(this.language()),
@ -80,6 +83,8 @@ export class UserSettingsGeneral extends AbstractViewSettings {
];
},
hasWysiwygs: () => 1 < WYSIWYGS().length,
msgDefaultActions: () => {
translateTrigger();
return [
@ -99,6 +104,7 @@ export class UserSettingsGeneral extends AbstractViewSettings {
});
this.addSetting('EditorDefaultType');
this.addSetting('editorWysiwyg');
this.addSetting('MsgDefaultAction');
this.addSetting('MessageReadDelay');
this.addSetting('MessagesPerPage');

View file

@ -47,6 +47,7 @@ export const SettingsUserStore = new class {
layout: 1,
editorDefaultType: 'Html',
editorWysiwyg: 'Squire',
msgDefaultAction: 1
});
@ -78,9 +79,10 @@ export const SettingsUserStore = new class {
init() {
const self = this;
self.editorDefaultType(SettingsGet('EditorDefaultType'));
[
'EditorDefaultType',
'editorWysiwyg',
'messageNewWindow',
'messageReadAuto',
'MsgDefaultAction',

View file

@ -704,7 +704,6 @@ export class ComposePopupView extends AbstractViewPopup {
// setTimeout(() => {
this.oEditor = new HtmlEditor(
this.editorArea(),
null,
() => fOnInit(this.oEditor),
bHtml => this.isHtml(!!bHtml)
);

View file

@ -0,0 +1,82 @@
(rl => {
class Example
{
constructor(owner, editor) {
this.mode = 'wysiwyg';
this.owner = owner;
this.editor = editor;
console.dir({editor});
}
setMode(mode) {
console.log(`WYSIWYG-Example.setMode(${mode})`);
this.mode = mode;
}
on(type, fn) {
console.log(`WYSIWYG-Example.on(${type}, ${fn})`);
}
execCommand(cmd, cfg) {
console.log(`WYSIWYG-Example.execCommand(${cmd}, ${cfg})`);
/*
execCommand('insertSignature', {
clearCache: true
}));
execCommand('insertSignature', {
isHtml: html,
insertBefore: insertBefore,
signature: signature
}));
*/
}
getData() {
console.log(`WYSIWYG-Example.getData()`);
return this.editor.innerHTML;
}
setData(html) {
console.log(`WYSIWYG-Example.setData(${html})`);
this.editor.innerHTML = html;
}
getPlainData() {
console.log(`WYSIWYG-Example.getPlainData()`);
return this.editor.innerText;
}
setPlainData(text) {
console.log(`WYSIWYG-Example.setPlainData(${text})`);
return this.editor.textContent = text;
}
blur() {
console.log(`WYSIWYG-Example.blur()`);
this.editor.blur();
}
focus() {
console.log(`WYSIWYG-Example.focus()`);
}
}
if (rl) {
const path = rl.settings.app('webVersionPath'),
script = document.createElement('script');
script.src = path + 'static/wysiwyg-example/example.min.js';
document.head.append(script);
/**
* owner = HtmlEditor
* container = HTMLElement
* onReady = callback(SMQuill)
*/
rl.registerWYSIWYG('Example', (owner, container, onReady) => {
const editor = new Example(owner, container);
onReady(editor);
});
}
})(window.rl);

View file

@ -0,0 +1,41 @@
<?php
class WysiwygQuillPlugin extends \RainLoop\Plugins\AbstractPlugin
{
const
NAME = 'WYSIWYG Example',
VERSION = '1.0',
RELEASE = '2024-01-30',
REQUIRED = '2.34.0',
DESCRIPTION = 'Add Example as WYSIWYG editor option';
public function Init() : void
{
$path = APP_VERSION_ROOT_PATH . 'static/wysiwyg-example';
// Apache AH00037: Symbolic link not allowed or link target not accessible
// That's why we clone the source
if (!\is_dir($path)/* && !\is_link($path)*/) {
$old_mask = umask(0022);
// $active = \symlink(__DIR__ . '/src', $path);
\mkdir($path, 0755);
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(__DIR__ . '/static', \RecursiveDirectoryIterator::SKIP_DOTS),
\RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $item) {
if ($item->isDir()) {
\mkdir($path . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
} else {
\copy($item, $path . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
}
}
umask($old_mask);
}
if (\is_file("{$path}/example.min.js")) {
// $this->addCss('style.css');
$this->addJs('example.js');
}
}
}

View file

@ -0,0 +1 @@

View file

@ -702,6 +702,7 @@ class Actions
'DesktopNotifications' => true,
'Layout' => (int) $oConfig->Get('defaults', 'view_layout', Enumerations\Layout::SIDE_PREVIEW),
'EditorDefaultType' => \str_replace('Forced', '', $oConfig->Get('defaults', 'view_editor_type', '')),
'editorWysiwyg' => 'Squire',
'UseCheckboxesInList' => (bool) $oConfig->Get('defaults', 'view_use_checkboxes', true),
'showNextMessage' => (bool) $oConfig->Get('defaults', 'view_show_next_message', false),
'AutoLogout' => (int) $oConfig->Get('defaults', 'autologout', 30),
@ -766,6 +767,7 @@ class Actions
}
$aResult['EditorDefaultType'] = \str_replace('Forced', '', $oSettings->GetConf('EditorDefaultType', $aResult['EditorDefaultType']));
$aResult['editorWysiwyg'] = $oSettings->GetConf('editorWysiwyg', $aResult['editorWysiwyg']);
$aResult['requestReadReceipt'] = (bool) $oSettings->GetConf('requestReadReceipt', false);
$aResult['requestDsn'] = (bool) $oSettings->GetConf('requestDsn', false);
$aResult['requireTLS'] = (bool) $oSettings->GetConf('requireTLS', false);

View file

@ -177,6 +177,7 @@ trait User
});
$this->setSettingsFromParams($oSettings, 'EditorDefaultType', 'string');
$this->setSettingsFromParams($oSettings, 'editorWysiwyg', 'string');
$this->setSettingsFromParams($oSettings, 'requestReadReceipt', 'bool');
$this->setSettingsFromParams($oSettings, 'requestDsn', 'bool');
$this->setSettingsFromParams($oSettings, 'requireTLS', 'bool');

View file

@ -119,6 +119,20 @@
}
}"></div>
</div>
<div class="control-group" data-bind="visible: hasWysiwygs">
<label>WYSIWYG</label>
<div data-bind="component: {
name: 'Select',
params: {
options: wysiwygs,
value: editorWysiwyg,
trigger: editorWysiwygTrigger,
optionsText: 'name',
optionsValue: 'name'
}
}"></div>
</div>
<div class="control-group">
<div>
<div data-bind="component: {