From 071e8f8eeeeee58f96e760659892ba3b315fb410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B5=D0=BD=D0=B8=D1=81=20=D0=A1=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=BA=D0=BE=D0=B2?= Date: Fri, 19 May 2023 02:50:51 +0300 Subject: [PATCH] push demo extensions --- src/extensions/colorerDialog.ts | 220 ++++++++++++++++++++++++++++++++ src/extensions/emojiDialog.ts | 100 +++++++++++++++ src/extensions/presetList.ts | 114 +++++++++++++++++ 3 files changed, 434 insertions(+) create mode 100644 src/extensions/colorerDialog.ts create mode 100644 src/extensions/emojiDialog.ts create mode 100644 src/extensions/presetList.ts diff --git a/src/extensions/colorerDialog.ts b/src/extensions/colorerDialog.ts new file mode 100644 index 0000000..567461d --- /dev/null +++ b/src/extensions/colorerDialog.ts @@ -0,0 +1,220 @@ +import { el } from '../core/el.js'; +import WCWYSIWYG from '../wc-wysiwyg.js'; +//Extnesion translates +const t = { + ru: { + bg: 'Фон', + text: 'Текст', + bgColor: 'Цвет фона', + textColor: 'Цвет текста', + }, + en: { + bg: 'Background', + text: 'Text', + bgColor: 'Background color', + textColor: 'Text color', + } +}; +const _t = (key:string, lang = navigator.language):string => t[lang] ? t[lang][key] || "-" : t["en"][key]; +const Colors = { + red: ['FFEBEE', 'FFCDD2', 'EF9A9A', 'E57373', 'EF5350', 'F44336', 'E53935', 'D32F2F', 'C62828', 'B71C1C'], + pink: ['FCE4EC', 'F8BBD0', 'F48FB1', 'F06292', 'EC407A', 'E91E63', 'D81B60', 'C2185B', 'AD1457', '880E4F'], + purple: ['F3E5F5', 'E1BEE7', 'CE93D8', 'BA68C8', 'AB47BC', '9C27B0', '8E24AA', '7B1FA2', '6A1B9A', '4A148C'], + // deepPurple: ['EDE7F6', 'D1C4E9', 'B39DDB', '9575CD', '7E57C2', '673AB7', '5E35B1', '512DA8', '4527A0', '311B92'], + indigo: ['E8EAF6', 'C5CAE9', '9FA8DA', '7986CB', '5C6BC0', '3F51B5', '3949AB', '303F9F', '283593', '1A237E'], + blue: ['E3F2FD', 'BBDEFB', '90CAF9', '64B5F6', '42A5F5', '2196F3', '1E88E5', '1976D2', '1565C0', '0D47A1'], + // lightBlue: ['E1F5FE', 'B3E5FC', '81D4FA', '4FC3F7', '29B6F6', '03A9F4', '039BE5', '0288D1', '0277BD', '01579B'], + cyan: ['E0F7FA', 'B2EBF2', '80DEEA', '4DD0E1', '26C6DA', '00BCD4', '00ACC1', '0097A7', '00838F', '006064'], + teal: ['E0F2F1', 'B2DFDB', '80CBC4', '4DB6AC', '26A69A', '009688', '00897B', '00796B', '00695C', '004D40'], + green: ['E8F5E9', 'C8E6C9', 'A5D6A7', '81C784', '66BB6A', '4CAF50', '43A047', '388E3C', '2E7D32', '1B5E20'], + // lightGreen: ['F1F8E9', 'DCEDC8', 'C5E1A5', 'AED581', '9CCC65', '8BC34A', '7CB342', '689F38', '558B2F', '33691E'], + lime: ['FFFDE7', 'FFF9C4', 'FFF59D', 'FFF176', 'FFEE58', 'FFEB3B', 'FDD835', 'FBC02D', 'F9A825', 'F57F17'], + yellow: ['FFF8E1', 'FFECB3', 'FFE082', 'FFD54F', 'FFCA28', 'FFC107', 'FFB300', 'FFA000', 'FF8F00', 'FF6F00'], + // amber: ['FFF3E0', 'FFE0B2', 'FFCC80', 'FFB74D', 'FFA726', 'FF9800', 'FB8C00', 'F57C00', 'EF6C00', 'E65100'], + orange: ['FBE9E7', 'FFCCBC', 'FFAB91', 'FF8A65', 'FF7043', 'FF5722', 'F4511E', 'E64A19', 'D84315', 'BF360C'], + // deepOrange: ['EFEBE9', 'D7CCC8', 'BCAAA4', 'A1887F', '8D6E63', '795548', '6D4C41', '5D4037', '4E342E', '3E2723'], + brown: ['EFEBE9', 'D7CCC8', 'BCAAA4', 'A1887F', '8D6E63', '795548', '6D4C41', '5D4037', '4E342E', '3E2723'], + grey:['FAFAFA', 'F5F5F5', 'EEEEEE', 'E0E0E0', 'BDBDBD', '9E9E9E', '757575', '616161', '424242', '212121'], + blueGrey: ['ECEFF1', 'CFD8DC', 'B0BEC5', '90A4AE', '78909C', '607D8B', '546E7A', '455A64', '37474F', '263238'], +}; + +class WCWYSIWYGExtensionColorerDialog { + WCWYSIWYG:WCWYSIWYG + ColorerText:HTMLButtonElement + ColorerBackground:HTMLButtonElement + ColorerDialogLabel:HTMLLabelElement + ColorerDialogColorInput:HTMLInputElement + Dialog:HTMLDialogElement|null = null + ActiveColors:{ + text:string|null, + bg:string|null, + } + ColorerTarget: HTMLElement + constructor(WYSIWYG) { + this.WCWYSIWYG = WYSIWYG; + this.ActiveColors = new Proxy({ + text: null, + bg: null, + }, { + get(target,prop) { + return target[prop] + }, + set: (target, prop, value) => { + console.log('set color', value, prop); + this.ColorerDialogColorInput.value = value; + target[prop] = value; + if(prop == 'text') { + this.ColorerTarget.style.color = value; + this.ColorerText.setAttribute('style', `--colorer:${value}`); + } else { + this.ColorerTarget.style.backgroundColor = value; + this.ColorerBackground.setAttribute('style', `--colorer:${value}`); + } + return true + } + }); + console.log('colorer constructor', this); + } + connectedCallback() { + console.log('colorer connectedCallback'); + this.ColorerText = el('button', { + classList: ['wc-wysiwyg_btn', '-prevcolor'], + props: { + innerHTML: _t('text'), + onpointerup: () => this.#showDialog('text'), + }, + attrs: { + "data-hint": _t('textColor') + }, + }); + this.ColorerBackground = el('button', { + classList: ['wc-wysiwyg_btn', '-prevcolor'], + props: { + innerHTML: _t('bg'), + onpointerup: () => this.#showDialog('bg'), + }, + attrs: { + "data-hint": _t('bgColor') + }, + }); + this.ColorerDialogLabel = el('label', { + props: { + innerText: '' + } + }); + this.ColorerDialogColorInput = el('input', { + props: { + type: 'color', + value: '#fffccc' + } + }); + this.WCWYSIWYG.addEventListener('editprops', (event:CustomEvent) => { + if(event.detail.eventTarget) { + const target = event.detail.eventTarget; + console.log('check target', target); + + this.ColorerTarget = target; + this.ActiveColors.text = this.#rgbToHex(target.style.color); + this.ActiveColors.bg = this.#rgbToHex(target.style.backgroundColor); + + // this.ColorerText.setAttribute('data-color', value); + // this.ColorerBackground.setAttribute('data-color', value); + + } + }); + this.WCWYSIWYG.EditorActionsSection.append( + this.ColorerText, + this.ColorerBackground + ); + } + /** + * Converts a rgb string to hex string + * @param {String} rgbString - A string of rgb values (e.g. "255, 0, 128") + * @return {String} A hex string in the format of #RRGGBB (e.g. "#FF0080") + */ + #rgbToHex(rgbString) { + const arrRgb = rgbString.match(/([0-9]{1,3})/gm); + if(arrRgb === null || arrRgb.length !== 3) { + return null; + } + const r = +arrRgb[0]; + const g = +arrRgb[1]; + const b = +arrRgb[2]; + return "#" + (1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1); + } + #showDialog(colorType:string) { + console.log('show dialog'); + if(this.Dialog === null) { + this.Dialog = el('dialog', { + classList: ['wc-wysiwyg_dialog', '-modal', '-colors'], + props: { + innerHTML: (() => { + let html = ''; + for(let colorName in Colors) { + html += `
`; + for (let i = 0; i < Colors[colorName].length; i++) { + const hexColor = Colors[colorName][i]; + html += ``; + } + html += `
`; + } + return html + })(), + onpointerup: event => { + if(event.target.classList.contains('-color')) { + this.ActiveColors[colorType] = event.target.getAttribute('data-color'); + } + } + }, + + append: [ + el('fieldset', { + styles: { + background: 'var(--color-blue-gray-50)', + outline: '0', + display: 'flex', + border: '0', + marginTop: '10px', + borderRadius: '0.5em', + width: '100%', + }, + append: [ + + this.ColorerDialogLabel, + this.ColorerDialogColorInput, + el('button', { + classList: ['wc-wysiwyg_btn'], + props: { + type: 'button', + innerText: 'OK', + onpointerup: event => event.target.closest('dialog').close(), + } + }), + el('button', { + classList: ['wc-wysiwyg_btn'], + props: { + type: 'button', + innerText: 'Clear', + onpointerup: event => this.ColorerDialogColorInput.value = null + } + }), + ] + }) + ] + }); + this.WCWYSIWYG.EditorActionsSection.append(this.Dialog); + + // this.WCWYSIWYG.EditorInlineDialog.append(this.Dialog); + } + this.Dialog.onpointerup = event => { + const target = event.target as HTMLElement; + if(target.classList.contains('-color')) { + this.ActiveColors[colorType] = target.getAttribute('data-color'); + } + }; + this.ColorerDialogLabel.innerText = _t(colorType); + this.Dialog.showModal(); + } +} + +export default WCWYSIWYGExtensionColorerDialog; \ No newline at end of file diff --git a/src/extensions/emojiDialog.ts b/src/extensions/emojiDialog.ts new file mode 100644 index 0000000..33590c4 --- /dev/null +++ b/src/extensions/emojiDialog.ts @@ -0,0 +1,100 @@ +import { el } from '../core/el.js'; + +//Extnesion translates +const t = { + ru: { + emoji: 'Смайлики', + action: 'Нажмите чтобы скопировать', + emoticons: 'Эмоции', + dingbats: 'Значки', + map: 'Транспорт и карты', + additional: 'Дополнительные', + }, + en: { + emoji: 'Emoji', + action: 'Click to copy', + emoticons: 'Emoticons', + dingbats: 'Dsingbats', + map: 'Transport and map', + additional: 'Other additional', + } +}; +const _t = (key:string, lang = navigator.language):string => t[lang] ? t[lang][key] || "-" : t["en"][key]; +/** + * Emoji dialog with list of emoji buttons + */ +class WCWYSIWYGExtensionEmojiDialog { + Dialog: HTMLDialogElement|null = null + WCWYSIWYG:any + constructor(WCWYSIWYG:any) { + this.WCWYSIWYG = WCWYSIWYG; + } + connectedCallback() { + this.WCWYSIWYG.EditorActionsSection.append(el('button', { + classList: ['wc-wysiwyg_btn'], + props: { + innerHTML: '😃', + onpointerup: () => this.#showDialog(), + }, + attrs: { + "data-hint": _t('emoji') + } + })); + } + #showDialog() { + //Check if first show dialog + if(this.Dialog === null) { + const emojiRanges = { + emoticons: [128513, 128591], + dingbats: [9986,10160], + map: [128640,128704], + additional: [127757,128359], + }; + this.Dialog = el('dialog', { + classList: ['wc-wysiwyg_dialog', '-modal'], + styles: { + maxWidth: '90vw', + }, + props: { + onpointerup: event => { + const target = event.target; + if(target.tagName === 'BUTTON') { + const emojiCode = target.innerHTML; + const data = [ + new ClipboardItem({ "text/html": new Blob([emojiCode], { type:"text/html" }) }), + ]; + navigator.clipboard.write(data).then( + () => { + console.log('writed'); + this.Dialog.close(); + }, + (err) => { throw new Error("WC-WYSIWYG: Copy emoji to clipboard failed", err); } + ); + } + }, + innerHTML: (() => { + let html = ''; + let showFirst = false; + for (let range in emojiRanges) { + html += `
${_t(range)}`; + for (let emojiCode = emojiRanges[range][0]; emojiCode < emojiRanges[range][1]; emojiCode++) { + html += ''; + } + html += '
'; + showFirst = true; + } + return html; + })() + } + }); + this.WCWYSIWYG.append(this.Dialog); + } + this.Dialog.showModal(); + } + +} + +//Put extension in global view +(window._WCWYSIWYG !== undefined) ? window._WCWYSIWYG.extensions.push(WCWYSIWYGExtensionEmojiDialog) : window._WCWYSIWYG = {extensions:[WCWYSIWYGExtensionEmojiDialog]}; + +export default WCWYSIWYGExtensionEmojiDialog; \ No newline at end of file diff --git a/src/extensions/presetList.ts b/src/extensions/presetList.ts new file mode 100644 index 0000000..72299f0 --- /dev/null +++ b/src/extensions/presetList.ts @@ -0,0 +1,114 @@ +import { el } from '../core/el.js'; + +/** + * Presets format + */ +interface WCWYSIWYGPreset { + /** + * Preset name on drop down + */ + name:string, + /** + * tag name + */ + tag:string, + /** + * will set to HTMLElement.className property + */ + class?:string, + /** + * will set to HTMLElement.style property + */ + style?:string +} +class WCWYSIWYGExtensionPresetList { + PresetList: HTMLElement|null = null + PresetBtn:HTMLElement + Presets: Array = [] + WCWYSIWYG:any + constructor(WCWYSIWYG:any) { + this.WCWYSIWYG = WCWYSIWYG; + this.Presets = JSON.parse(WCWYSIWYG.getAttribute('data-preset-list')); + this.PresetBtn = el('button', { + classList: ['wc-wysiwyg_btn'], + props: { + innerHTML: 'Оформление', + onpointerup: () => this.#showPresetList(), + }, + }); + } + connectedCallback() { + if(this.Presets !== null) { + this.WCWYSIWYG.EditorActionsSection.append(this.PresetBtn); + } + } + #showPresetList() { + console.log('show dialog'); + if(this.PresetList === null) { + const section = el("section", { }); + //Make presets blocks + this.Presets.forEach(preset => { + const presetEl = el(preset.tag, { + props: { + innerHTML: `${preset.name}`, + className: preset.class || null, + style: preset.style || null, + } + }); + section.append(el('button', { + props: { + type: 'button', + style: "cursor:pointer;border: 1px solid rgba(0,0,0,0.5); margin-bottom:5px; border-radius:5px; background: transparent; display:block;", + onclick: e => this.#makePreset(preset), + }, + append: [presetEl], + })); + }); + + //Close dialog button + this.PresetList = el('div', { + // classList: ['wc-wysiwyg_ec'], + append: [ + section, + el('button', { + classList: ['wc-wysiwyg_btn'], + props: { + type: 'button', + innerText: 'Закрыть', + onclick: event => this.PresetList.style.display = 'none' + } + }) + ] + }); + // this.WCWYSIWYG.EditorActionsSection.append(this.PresetList); + this.WCWYSIWYG.EditorActionsSection.insertAdjacentElement('beforeend', this.PresetList); + // this.PresetBtn.append(this.PresetList); + } + // this.PresetList.show(); + this.PresetList.style.display = 'block'; + } + #makePreset(preset:WCWYSIWYGPreset) { + const editorSelection = this.WCWYSIWYG.getSelection(); + + console.log('make preset', preset, editorSelection); + let tagNode = el(preset.tag, {}); + if(preset.style) { + tagNode.setAttribute('style', preset.style); + } + if(preset.class) { + tagNode.className = preset.class; + } + if (editorSelection.selection !== null && editorSelection.selection.rangeCount && editorSelection.text !== null) { + const range = editorSelection.selection.getRangeAt(0).cloneRange(); + range.surroundContents(tagNode); + editorSelection.selection.removeAllRanges(); + editorSelection.selection.addRange(range); + tagNode.innerText = editorSelection.text; + this.WCWYSIWYG.updateContent(); + // this.WCWYSIWY.#checkEditProps({target:tagNode, stopPropagation: () => false}); + } + } +} +//Put extension in global view +(window._WCWYSIWYG !== undefined) ? window._WCWYSIWYG.extensions.push(WCWYSIWYGExtensionPresetList) : window._WCWYSIWYG = {extensions:[WCWYSIWYGExtensionPresetList]}; +export default WCWYSIWYGExtensionPresetList; \ No newline at end of file