Произносится [ˈwɪziwɪɡ], является аббревиатурой от
англ. What You See Is What You Get, «что видишь, то и получишь».
И так, казалось бы всё просто, задаём `contentEditable` и вуаля, содержимое элемента прекрасно редактируется, так же из коробки работают некотоыре хоткеи, например `cmd+B`.
<div contentEditable="true"> Давай, нажми enter! </div>
Starting CKBuilder... Cleaning up target folder Copying files (relax, this may take a while) Time taken.....: 13.688 seconds Merging language files Time taken.....: 3.536 seconds Generating plugins sprite image Building ckeditor.js Created ckeditor.js (**1 536 KB**) Time taken.....: 5.269 seconds Building skins Cleaning up target folder ========================== Release process completed: Number of files: 122 Total size.....: **3 503 854 bytes** Time taken.....: 22.964 seconds
import {wysiwyg} from "compose-wysiwyg";
#! import CKEDITOR from "ckeditor-clean";
#! import ckeditorAdapter from "compose-wysiwyg/adapter/ckeditor";
#! import whiteTheme from "compose-wysiwyg/theme/white";
#!+ wysiwyg({
el: document.getElementById("root"),
#! engine: **ckeditorAdapter(CKEDITOR)**,
#! theme: **whiteTheme()**,
}).then(editor => {
#!+ // Установка контента
#!- editor.setContent("<br/><h1>Hi!</h1>");
#!+
// Курсор в начало строки
const firstChild = editor.getEditableContainer().firstChild;
#!- editor.setCursor(firstChild, "before");
#!- });
import {createTheme} from "./_theme";
export default createTheme(`
.editor { background: #fff; }
.editable {
font-size: 15px;
font-family: "San Francisco", Arial, sans-serif;
}
.toolbar { padding: 5px 10px; }
// И так далее
`);
А дальше... дальше меня всё больше и больше начала разбирать любопытство, что же там такое, как вставить BR и сделать текст жирным ;]
<div> Очень <em>полезный</em> и <strong>красивый</strong> текст! </div>
{
collapsed: false,
commonAncestorContainer: <div>
startContainer: <em>,
startOffset: 6,
endContainer: <strong>,
endOffset: 3,
}
Как видите, всё очень просто, работа с выделение имеет прерасное API, так почему не попробовать написать это самому.
Тут должно много букф, но я просто не осилю рассказать всё то, что узнал, но теперь точно могу сказать: «Да, я умею работать с DOM!».
<b style="color: black">x(-+)y</b> ↓ ↓ ↓ <b style="color: black">x</b> <b style="color: red">(-+)</b> <b style="color: black">y</b>
<span style="color: black">(x-</span> <span style="color: green">+y)</span> ↓ ↓ ↓ <span style="color: black">(x-+y)</span>
<div>Очень <em>полез(ный</em> <u>или</u></div> <strong>кра)сивый</strong> ↓ ↓ ↓ <div>Очень <em>полез**<b>(ный</b>**</em>**<b> <u>или</u></b>**</div> <strong>**<b>кра)</b>**сивый</strong>
// есть текст: foo -> <b>foo</b>
#! range.setStart(text, 0);
#! range.setEnd(text, 3);
#! range.surroundContents(document.createElement("b"));
#! // But...
#!+ console.log(rootContainer.childNodes);
#!- // [text, b, text] мдя...
// есть текст: foo -> <b>foo</b>
#! range.setStartBefore(text);
#! range.setEndAfter(text);
#!+ range.surroundContents(document.createElement("b"));
console.log(rootContainer.childNodes);
#!- // [b]
Это моя попытка написать API для работы с Range и на базе этого API создать WYSIWYG. Он не имеет UI и в изначальной конфигурации умеет только:
import Reviser, {CARET_AT_END} from "reviser";
#!import reviserExtensionBasePack from "reviser/extensions/base-pack";
#!+ const container = document.getElementById("root");
const reviser = new Reviser(container, {
#! extensions: reviserExtensionBasePack,
#! defaultCaretPosition: CARET_AT_END,
#!- });
#!+ reviser.content = "x<div></div>y <b>http</b>://mail.ru";
#!- reviser.focus();
import zwsFactory from "reviser/extensions/base-pack/zws/zws";
import linkFactory from "reviser/extensions/base-pack/link/link";
import enterFactory from "reviser/extensions/base-pack/enter/enter";
import backspaceFactory from "reviser/extensions/base-pack/backspace/backspace";
import baseStyleFactory from "reviser/extensions/base-pack/base-style/base-style";
export default [
zwsFactory(),
linkFactory(),
enterFactory(),
backspaceFactory(),
baseStyleFactory({bold: true, italic: true, underline: true}),
];
<div id="toolbar"> <button data-action="bold">B</button> <button data-action="italic">I</button> <button data-action="underline">U</button> </div>
import {listenForm} from "reviser/pen-box/dom";
import commandBold from "reviser/commands/bold";
import commandItalic from "reviser/commands/italic";
import commandUnderline from "reviser/commands/underline";
#!+ const actions = {
"bold": commandBold,
"italic": commandItalic,
"underline": commandUnderline,
#!- };
#!+ export function linkReviserAndUI(reviser, toolbar) {
#!+ listenForm(toolbar, "click", "data-action", (actionName, target, evt) => {
#! const range = reviser.getSelectionRange();
#! actions[actionName](range);
#! reviser.revertFocus(range);
#!- });
#!- };
import {Block} from "feast";
import Icon from "../blocks/icon/icon";
interface IButton {
icon?: string;
value: string;
}
export class Button extends Block<IButton> {
name: "button",
blocks: {Icon},
template: `
<div>
<Icon fn:if="attrs.icon" name="{attrs.icon}"/>
{attrs.value}
</div>
`,
}
import {Block} from "feast";
import Icon from "../blocks/icon/icon";
interface IButton {
icon?: string;
value: string;
}
export class Button extends Block<IButton> {
name: "button",
template: ({icon, value}, React) => (
<div>
{icon && <Icon name={icon}/>}
{value}
</div>
),
}
var MyCoolAction = Action.extend(/** @lends MyCoolAction# */{
#!+ // Подготовка данных
prepare: function (params, options) {
return data; // либо просто `params`, если готовить ничего не нужно
#!- },
#!+ // Выполняемая операция
operation: function (data, params, options) {
// ...
#!- },
#!+ // Откатываем изменения в случае ошибки при выполнении операции
rollbackOperation: function () {
// ...
#!- },
#!+ // Обратная операция
undoOperation: function (data, params, options) {
// ...
#!- },
});
var MyCoolAction = Action.extend(/** @lends MyCoolAction# */{
// Подготовка данных
prepare: function (params, options) {
return Folder.findOne(params.folder);
},
#!+ // Выполняемая операция
operation: function (folder, params, options) {
return RPC.call(Foler.url("myCoolAction"), {
flag: params.flag,
state: params.state,
});
#!- }
});
MyCoolAction.execute({
folder: Folder.INBOX,
flag: "foo",
state: false,
}).then(action => {
// ...
console.log(action.result);
// и
return action.undo();
});