Произносится [ˈ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(); });