mirror of
https://github.com/the-djmaze/snappymail.git
synced 2024-12-25 16:42:13 +08:00
Redesign WYSIWYG handling to allow any editor (quill, tiny, etc. etc.)
This commit is contained in:
parent
e6db60e76c
commit
9eb5646aec
12 changed files with 180 additions and 51 deletions
|
@ -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() {
|
||||
|
|
27
dev/External/SquireUI.js
vendored
27
dev/External/SquireUI.js
vendored
|
@ -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();
|
||||
|
|
2
dev/External/User/ko.js
vendored
2
dev/External/User/ko.js
vendored
|
@ -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;
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -704,7 +704,6 @@ export class ComposePopupView extends AbstractViewPopup {
|
|||
// setTimeout(() => {
|
||||
this.oEditor = new HtmlEditor(
|
||||
this.editorArea(),
|
||||
null,
|
||||
() => fOnInit(this.oEditor),
|
||||
bHtml => this.isHtml(!!bHtml)
|
||||
);
|
||||
|
|
82
plugins/wysiwyg-example/example.js
Normal file
82
plugins/wysiwyg-example/example.js
Normal 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);
|
41
plugins/wysiwyg-example/index.php
Normal file
41
plugins/wysiwyg-example/index.php
Normal 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');
|
||||
}
|
||||
}
|
||||
}
|
1
plugins/wysiwyg-example/static/example.min.js
vendored
Normal file
1
plugins/wysiwyg-example/static/example.min.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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: {
|
||||
|
|
Loading…
Reference in a new issue