kempo-ui 0.4.4 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Chat.js +201 -0
- package/dist/components/CodeEditor.js +21 -2
- package/dist/components/Dropdown.js +1 -1
- package/dist/components/HtmlEditor.js +42 -3
- package/dist/components/MarkdownEditor.js +173 -0
- package/dist/components/Resize.js +12 -2
- package/dist/components/TextToSpeech.js +4 -4
- package/dist/components/VoiceSelector.js +49 -0
- package/dist/components/markdownEditorControls/Bold.js +11 -0
- package/dist/components/markdownEditorControls/BulletList.js +11 -0
- package/dist/components/markdownEditorControls/Code.js +11 -0
- package/dist/components/markdownEditorControls/FormatBlock.js +30 -0
- package/dist/components/markdownEditorControls/Heading.js +11 -0
- package/dist/components/markdownEditorControls/Image.js +93 -0
- package/dist/components/markdownEditorControls/Italic.js +11 -0
- package/dist/components/markdownEditorControls/Link.js +11 -0
- package/dist/components/markdownEditorControls/MarkdownEditorControl.js +39 -0
- package/dist/components/markdownEditorControls/Menu.js +30 -0
- package/dist/components/markdownEditorControls/NumberedList.js +11 -0
- package/dist/components/markdownEditorControls/Quote.js +11 -0
- package/dist/components/markdownEditorControls/SpeechToText.js +16 -0
- package/dist/components/markdownEditorControls/Strikethrough.js +11 -0
- package/dist/components/markdownEditorControls/Table.js +80 -0
- package/dist/utils/marked.esm.js +1 -0
- package/dist/utils/renderMarkdown.js +1 -0
- package/dist/utils/sanitizeHtml.js +1 -0
- package/dist/utils/voice.js +1 -0
- package/docs/components/accordion.html +19 -0
- package/docs/components/aside.html +19 -0
- package/docs/components/calendar.html +19 -0
- package/docs/components/card.html +19 -0
- package/docs/components/chat.html +715 -0
- package/docs/components/code-editor.html +55 -0
- package/docs/components/color-picker.html +19 -0
- package/docs/components/combobox.html +19 -0
- package/docs/components/content-slider.html +19 -0
- package/docs/components/context.html +19 -0
- package/docs/components/dialog.html +19 -0
- package/docs/components/dropdown.html +19 -0
- package/docs/components/filter-list.html +19 -0
- package/docs/components/focus-capture.html +19 -0
- package/docs/components/html-editor.html +60 -0
- package/docs/components/hybrid-component.html +19 -0
- package/docs/components/icon.html +19 -0
- package/docs/components/import.html +19 -0
- package/docs/components/light-component.html +19 -0
- package/docs/components/markdown-editor.html +924 -0
- package/docs/components/nav-spacer.html +19 -0
- package/docs/components/nav.html +19 -0
- package/docs/components/photo-viewer.html +19 -0
- package/docs/components/progress.html +19 -0
- package/docs/components/resize.html +22 -0
- package/docs/components/rich-textarea.html +650 -0
- package/docs/components/shadow-component.html +19 -0
- package/docs/components/show-more.html +19 -0
- package/docs/components/slider.html +19 -0
- package/docs/components/sortable.html +19 -0
- package/docs/components/speech-to-text.html +19 -0
- package/docs/components/spinner.html +19 -0
- package/docs/components/split.html +19 -0
- package/docs/components/table.html +19 -0
- package/docs/components/tableControls.html +19 -0
- package/docs/components/tableCustomFields.html +19 -0
- package/docs/components/tableFetchRecords.html +19 -0
- package/docs/components/tableFieldSortHide.html +19 -0
- package/docs/components/tablePagination.html +19 -0
- package/docs/components/tablePlaceholder.html +19 -0
- package/docs/components/tableRecordEditing.html +19 -0
- package/docs/components/tableRecordFiltering.html +19 -0
- package/docs/components/tableRecordHiding.html +19 -0
- package/docs/components/tableRecordSearching.html +19 -0
- package/docs/components/tableRecordSelection.html +19 -0
- package/docs/components/tableRowControls.html +19 -0
- package/docs/components/tableServerSync.html +19 -0
- package/docs/components/tableSorting.html +19 -0
- package/docs/components/tabs.html +19 -0
- package/docs/components/tags.html +19 -0
- package/docs/components/text-to-speech.html +63 -1
- package/docs/components/theme-select.html +19 -0
- package/docs/components/theme-switcher.html +19 -0
- package/docs/components/time.html +19 -0
- package/docs/components/timestamp.html +19 -0
- package/docs/components/toast.html +19 -0
- package/docs/components/toggle.html +19 -0
- package/docs/components/tree.html +19 -0
- package/docs/components/voice-selector.html +610 -0
- package/docs/icons/done_all.svg +1 -0
- package/docs/icons/format_quote.svg +1 -0
- package/docs/icons/format_strikethrough.svg +1 -0
- package/docs/icons/send.svg +1 -0
- package/docs/icons/text_fields.svg +1 -0
- package/docs/index.html +49 -0
- package/docs/src/components/Chat.js +201 -0
- package/docs/src/components/CodeEditor.js +21 -2
- package/docs/src/components/Dropdown.js +1 -1
- package/docs/src/components/HtmlEditor.js +42 -3
- package/docs/src/components/MarkdownEditor.js +173 -0
- package/docs/src/components/Resize.js +12 -2
- package/docs/src/components/TextToSpeech.js +4 -4
- package/docs/src/components/VoiceSelector.js +49 -0
- package/docs/src/components/markdownEditorControls/Bold.js +11 -0
- package/docs/src/components/markdownEditorControls/BulletList.js +11 -0
- package/docs/src/components/markdownEditorControls/Code.js +11 -0
- package/docs/src/components/markdownEditorControls/FormatBlock.js +30 -0
- package/docs/src/components/markdownEditorControls/Heading.js +11 -0
- package/docs/src/components/markdownEditorControls/Image.js +93 -0
- package/docs/src/components/markdownEditorControls/Italic.js +11 -0
- package/docs/src/components/markdownEditorControls/Link.js +11 -0
- package/docs/src/components/markdownEditorControls/MarkdownEditorControl.js +39 -0
- package/docs/src/components/markdownEditorControls/Menu.js +30 -0
- package/docs/src/components/markdownEditorControls/NumberedList.js +11 -0
- package/docs/src/components/markdownEditorControls/Quote.js +11 -0
- package/docs/src/components/markdownEditorControls/SpeechToText.js +16 -0
- package/docs/src/components/markdownEditorControls/Strikethrough.js +11 -0
- package/docs/src/components/markdownEditorControls/Table.js +80 -0
- package/docs/src/utils/marked.esm.js +1 -0
- package/docs/src/utils/renderMarkdown.js +1 -0
- package/docs/src/utils/sanitizeHtml.js +1 -0
- package/docs/src/utils/voice.js +1 -0
- package/docs/utils/context.html +19 -0
- package/docs/utils/cookie.html +19 -0
- package/docs/utils/debounce.html +19 -0
- package/docs/utils/drag.html +19 -0
- package/docs/utils/elevation.html +23 -10
- package/docs/utils/formatTimestamp.html +19 -0
- package/docs/utils/object.html +19 -0
- package/docs/utils/propConverters.html +19 -0
- package/docs/utils/sanitizeHtml.html +555 -0
- package/docs/utils/string.html +19 -0
- package/docs/utils/theme.html +19 -0
- package/docs/utils/toTitleCase.html +19 -0
- package/docs/utils/type.html +19 -0
- package/docs/utils/voice.html +548 -0
- package/docs/utils/wait.html +19 -0
- package/docs-src/components/chat.page.html +230 -0
- package/docs-src/components/code-editor.page.html +36 -0
- package/docs-src/components/html-editor.page.html +41 -0
- package/docs-src/components/markdown-editor.page.html +439 -0
- package/docs-src/components/resize.page.html +3 -0
- package/docs-src/components/text-to-speech.page.html +44 -1
- package/docs-src/components/voice-selector.page.html +125 -0
- package/docs-src/index.page.html +30 -0
- package/docs-src/nav.fragment.html +19 -0
- package/docs-src/utils/elevation.page.html +4 -10
- package/docs-src/utils/sanitizeHtml.page.html +73 -0
- package/docs-src/utils/voice.page.html +66 -0
- package/icons/done_all.svg +1 -0
- package/icons/format_quote.svg +1 -0
- package/icons/format_strikethrough.svg +1 -0
- package/icons/send.svg +1 -0
- package/icons/text_fields.svg +1 -0
- package/llms.txt +7 -2
- package/package.json +3 -2
- package/src/components/Chat.js +435 -0
- package/src/components/CodeEditor.js +58 -2
- package/src/components/Dropdown.js +1 -1
- package/src/components/HtmlEditor.js +84 -4
- package/src/components/MarkdownEditor.js +654 -0
- package/src/components/Resize.js +17 -1
- package/src/components/TextToSpeech.js +13 -2
- package/src/components/VoiceSelector.js +271 -0
- package/src/components/markdownEditorControls/Bold.js +30 -0
- package/src/components/markdownEditorControls/BulletList.js +30 -0
- package/src/components/markdownEditorControls/Code.js +39 -0
- package/src/components/markdownEditorControls/FormatBlock.js +102 -0
- package/src/components/markdownEditorControls/Heading.js +45 -0
- package/src/components/markdownEditorControls/Image.js +250 -0
- package/src/components/markdownEditorControls/Italic.js +30 -0
- package/src/components/markdownEditorControls/Link.js +74 -0
- package/src/components/markdownEditorControls/MarkdownEditorControl.js +158 -0
- package/src/components/markdownEditorControls/Menu.js +86 -0
- package/src/components/markdownEditorControls/NumberedList.js +69 -0
- package/src/components/markdownEditorControls/Quote.js +30 -0
- package/src/components/markdownEditorControls/SpeechToText.js +65 -0
- package/src/components/markdownEditorControls/Strikethrough.js +35 -0
- package/src/components/markdownEditorControls/Table.js +164 -0
- package/src/utils/marked.esm.js +77 -0
- package/src/utils/renderMarkdown.js +29 -0
- package/src/utils/sanitizeHtml.js +137 -0
- package/src/utils/voice.js +103 -0
- package/tests/components/Chat.browser-test.js +540 -0
- package/tests/components/TextToSpeech.browser-test.js +150 -1
- package/tests/components/VoiceSelector.browser-test.js +374 -0
- package/tests/utils/sanitizeHtml.browser-test.js +179 -0
- package/tests/utils/voice.browser-test.js +128 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{html as e,css as t}from"../lit-all.min.js";import i from"./ShadowComponent.js";import o from"../utils/formatCode.js";import l from"../utils/debounce.js";import{getCalculatedTheme as r,subscribeToTheme as s}from"../utils/theme.js";import a from"./Dialog.js";export default class c extends i{static formAssociated=!0;static properties={name:{type:String,reflect:!0},value:{type:String,reflect:!0},selection:{type:Object,state:!0},mode:{type:String,reflect:!0},controls:{type:String,reflect:!0},lexicalSrc:{type:String,attribute:"lexical-src"},monacoSrc:{type:String,attribute:"monaco-src"},nodes:{type:String},hasTopToolbar:{type:Boolean,state:!0},hasBottomToolbar:{type:Boolean,state:!0},fullscreen:{type:Boolean,reflect:!0}};constructor(){super(),this.internals=this.attachInternals(),this.name="",this.value="",this.selection=null,this.cursor=null,this.mode="visual",this.controls="",this.controlsLoaded=!1,this.lexicalSrc="",this.monacoSrc="",this.nodes="",this.hasTopToolbar=!1,this.hasBottomToolbar=!1,this.skipValueSync=!1,this.lexicalValueSync=!1,this.savedSelection=null,this.lexicalEditor=null,this.monacoEditor=null,this.editorTheme="auto",this.wordWrap=!0,this.minimapEnabled=!1,this.fontSize=14,this.fullscreen=!1,this.lx={},this.debouncedSyncValue=l(()=>this.syncValueFromLexical(),300)}connectedCallback(){super.connectedCallback(),this.hasAttribute("value")&&(this.value=this.getAttribute("value")),this.slotObserver=new MutationObserver(()=>this.updateToolbarVisibility()),this.slotObserver.observe(this,{childList:!0,subtree:!0}),this.updateToolbarVisibility()}disconnectedCallback(){super.disconnectedCallback(),this.slotObserver?.disconnect(),this.cleanupFns?.forEach(e=>e?.()),this.monacoEditor?.dispose(),this.unsubscribeTheme?.(),this.fullscreen&&this.exitFullscreen()}updateToolbarVisibility(){const e=new Set(Array.from(this.children).map(e=>e.getAttribute("slot"))),t=this.constructor.controlSets[this.controls]??null;this.hasTopToolbar=!(!t?.topLeft&&!t?.topRight)||["toolbar-top","toolbar-top-left","toolbar-top-right"].some(t=>e.has(t)),this.hasBottomToolbar=!(!t?.bottomLeft&&!t?.bottomRight)||["toolbar-bottom","toolbar-bottom-left","toolbar-bottom-right"].some(t=>e.has(t))}updated(e){if(super.updated(e),e.has("controls")&&(this.updateToolbarVisibility(),this.controls&&"none"!==this.controls&&this.loadControls()),e.has("value")&&!this.skipValueSync&&(this.lexicalValueSync?this.lexicalValueSync=!1:this.lexicalEditor&&"visual"===this.mode&&!this.isVisualCompatible(this.value)?(this.skipLexicalExport=!0,this.mode="code"):this.syncContentToEditors(),this.updateFormValue(),this.dispatchEvent(new CustomEvent("change",{detail:{value:this.value},bubbles:!0}))),e.has("mode")){const t=window.scrollY;this.handleModeSwitch(e.get("mode")),this.dispatchEvent(new CustomEvent("mode-changed",{detail:{mode:this.mode},bubbles:!0})),requestAnimationFrame(()=>window.scrollTo(0,t))}e.has("fullscreen")&&requestAnimationFrame(()=>this.monacoEditor?.layout())}async firstUpdated(){this.lexicalContainer=this.shadowRoot.querySelector(".lexical-editor"),this.monacoContainer=this.shadowRoot.querySelector(".monaco-editor-container"),await this.initLexical(),this.dispatchEvent(new CustomEvent("ready",{detail:{value:this.value},bubbles:!0}))}async loadControls(){if(this.controlsLoaded)return;this.controlsLoaded=!0;const e=new URL("./htmlEditorControls/",import.meta.url).href;await Promise.all([import(`${e}Bold.js`),import(`${e}Italic.js`),import(`${e}Underline.js`),import(`${e}Strikethrough.js`),import(`${e}InlineCode.js`),import(`${e}DropdownControl.js`),import(`${e}FormatBlock.js`),import(`${e}CodeBlock.js`),import(`${e}BulletList.js`),import(`${e}NumberList.js`),import(`${e}AlignLeft.js`),import(`${e}AlignCenter.js`),import(`${e}AlignRight.js`),import(`${e}AlignJustify.js`),import(`${e}TextColor.js`),import(`${e}TextBackgroundColor.js`),import(`${e}ClearFormatting.js`),import(`${e}CreateLink.js`),import(`${e}InsertTable.js`),import(`${e}ControlGroup.js`),import(`${e}ControlSpacer.js`),import(`${e}Mode.js`),import(`${e}WordCount.js`),import(`${e}CharacterCount.js`)]);const t=new URL("./codeEditorControls/",import.meta.url).href;await Promise.all([import(`${t}FormatCode.js`),import(`${t}CopyCode.js`),import(`${t}Undo.js`),import(`${t}Redo.js`),import(`${t}WordWrap.js`),import(`${t}Minimap.js`),import(`${t}FindReplace.js`),import(`${t}FontSize.js`),import(`${t}FoldAll.js`),import(`${t}EditorTheme.js`),import(`${t}Fullscreen.js`),import(`${t}ControlGroup.js`)]),this.requestUpdate()}async loadNodeModules(){if(!this.nodes?.trim())return[];const e=new URL("./htmlEditorNodes/",import.meta.url).href;return(await Promise.all(this.nodes.split(",").map(e=>e.trim()).filter(Boolean).map(t=>import(`${e}${t}.js`)))).map(e=>e.default?.lexicalNode).filter(Boolean)}async loadLexicalModules(){const e=this.lexicalSrc||window.kempo?.lexicalUrl||"https://esm.sh",t=t=>((e,t)=>`${e}/${t}@0.43.0`)(e,t),[i,o,l,r,s,a,c,n,h]=await Promise.all([import(t("lexical")),import(t("@lexical/rich-text")),import(t("@lexical/html")),import(t("@lexical/history")),import(t("@lexical/list")),import(t("@lexical/link")),import(t("@lexical/selection")),import(t("@lexical/table")),import(t("@lexical/code"))]);this.lx={lexical:i,richText:o,lexicalHtml:l,history:r,list:s,link:a,selection:c,table:n,code:h},this.StyledTextNode=class extends i.TextNode{static getType(){return"styled-text"}static clone(e){return new this(e.__text,e.__key)}static importDOM(){return{span:()=>({conversion:e=>{const t=e.getAttribute("style");if(!t)return null;const o=i.$createTextNode(e.textContent);return o.setStyle(t),{node:o}},priority:1})}}static importJSON(e){return i.$createTextNode(e.text)}exportJSON(){return{...super.exportJSON(),type:"styled-text"}}}}async initLexical(){await this.loadLexicalModules(),this.customNodes=await this.loadNodeModules(),this.nodeCompatCheckers=this.customNodes.filter(e=>"function"==typeof e.isVisualCompatible).map(e=>e.isVisualCompatible),this.nodePreprocessors=this.customNodes.filter(e=>"function"==typeof e.preprocessHtml).map(e=>e.preprocessHtml);const{lexical:e,richText:t,history:i,list:o,link:l,table:r,code:s}=this.lx,a={namespace:"KempoHtmlEditor",theme:{paragraph:"k-editor-p",heading:{h1:"k-editor-h1",h2:"k-editor-h2",h3:"k-editor-h3",h4:"k-editor-h4",h5:"k-editor-h5",h6:"k-editor-h6"},text:{underline:"td-u",strikethrough:"td-lt"},list:{ul:"k-editor-ul",ol:"k-editor-ol",listitem:"k-editor-li"},link:"k-editor-link",quote:"k-editor-quote",code:"k-editor-code-block",codeHighlight:{},table:"k-editor-table",tableCell:"k-editor-table-cell",tableCellHeader:"k-editor-table-cell-header"},nodes:[t.HeadingNode,t.QuoteNode,o.ListNode,o.ListItemNode,l.LinkNode,r.TableNode,r.TableCellNode,r.TableRowNode,s.CodeNode,s.CodeHighlightNode,this.StyledTextNode,...this.customNodes],onError:console.error,editorState:null};this.lexicalEditor=e.createEditor(a),this.lexicalEditor.setRootElement(this.lexicalContainer),this.lexicalEditor._window=new Proxy(window,{get:(e,t)=>{if("getSelection"===t)return()=>this.shadowRoot.getSelection();const i=Reflect.get(e,t);return"function"==typeof i?i.bind(e):i}}),this.cleanupFns=[t.registerRichText(this.lexicalEditor),i.registerHistory(this.lexicalEditor,i.createEmptyHistoryState(),300)],o.registerList&&this.cleanupFns.push(o.registerList(this.lexicalEditor)),r.registerTable&&this.cleanupFns.push(r.registerTable(this.lexicalEditor)),s.registerCodeHighlighting&&this.cleanupFns.push(s.registerCodeHighlighting(this.lexicalEditor)),l.registerLink&&this.cleanupFns.push(l.registerLink(this.lexicalEditor,{validateUrl:e=>{try{return new URL(e),!0}catch{return!1}}})),this.value&&(this.isVisualCompatible(this.value)?this.importHtmlToLexical(this.value):(this.skipLexicalExport=!0,this.mode="code")),this.lexicalEditor.registerUpdateListener(({dirtyElements:e,dirtyLeaves:t})=>{0===e.size&&0===t.size||(this.debouncedSyncValue(),this.dispatchEvent(new CustomEvent("input",{detail:{value:this.exportHtmlFromLexical()},bubbles:!0})))}),this.lexicalEditor.registerCommand(e.SELECTION_CHANGE_COMMAND,()=>(this.updateSelection(),!1),e.COMMAND_PRIORITY_LOW)}async initMonaco(){if(!this.monacoEditor){if(this.monacoInitPromise)return this.monacoInitPromise;this.monacoInitPromise=this._initMonaco(),await this.monacoInitPromise,this.monacoInitPromise=null}}async _initMonaco(){const e=this.monacoSrc||window.kempo?.monacoUrl||"https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min";await new Promise((t,i)=>{if(window.monaco)return void t();if(window.require?.defined?.("vs/editor/editor.main"))return void t();const o=document.querySelector(`script[src="${e}/vs/loader.js"]`);if(o)return void o.addEventListener("load",()=>{window.require.config({paths:{vs:`${e}/vs`}}),window.require(["vs/editor/editor.main"],()=>t(),i)});const l=document.createElement("script");l.src=`${e}/vs/loader.js`,l.onload=()=>{window.require.config({paths:{vs:`${e}/vs`}}),window.require(["vs/editor/editor.main"],()=>t(),i)},l.onerror=i,document.head.appendChild(l)}),this.monacoEditor=window.monaco.editor.create(this.monacoContainer,{value:o(this.value),language:"html",theme:this.resolveMonacoTheme(),minimap:{enabled:this.minimapEnabled},wordWrap:this.wordWrap?"on":"off",fontSize:this.fontSize,scrollBeyondLastLine:!1,automaticLayout:!0,tabSize:2,padding:{top:8}});const t=document.querySelector('link[href*="monaco"][href*="editor.main.css"]');if(t){const e=document.createElement("link");e.rel="stylesheet",e.href=t.href,this.shadowRoot.appendChild(e)}this.unsubscribeTheme=s(()=>{this.monacoEditor&&"auto"===this.editorTheme&&window.monaco.editor.setTheme(this.resolveMonacoTheme())}),this.monacoEditor.onDidChangeModelContent(()=>{this.skipValueSync=!0,this.value=this.monacoEditor.getValue(),this.skipValueSync=!1,this.updateFormValue(),this.dispatchEvent(new CustomEvent("change",{detail:{value:this.value},bubbles:!0})),this.dispatchEvent(new CustomEvent("input",{detail:{value:this.value},bubbles:!0}))})}importHtmlToLexical(e){if(!this.lexicalEditor||!this.lx.lexicalHtml)return;const{lexical:t,lexicalHtml:i}=this.lx,o=(this.nodePreprocessors||[]).reduce((e,t)=>t(e),e);this.lexicalEditor.update(()=>{if(t.$getRoot().clear(),!o?.trim())return;const e=(new DOMParser).parseFromString(o,"text/html"),l=i.$generateNodesFromDOM(this.lexicalEditor,e);l.length>0&&t.$insertNodes(l)},{discrete:!0})}exportHtmlFromLexical(){if(!this.lexicalEditor||!this.lx.lexicalHtml)return this.value;let e="";return this.lexicalEditor.getEditorState().read(()=>{e=this.lx.lexicalHtml.$generateHtmlFromNodes(this.lexicalEditor,null)}),this.cleanExportedHtml(e)}isVisualCompatible(e){if(!e?.trim())return!0;const t=new Set(["script","style","meta","link","head","iframe","object","embed","canvas","video","audio","form","input","button","select","textarea","fieldset","label","noscript","template","slot","svg","math"]),i=this.nodeCompatCheckers||[],o=(new DOMParser).parseFromString(e,"text/html"),l=document.createTreeWalker(o.body,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_COMMENT);let r;for(;r=l.nextNode();)if(!i.some(e=>e(r))){if(r.nodeType===Node.COMMENT_NODE)return!1;if(t.has(r.tagName?.toLowerCase()))return!1}return!0}cleanExportedHtml(e){const t=(new DOMParser).parseFromString(e,"text/html");return t.body.querySelectorAll("[class]").forEach(e=>{const t=Array.from(e.classList).filter(e=>!e.startsWith("k-editor-")&&"td-u"!==e&&"td-lt"!==e);0===t.length?e.removeAttribute("class"):e.className=t.join(" ")}),t.body.querySelectorAll("[style]").forEach(e=>{const t=e.style.cssText.replace(/white-space:\s*pre-wrap;?\s*/g,"").trim();t?e.style.cssText=t:e.removeAttribute("style")}),t.body.querySelectorAll("span:not([class]):not([style]):not([id])").forEach(e=>{e.attributes.length||e.replaceWith(...e.childNodes)}),t.body.querySelectorAll("b > strong, i > em, b > b, strong > strong, i > i, em > em").forEach(e=>{e.replaceWith(...e.childNodes)}),t.body.querySelectorAll("pre[data-highlight-language], code[data-highlight-language]").forEach(e=>{e.removeAttribute("data-highlight-language"),e.removeAttribute("data-language")}),t.body.innerHTML}syncValueFromLexical(){this.lexicalEditor&&(this.lexicalValueSync=!0,this.value=this.exportHtmlFromLexical(),this.updateFormValue())}syncContentToEditors(){"visual"===this.mode&&this.lexicalEditor&&this.importHtmlToLexical(this.value)}async handleModeSwitch(e){"code"===this.mode?(this.lexicalEditor&&!this.skipLexicalExport&&(this.value=this.exportHtmlFromLexical()),this.skipLexicalExport=!1,await this.initMonaco(),this.monacoEditor&&(this.monacoEditor.setValue(o(this.value)),this.monacoEditor.layout())):"visual"===this.mode&&(this.monacoEditor&&(this.value=this.monacoEditor.getValue()),this.lexicalEditor&&this.importHtmlToLexical(this.value)),this.requestUpdate()}updateFormValue(){this.internals.setFormValue(this.getValue())}formResetCallback(){this.value=""}formStateRestoreCallback(e){this.value=e}updateSelection=()=>{if("visual"!==this.mode||!this.lexicalEditor)return void(this.selection=null);const{lexical:e}=this.lx;this.lexicalEditor.getEditorState().read(()=>{const t=e.$getSelection();e.$isRangeSelection(t)&&!t.isCollapsed()?this.selection={text:t.getTextContent(),collapsed:!1}:(this.selection=null,this.cursor=t?{anchor:t.anchor,focus:t.focus}:null)})};setMode(e){return["visual","code"].includes(e)?"visual"!==e||this.isVisualCompatible(this.getValue())?(this.mode=e,this):(a.confirm("This html contains code that is not compatible with the visual editor, the incompatible code will be lost",t=>{t&&(this.mode=e)},{title:"Warning",confirmText:"Change Anyways"}),this):this}toggleMode(){return this.setMode("visual"===this.mode?"code":"visual")}getValue(){if("visual"===this.mode&&this.lexicalEditor)this.skipValueSync=!0,this.value=this.exportHtmlFromLexical(),this.skipValueSync=!1;else if("code"===this.mode&&this.monacoEditor)return this.monacoEditor.getValue();return this.value}setValue(e){return this.lexicalEditor&&"visual"===this.mode&&!this.isVisualCompatible(e)?(this.value=e,this.skipLexicalExport=!0,this.mode="code",this.updateFormValue(),this):(this.skipValueSync=!0,this.value=e,"visual"===this.mode?this.syncContentToEditors():"code"===this.mode&&this.monacoEditor&&this.monacoEditor.setValue(o(e)),this.updateFormValue(),this.skipValueSync=!1,this)}clear(){return this.setValue("")}bold(){return this.lexicalFormat("bold"),this}italic(){return this.lexicalFormat("italic"),this}underline(){return this.lexicalFormat("underline"),this}strikethrough(){return this.lexicalFormat("strikethrough"),this}inlineCode(){return this.lexicalFormat("code"),this}orderedList(){return"visual"===this.mode&&this.lexicalEditor?(this.lexicalEditor.update(()=>{this.lx.list.$insertList("number")},{discrete:!0}),this):this}unorderedList(){return"visual"===this.mode&&this.lexicalEditor?(this.lexicalEditor.update(()=>{this.lx.list.$insertList("bullet")},{discrete:!0}),this):this}alignLeft(){return this.lexicalFormatElement("left"),this}alignCenter(){return this.lexicalFormatElement("center"),this}alignRight(){return this.lexicalFormatElement("right"),this}alignJustify(){return this.lexicalFormatElement("justify"),this}setTextColor(e){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:t,selection:i}=this.lx;return this.lexicalEditor.update(()=>{const o=t.$getSelection();t.$isRangeSelection(o)&&i.$patchStyleText(o,{color:e})},{discrete:!0}),this}removeTextColor(){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:e,selection:t}=this.lx;return this.lexicalEditor.update(()=>{const i=e.$getSelection();e.$isRangeSelection(i)&&t.$patchStyleText(i,{color:null})},{discrete:!0}),this}setTextBackgroundColor(e){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:t,selection:i}=this.lx;return this.lexicalEditor.update(()=>{const o=t.$getSelection();t.$isRangeSelection(o)&&i.$patchStyleText(o,{"background-color":e})},{discrete:!0}),this}removeTextBackgroundColor(){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:e,selection:t}=this.lx;return this.lexicalEditor.update(()=>{const i=e.$getSelection();e.$isRangeSelection(i)&&t.$patchStyleText(i,{"background-color":null})},{discrete:!0}),this}removeFormat(){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:e,selection:t}=this.lx;return this.lexicalEditor.update(()=>{const i=e.$getSelection();e.$isRangeSelection(i)&&(i.getNodes().forEach(t=>{e.$isTextNode(t)&&t.setFormat(0)}),t.$patchStyleText(i,{color:null,"background-color":null}))},{discrete:!0}),this}formatBlock(e){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:t,richText:i,code:o}=this.lx;return this.lexicalEditor.update(()=>{const l=t.$getSelection();if(!t.$isRangeSelection(l))return;const r=l.anchor.getNode().getTopLevelElementOrThrow(),s=o.$isCodeNode(r);let a;if("p"===e)a=t.$createParagraphNode();else if(e.match(/^h[1-6]$/))a=i.$createHeadingNode(e);else if("blockquote"===e)a=i.$createQuoteNode();else{if("pre"!==e)return;a=o.$createCodeNode()}if(s&&"pre"!==e){const e=r.getTextContent();r.replace(a),a.append(t.$createTextNode(e))}else{const e=r.getChildren();r.replace(a),e.forEach(e=>a.append(e))}a.selectEnd()},{discrete:!0}),this}isSelectionInCodeBlock(){if("visual"!==this.mode||!this.lexicalEditor)return!1;let e=!1;const{lexical:t,code:i}=this.lx;return this.lexicalEditor.getEditorState().read(()=>{const o=t.$getSelection();t.$isRangeSelection(o)&&(e=i.$isCodeNode(o.anchor.getNode().getTopLevelElementOrThrow()))}),e}getTableAtSelection(){if("visual"!==this.mode||!this.lexicalEditor)return null;let e=null;const{lexical:t,table:i}=this.lx;return this.lexicalEditor.getEditorState().read(()=>{const o=t.$getSelection();if(!t.$isRangeSelection(o))return;let l=o.anchor.getNode();for(;l;){if(i.$isTableNode(l)){const t=l.getChildren(),o=[];let r=!1,s=0;t.forEach((e,t)=>{const l=[];e.getChildren().forEach(e=>{0===t&&i.$isTableCellNode(e)&&e.getHeaderStyles()===i.TableCellHeaderStates.ROW&&(r=!0),l.push(e.getTextContent())}),l.length>s&&(s=l.length),o.push(l)}),e={key:l.getKey(),rows:r?t.length-1:t.length,cols:s,hasHeaders:r,cellData:o};break}l=l.getParent()}}),e}removeTableByKey(e){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:t}=this.lx;return this.lexicalEditor.update(()=>{const i=t.$getNodeByKey(e);i&&i.remove()},{discrete:!0}),this}insertHTML(e){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:t,lexicalHtml:i}=this.lx;return this.lexicalEditor.update(()=>{const o=(new DOMParser).parseFromString(e,"text/html"),l=i.$generateNodesFromDOM(this.lexicalEditor,o);t.$insertNodes(l)},{discrete:!0}),this}insertAtCursor(e){return this.insertHTML(e)}insertTable(e,t,i=!1,o=null){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:l,table:r}=this.lx;return this.lexicalEditor.update(()=>{const s=e+(i?1:0),a=[];for(let e=0;e<s;e++){const s=[];for(let a=0;a<t;a++){const t=i&&0===e,c=t?r.TableCellHeaderStates.ROW:r.TableCellHeaderStates.NO_STATUS,n=r.$createTableCellNode(c),h=o?.[e]?.[a]??(t?`Header ${a+1}`:""),d=l.$createParagraphNode();d.append(l.$createTextNode(h||" ")),n.append(d),s.push(n)}a.push(r.$createTableRowNode().append(...s))}const c=r.$createTableNode().append(...a),n=l.$getSelection();if(l.$isRangeSelection(n)){n.anchor.getNode().getTopLevelElementOrThrow().insertAfter(c);const e=l.$createParagraphNode();c.insertAfter(e),e.selectEnd()}else{const e=l.$getRoot();e.append(c),e.append(l.$createParagraphNode())}},{discrete:!0}),this}insertElementAtCursor(e){return this.insertHTML(e.outerHTML)}replaceSelectionWithElement(e){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:t,lexicalHtml:i}=this.lx;return this.lexicalEditor.update(()=>{const o=t.$getSelection();if(!t.$isRangeSelection(o))return;o.removeText();const l=(new DOMParser).parseFromString(e.outerHTML,"text/html"),r=i.$generateNodesFromDOM(this.lexicalEditor,l);t.$insertNodes(r)},{discrete:!0}),this}wrapSelection(e,t,i=null){if("visual"!==this.mode||!this.lexicalEditor)return this;const o=i||this.getSelectedText();return o?this.insertHTML(e+o+t):this}getSelection(){if("visual"!==this.mode||!this.lexicalEditor)return null;let e=null;const{lexical:t}=this.lx;return this.lexicalEditor.getEditorState().read(()=>{const i=t.$getSelection();t.$isRangeSelection(i)&&!i.isCollapsed()&&(e={text:i.getTextContent(),html:i.getTextContent(),selection:i})}),e}getSelectedText(){let e="";if("visual"!==this.mode||!this.lexicalEditor)return e;const{lexical:t}=this.lx;return this.lexicalEditor.getEditorState().read(()=>{const i=t.$getSelection();t.$isRangeSelection(i)&&(e=i.getTextContent())}),e}getSelectedHTML(){return this.getSelectedText()}selectAll(){if("visual"===this.mode&&this.lexicalEditor){const{lexical:e}=this.lx;this.lexicalEditor.update(()=>{e.$selectAll()},{discrete:!0})}else if(this.monacoEditor){const e=this.monacoEditor.getModel();e&&this.monacoEditor.setSelection(e.getFullModelRange())}return this}replaceSelection(e){return this.insertHTML(e)}deleteSelection(){return"visual"===this.mode?this.lexicalCmd("DELETE_CHARACTER_COMMAND",!1):this.monacoEditor&&this.monacoEditor.trigger("keyboard","deleteAllLeft",null),this}getValueWithSelectionMarkers(){if("visual"!==this.mode||!this.lexicalEditor)return{html:this.value,hasCursor:!1,hasSelection:!1,selectedText:""};let e={html:this.exportHtmlFromLexical(),hasCursor:!1,hasSelection:!1,selectedText:""};const{lexical:t}=this.lx;return this.lexicalEditor.getEditorState().read(()=>{const i=t.$getSelection();t.$isRangeSelection(i)&&(i.isCollapsed()?e.hasCursor=!0:(e.hasSelection=!0,e.selectedText=i.getTextContent()))}),e}setValueFromSelectionMarkers(e){return this.setValue(e)}captureSelection(){return this.selection}restoreSavedSelection(){return!1}clearSavedSelection(){this.savedSelection=null}createLink(e){if("visual"!==this.mode||!this.lexicalEditor)return this;const{link:t}=this.lx;return this.lexicalEditor.update(()=>{t.$toggleLink(e)},{discrete:!0}),this}createLinkWithText(e,t){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:i,link:o}=this.lx;return this.lexicalEditor.update(()=>{const l=i.$getSelection();i.$isRangeSelection(l)&&!l.isCollapsed()&&l.removeText();const r=o.$createLinkNode(e);r.append(i.$createTextNode(t)),i.$insertNodes([r])},{discrete:!0}),this}unlink(){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:e,link:t}=this.lx;return this.lexicalEditor.update(()=>{const i=e.$getSelection();e.$isRangeSelection(i)&&i.getNodes().forEach(e=>{const i=e.getParent();i&&t.$isLinkNode?.(i)&&(i.getChildren().forEach(e=>i.insertBefore(e)),i.remove())})},{discrete:!0}),this}insertImage(e){return this.insertHTML(`<img src="${encodeURI(e)}" />`)}undo(){return"code"===this.mode?this.monacoEditor?.trigger("toolbar","undo"):this.lexicalCmd("UNDO_COMMAND",void 0),this}redo(){return"code"===this.mode?this.monacoEditor?.trigger("toolbar","redo"):this.lexicalCmd("REDO_COMMAND",void 0),this}copyToClipboard(){return navigator.clipboard.writeText(this.getValue()),this}setEditorTheme(e){return["auto","light","dark"].includes(e)&&(this.editorTheme=e),this.monacoEditor&&window.monaco.editor.setTheme(this.resolveMonacoTheme()),this}openFind(){return this.monacoEditor?.getAction("actions.find")?.run(),this}foldAll(){return this.monacoEditor?.getAction("editor.foldAll")?.run(),this}unfoldAll(){return this.monacoEditor?.getAction("editor.unfoldAll")?.run(),this}enterFullscreen(){return this.fullscreen=!0,document.body.classList.add("no-scroll"),this.dispatchEvent(new CustomEvent("fullscreen-changed",{detail:{fullscreen:!0},bubbles:!0})),this}exitFullscreen(){return this.fullscreen=!1,document.body.classList.remove("no-scroll"),this.dispatchEvent(new CustomEvent("fullscreen-changed",{detail:{fullscreen:!1},bubbles:!0})),this}toggleFullscreen(){return this.fullscreen?this.exitFullscreen():this.enterFullscreen()}increaseFontSize(){return this.fontSize=Math.min(this.fontSize+2,40),this.monacoEditor?.updateOptions({fontSize:this.fontSize}),this}decreaseFontSize(){return this.fontSize=Math.max(this.fontSize-2,8),this.monacoEditor?.updateOptions({fontSize:this.fontSize}),this}setWordWrap(e){return this.wordWrap=e,this.monacoEditor?.updateOptions({wordWrap:e?"on":"off"}),this}setMinimap(e){return this.minimapEnabled=e,this.monacoEditor?.updateOptions({minimap:{enabled:e}}),this}resolveMonacoTheme(){return"dark"===this.editorTheme?"vs-dark":"light"===this.editorTheme?"vs":"dark"===r()?"vs-dark":"vs"}lexicalCmd(e,t){if("visual"!==this.mode||!this.lexicalEditor)return;const i=this.lx.lexical?.[e];i&&this.lexicalEditor.dispatchCommand(i,t)}lexicalFormat(e){if("visual"!==this.mode||!this.lexicalEditor)return;const{lexical:t}=this.lx;this.lexicalEditor.update(()=>{const i=t.$getSelection();t.$isRangeSelection(i)&&i.formatText(e)},{discrete:!0})}lexicalFormatElement(e){"visual"===this.mode&&this.lexicalEditor&&this.lexicalEditor.dispatchCommand(this.lx.lexical.FORMAT_ELEMENT_COMMAND,e)}render(){const t=this.constructor.controlSets[this.controls]??{};return e`
|
|
1
|
+
import{html as e,css as t}from"../lit-all.min.js";import i from"./ShadowComponent.js";import o from"../utils/formatCode.js";import l from"../utils/debounce.js";import{getCalculatedTheme as r,subscribeToTheme as s}from"../utils/theme.js";import a from"./Dialog.js";export default class c extends i{static formAssociated=!0;static properties={name:{type:String,reflect:!0},value:{type:String,reflect:!0},selection:{type:Object,state:!0},mode:{type:String,reflect:!0},controls:{type:String,reflect:!0},lexicalSrc:{type:String,attribute:"lexical-src"},monacoSrc:{type:String,attribute:"monaco-src"},nodes:{type:String},hasTopToolbar:{type:Boolean,state:!0},hasBottomToolbar:{type:Boolean,state:!0},fullscreen:{type:Boolean,reflect:!0},disabled:{type:Boolean,reflect:!0},readonly:{type:Boolean,reflect:!0},required:{type:Boolean,reflect:!0}};constructor(){super(),this.internals=this.attachInternals(),this.name="",this.value="",this.selection=null,this.cursor=null,this.mode="visual",this.controls="",this.controlsLoaded=!1,this.lexicalSrc="",this.monacoSrc="",this.nodes="",this.hasTopToolbar=!1,this.hasBottomToolbar=!1,this.skipValueSync=!1,this.lexicalValueSync=!1,this.savedSelection=null,this.lexicalEditor=null,this.monacoEditor=null,this.editorTheme="auto",this.wordWrap=!0,this.minimapEnabled=!1,this.fontSize=14,this.fullscreen=!1,this.disabled=!1,this.readonly=!1,this.required=!1,this.lx={},this.debouncedSyncValue=l(()=>this.syncValueFromLexical(),300)}connectedCallback(){super.connectedCallback(),this.hasAttribute("value")&&(this.value=this.getAttribute("value")),this.slotObserver=new MutationObserver(()=>this.updateToolbarVisibility()),this.slotObserver.observe(this,{childList:!0,subtree:!0}),this.updateToolbarVisibility()}disconnectedCallback(){super.disconnectedCallback(),this.slotObserver?.disconnect(),this.cleanupFns?.forEach(e=>e?.()),this.monacoEditor?.dispose(),this.unsubscribeTheme?.(),this.fullscreen&&this.exitFullscreen()}updateToolbarVisibility(){const e=new Set(Array.from(this.children).map(e=>e.getAttribute("slot"))),t=this.constructor.controlSets[this.controls]??null;this.hasTopToolbar=!(!t?.topLeft&&!t?.topRight)||["toolbar-top","toolbar-top-left","toolbar-top-right"].some(t=>e.has(t)),this.hasBottomToolbar=!(!t?.bottomLeft&&!t?.bottomRight)||["toolbar-bottom","toolbar-bottom-left","toolbar-bottom-right"].some(t=>e.has(t))}updated(e){if(super.updated(e),e.has("controls")&&(this.updateToolbarVisibility(),this.controls&&"none"!==this.controls&&this.loadControls()),e.has("value")&&!this.skipValueSync&&(this.lexicalValueSync?this.lexicalValueSync=!1:this.lexicalEditor&&"visual"===this.mode&&!this.isVisualCompatible(this.value)?(this.skipLexicalExport=!0,this.mode="code"):this.syncContentToEditors(),this.updateFormValue(),this.dispatchEvent(new CustomEvent("change",{detail:{value:this.value},bubbles:!0}))),e.has("mode")){const t=window.scrollY;this.handleModeSwitch(e.get("mode")),this.dispatchEvent(new CustomEvent("mode-changed",{detail:{mode:this.mode},bubbles:!0})),requestAnimationFrame(()=>window.scrollTo(0,t))}if(e.has("fullscreen")&&requestAnimationFrame(()=>this.monacoEditor?.layout()),e.has("disabled")||e.has("readonly")){const e=this.disabled||this.readonly;this.lexicalEditor?.setEditable(!e),this.monacoEditor?.updateOptions({readOnly:e})}(e.has("value")||e.has("required")||e.has("disabled"))&&this.#e()}#e=()=>{if(this.disabled)return void this.internals.setValidity({});const e=!(this.value||"").replace(/<[^>]+>/g,"").trim();this.required&&e?this.internals.setValidity({valueMissing:!0},"Please fill out this field.",this.lexicalContainer||this):this.internals.setValidity({})};async firstUpdated(){this.lexicalContainer=this.shadowRoot.querySelector(".lexical-editor"),this.monacoContainer=this.shadowRoot.querySelector(".monaco-editor-container"),await this.initLexical(),this.dispatchEvent(new CustomEvent("ready",{detail:{value:this.value},bubbles:!0}))}async loadControls(){if(this.controlsLoaded)return;this.controlsLoaded=!0;const e=new URL("./htmlEditorControls/",import.meta.url).href;await Promise.all([import(`${e}Bold.js`),import(`${e}Italic.js`),import(`${e}Underline.js`),import(`${e}Strikethrough.js`),import(`${e}InlineCode.js`),import(`${e}DropdownControl.js`),import(`${e}FormatBlock.js`),import(`${e}CodeBlock.js`),import(`${e}BulletList.js`),import(`${e}NumberList.js`),import(`${e}AlignLeft.js`),import(`${e}AlignCenter.js`),import(`${e}AlignRight.js`),import(`${e}AlignJustify.js`),import(`${e}TextColor.js`),import(`${e}TextBackgroundColor.js`),import(`${e}ClearFormatting.js`),import(`${e}CreateLink.js`),import(`${e}InsertTable.js`),import(`${e}ControlGroup.js`),import(`${e}ControlSpacer.js`),import(`${e}Mode.js`),import(`${e}WordCount.js`),import(`${e}CharacterCount.js`)]);const t=new URL("./codeEditorControls/",import.meta.url).href;await Promise.all([import(`${t}FormatCode.js`),import(`${t}CopyCode.js`),import(`${t}Undo.js`),import(`${t}Redo.js`),import(`${t}WordWrap.js`),import(`${t}Minimap.js`),import(`${t}FindReplace.js`),import(`${t}FontSize.js`),import(`${t}FoldAll.js`),import(`${t}EditorTheme.js`),import(`${t}Fullscreen.js`),import(`${t}ControlGroup.js`)]),this.requestUpdate()}async loadNodeModules(){if(!this.nodes?.trim())return[];const e=new URL("./htmlEditorNodes/",import.meta.url).href;return(await Promise.all(this.nodes.split(",").map(e=>e.trim()).filter(Boolean).map(t=>import(`${e}${t}.js`)))).map(e=>e.default?.lexicalNode).filter(Boolean)}async loadLexicalModules(){const e=this.lexicalSrc||window.kempo?.lexicalUrl||"https://esm.sh",t=t=>((e,t)=>`${e}/${t}@0.43.0`)(e,t),[i,o,l,r,s,a,c,n,h]=await Promise.all([import(t("lexical")),import(t("@lexical/rich-text")),import(t("@lexical/html")),import(t("@lexical/history")),import(t("@lexical/list")),import(t("@lexical/link")),import(t("@lexical/selection")),import(t("@lexical/table")),import(t("@lexical/code"))]);this.lx={lexical:i,richText:o,lexicalHtml:l,history:r,list:s,link:a,selection:c,table:n,code:h},this.StyledTextNode=class extends i.TextNode{static getType(){return"styled-text"}static clone(e){return new this(e.__text,e.__key)}static importDOM(){return{span:()=>({conversion:e=>{const t=e.getAttribute("style");if(!t)return null;const o=i.$createTextNode(e.textContent);return o.setStyle(t),{node:o}},priority:1})}}static importJSON(e){return i.$createTextNode(e.text)}exportJSON(){return{...super.exportJSON(),type:"styled-text"}}}}async initLexical(){await this.loadLexicalModules(),this.customNodes=await this.loadNodeModules(),this.nodeCompatCheckers=this.customNodes.filter(e=>"function"==typeof e.isVisualCompatible).map(e=>e.isVisualCompatible),this.nodePreprocessors=this.customNodes.filter(e=>"function"==typeof e.preprocessHtml).map(e=>e.preprocessHtml);const{lexical:e,richText:t,history:i,list:o,link:l,table:r,code:s}=this.lx,a={namespace:"KempoHtmlEditor",theme:{paragraph:"k-editor-p",heading:{h1:"k-editor-h1",h2:"k-editor-h2",h3:"k-editor-h3",h4:"k-editor-h4",h5:"k-editor-h5",h6:"k-editor-h6"},text:{underline:"td-u",strikethrough:"td-lt"},list:{ul:"k-editor-ul",ol:"k-editor-ol",listitem:"k-editor-li"},link:"k-editor-link",quote:"k-editor-quote",code:"k-editor-code-block",codeHighlight:{},table:"k-editor-table",tableCell:"k-editor-table-cell",tableCellHeader:"k-editor-table-cell-header"},nodes:[t.HeadingNode,t.QuoteNode,o.ListNode,o.ListItemNode,l.LinkNode,r.TableNode,r.TableCellNode,r.TableRowNode,s.CodeNode,s.CodeHighlightNode,this.StyledTextNode,...this.customNodes],onError:console.error,editorState:null};this.lexicalEditor=e.createEditor(a),this.lexicalEditor.setRootElement(this.lexicalContainer),(this.disabled||this.readonly)&&this.lexicalEditor.setEditable(!1),this.lexicalEditor._window=new Proxy(window,{get:(e,t)=>{if("getSelection"===t)return()=>this.shadowRoot.getSelection();const i=Reflect.get(e,t);return"function"==typeof i?i.bind(e):i}}),this.cleanupFns=[t.registerRichText(this.lexicalEditor),i.registerHistory(this.lexicalEditor,i.createEmptyHistoryState(),300)],o.registerList&&this.cleanupFns.push(o.registerList(this.lexicalEditor)),r.registerTable&&this.cleanupFns.push(r.registerTable(this.lexicalEditor)),s.registerCodeHighlighting&&this.cleanupFns.push(s.registerCodeHighlighting(this.lexicalEditor)),l.registerLink&&this.cleanupFns.push(l.registerLink(this.lexicalEditor,{validateUrl:e=>{try{return new URL(e),!0}catch{return!1}}})),this.value&&(this.isVisualCompatible(this.value)?this.importHtmlToLexical(this.value):(this.skipLexicalExport=!0,this.mode="code")),this.lexicalEditor.registerUpdateListener(({dirtyElements:e,dirtyLeaves:t})=>{0===e.size&&0===t.size||(this.debouncedSyncValue(),this.dispatchEvent(new CustomEvent("input",{detail:{value:this.exportHtmlFromLexical()},bubbles:!0})))}),this.lexicalEditor.registerCommand(e.SELECTION_CHANGE_COMMAND,()=>(this.updateSelection(),!1),e.COMMAND_PRIORITY_LOW)}async initMonaco(){if(!this.monacoEditor){if(this.monacoInitPromise)return this.monacoInitPromise;this.monacoInitPromise=this._initMonaco(),await this.monacoInitPromise,this.monacoInitPromise=null}}async _initMonaco(){const e=this.monacoSrc||window.kempo?.monacoUrl||"https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min";await new Promise((t,i)=>{if(window.monaco)return void t();if(window.require?.defined?.("vs/editor/editor.main"))return void t();const o=document.querySelector(`script[src="${e}/vs/loader.js"]`);if(o)return void o.addEventListener("load",()=>{window.require.config({paths:{vs:`${e}/vs`}}),window.require(["vs/editor/editor.main"],()=>t(),i)});const l=document.createElement("script");l.src=`${e}/vs/loader.js`,l.onload=()=>{window.require.config({paths:{vs:`${e}/vs`}}),window.require(["vs/editor/editor.main"],()=>t(),i)},l.onerror=i,document.head.appendChild(l)}),this.monacoEditor=window.monaco.editor.create(this.monacoContainer,{value:o(this.value),language:"html",theme:this.resolveMonacoTheme(),minimap:{enabled:this.minimapEnabled},wordWrap:this.wordWrap?"on":"off",fontSize:this.fontSize,scrollBeyondLastLine:!1,automaticLayout:!0,tabSize:2,padding:{top:8},readOnly:this.disabled||this.readonly});const t=document.querySelector('link[href*="monaco"][href*="editor.main.css"]');if(t){const e=document.createElement("link");e.rel="stylesheet",e.href=t.href,this.shadowRoot.appendChild(e)}this.unsubscribeTheme=s(()=>{this.monacoEditor&&"auto"===this.editorTheme&&window.monaco.editor.setTheme(this.resolveMonacoTheme())}),this.monacoEditor.onDidChangeModelContent(()=>{this.skipValueSync=!0,this.value=this.monacoEditor.getValue(),this.skipValueSync=!1,this.updateFormValue(),this.dispatchEvent(new CustomEvent("change",{detail:{value:this.value},bubbles:!0})),this.dispatchEvent(new CustomEvent("input",{detail:{value:this.value},bubbles:!0}))})}importHtmlToLexical(e){if(!this.lexicalEditor||!this.lx.lexicalHtml)return;const{lexical:t,lexicalHtml:i}=this.lx,o=(this.nodePreprocessors||[]).reduce((e,t)=>t(e),e);this.lexicalEditor.update(()=>{if(t.$getRoot().clear(),!o?.trim())return;const e=(new DOMParser).parseFromString(o,"text/html"),l=i.$generateNodesFromDOM(this.lexicalEditor,e);l.length>0&&t.$insertNodes(l)},{discrete:!0})}exportHtmlFromLexical(){if(!this.lexicalEditor||!this.lx.lexicalHtml)return this.value;let e="";return this.lexicalEditor.getEditorState().read(()=>{e=this.lx.lexicalHtml.$generateHtmlFromNodes(this.lexicalEditor,null)}),this.cleanExportedHtml(e)}isVisualCompatible(e){if(!e?.trim())return!0;const t=new Set(["script","style","meta","link","head","iframe","object","embed","canvas","video","audio","form","input","button","select","textarea","fieldset","label","noscript","template","slot","svg","math"]),i=this.nodeCompatCheckers||[],o=(new DOMParser).parseFromString(e,"text/html"),l=document.createTreeWalker(o.body,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_COMMENT);let r;for(;r=l.nextNode();)if(!i.some(e=>e(r))){if(r.nodeType===Node.COMMENT_NODE)return!1;if(t.has(r.tagName?.toLowerCase()))return!1}return!0}cleanExportedHtml(e){const t=(new DOMParser).parseFromString(e,"text/html");return t.body.querySelectorAll("[class]").forEach(e=>{const t=Array.from(e.classList).filter(e=>!e.startsWith("k-editor-")&&"td-u"!==e&&"td-lt"!==e);0===t.length?e.removeAttribute("class"):e.className=t.join(" ")}),t.body.querySelectorAll("[style]").forEach(e=>{const t=e.style.cssText.replace(/white-space:\s*pre-wrap;?\s*/g,"").trim();t?e.style.cssText=t:e.removeAttribute("style")}),t.body.querySelectorAll("span:not([class]):not([style]):not([id])").forEach(e=>{e.attributes.length||e.replaceWith(...e.childNodes)}),t.body.querySelectorAll("b > strong, i > em, b > b, strong > strong, i > i, em > em").forEach(e=>{e.replaceWith(...e.childNodes)}),t.body.querySelectorAll("pre[data-highlight-language], code[data-highlight-language]").forEach(e=>{e.removeAttribute("data-highlight-language"),e.removeAttribute("data-language")}),t.body.innerHTML}syncValueFromLexical(){this.lexicalEditor&&(this.lexicalValueSync=!0,this.value=this.exportHtmlFromLexical(),this.updateFormValue())}syncContentToEditors(){"visual"===this.mode&&this.lexicalEditor&&this.importHtmlToLexical(this.value)}async handleModeSwitch(e){"code"===this.mode?(this.lexicalEditor&&!this.skipLexicalExport&&(this.value=this.exportHtmlFromLexical()),this.skipLexicalExport=!1,await this.initMonaco(),this.monacoEditor&&(this.monacoEditor.setValue(o(this.value)),this.monacoEditor.layout())):"visual"===this.mode&&(this.monacoEditor&&(this.value=this.monacoEditor.getValue()),this.lexicalEditor&&this.importHtmlToLexical(this.value)),this.requestUpdate()}updateFormValue(){this.internals.setFormValue(this.getValue())}formResetCallback(){this.value=""}formStateRestoreCallback(e){this.value=e}formDisabledCallback(e){this.disabled=e}updateSelection=()=>{if("visual"!==this.mode||!this.lexicalEditor)return void(this.selection=null);const{lexical:e}=this.lx;this.lexicalEditor.getEditorState().read(()=>{const t=e.$getSelection();e.$isRangeSelection(t)&&!t.isCollapsed()?this.selection={text:t.getTextContent(),collapsed:!1}:(this.selection=null,this.cursor=t?{anchor:t.anchor,focus:t.focus}:null)})};setMode(e){return["visual","code"].includes(e)?"visual"!==e||this.isVisualCompatible(this.getValue())?(this.mode=e,this):(a.confirm("This html contains code that is not compatible with the visual editor, the incompatible code will be lost",t=>{t&&(this.mode=e)},{title:"Warning",confirmText:"Change Anyways"}),this):this}toggleMode(){return this.setMode("visual"===this.mode?"code":"visual")}getValue(){if("visual"===this.mode&&this.lexicalEditor)this.skipValueSync=!0,this.value=this.exportHtmlFromLexical(),this.skipValueSync=!1;else if("code"===this.mode&&this.monacoEditor)return this.monacoEditor.getValue();return this.value}setValue(e){return this.lexicalEditor&&"visual"===this.mode&&!this.isVisualCompatible(e)?(this.value=e,this.skipLexicalExport=!0,this.mode="code",this.updateFormValue(),this):(this.skipValueSync=!0,this.value=e,"visual"===this.mode?this.syncContentToEditors():"code"===this.mode&&this.monacoEditor&&this.monacoEditor.setValue(o(e)),this.updateFormValue(),this.skipValueSync=!1,this)}clear(){return this.setValue("")}bold(){return this.lexicalFormat("bold"),this}italic(){return this.lexicalFormat("italic"),this}underline(){return this.lexicalFormat("underline"),this}strikethrough(){return this.lexicalFormat("strikethrough"),this}inlineCode(){return this.lexicalFormat("code"),this}orderedList(){return"visual"===this.mode&&this.lexicalEditor?(this.lexicalEditor.update(()=>{this.lx.list.$insertList("number")},{discrete:!0}),this):this}unorderedList(){return"visual"===this.mode&&this.lexicalEditor?(this.lexicalEditor.update(()=>{this.lx.list.$insertList("bullet")},{discrete:!0}),this):this}alignLeft(){return this.lexicalFormatElement("left"),this}alignCenter(){return this.lexicalFormatElement("center"),this}alignRight(){return this.lexicalFormatElement("right"),this}alignJustify(){return this.lexicalFormatElement("justify"),this}setTextColor(e){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:t,selection:i}=this.lx;return this.lexicalEditor.update(()=>{const o=t.$getSelection();t.$isRangeSelection(o)&&i.$patchStyleText(o,{color:e})},{discrete:!0}),this}removeTextColor(){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:e,selection:t}=this.lx;return this.lexicalEditor.update(()=>{const i=e.$getSelection();e.$isRangeSelection(i)&&t.$patchStyleText(i,{color:null})},{discrete:!0}),this}setTextBackgroundColor(e){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:t,selection:i}=this.lx;return this.lexicalEditor.update(()=>{const o=t.$getSelection();t.$isRangeSelection(o)&&i.$patchStyleText(o,{"background-color":e})},{discrete:!0}),this}removeTextBackgroundColor(){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:e,selection:t}=this.lx;return this.lexicalEditor.update(()=>{const i=e.$getSelection();e.$isRangeSelection(i)&&t.$patchStyleText(i,{"background-color":null})},{discrete:!0}),this}removeFormat(){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:e,selection:t}=this.lx;return this.lexicalEditor.update(()=>{const i=e.$getSelection();e.$isRangeSelection(i)&&(i.getNodes().forEach(t=>{e.$isTextNode(t)&&t.setFormat(0)}),t.$patchStyleText(i,{color:null,"background-color":null}))},{discrete:!0}),this}formatBlock(e){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:t,richText:i,code:o}=this.lx;return this.lexicalEditor.update(()=>{const l=t.$getSelection();if(!t.$isRangeSelection(l))return;const r=l.anchor.getNode().getTopLevelElementOrThrow(),s=o.$isCodeNode(r);let a;if("p"===e)a=t.$createParagraphNode();else if(e.match(/^h[1-6]$/))a=i.$createHeadingNode(e);else if("blockquote"===e)a=i.$createQuoteNode();else{if("pre"!==e)return;a=o.$createCodeNode()}if(s&&"pre"!==e){const e=r.getTextContent();r.replace(a),a.append(t.$createTextNode(e))}else{const e=r.getChildren();r.replace(a),e.forEach(e=>a.append(e))}a.selectEnd()},{discrete:!0}),this}isSelectionInCodeBlock(){if("visual"!==this.mode||!this.lexicalEditor)return!1;let e=!1;const{lexical:t,code:i}=this.lx;return this.lexicalEditor.getEditorState().read(()=>{const o=t.$getSelection();t.$isRangeSelection(o)&&(e=i.$isCodeNode(o.anchor.getNode().getTopLevelElementOrThrow()))}),e}getTableAtSelection(){if("visual"!==this.mode||!this.lexicalEditor)return null;let e=null;const{lexical:t,table:i}=this.lx;return this.lexicalEditor.getEditorState().read(()=>{const o=t.$getSelection();if(!t.$isRangeSelection(o))return;let l=o.anchor.getNode();for(;l;){if(i.$isTableNode(l)){const t=l.getChildren(),o=[];let r=!1,s=0;t.forEach((e,t)=>{const l=[];e.getChildren().forEach(e=>{0===t&&i.$isTableCellNode(e)&&e.getHeaderStyles()===i.TableCellHeaderStates.ROW&&(r=!0),l.push(e.getTextContent())}),l.length>s&&(s=l.length),o.push(l)}),e={key:l.getKey(),rows:r?t.length-1:t.length,cols:s,hasHeaders:r,cellData:o};break}l=l.getParent()}}),e}removeTableByKey(e){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:t}=this.lx;return this.lexicalEditor.update(()=>{const i=t.$getNodeByKey(e);i&&i.remove()},{discrete:!0}),this}insertHTML(e){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:t,lexicalHtml:i}=this.lx;return this.lexicalEditor.update(()=>{const o=(new DOMParser).parseFromString(e,"text/html"),l=i.$generateNodesFromDOM(this.lexicalEditor,o);t.$insertNodes(l)},{discrete:!0}),this}insertAtCursor(e){return this.insertHTML(e)}insertTable(e,t,i=!1,o=null){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:l,table:r}=this.lx;return this.lexicalEditor.update(()=>{const s=e+(i?1:0),a=[];for(let e=0;e<s;e++){const s=[];for(let a=0;a<t;a++){const t=i&&0===e,c=t?r.TableCellHeaderStates.ROW:r.TableCellHeaderStates.NO_STATUS,n=r.$createTableCellNode(c),h=o?.[e]?.[a]??(t?`Header ${a+1}`:""),d=l.$createParagraphNode();d.append(l.$createTextNode(h||" ")),n.append(d),s.push(n)}a.push(r.$createTableRowNode().append(...s))}const c=r.$createTableNode().append(...a),n=l.$getSelection();if(l.$isRangeSelection(n)){n.anchor.getNode().getTopLevelElementOrThrow().insertAfter(c);const e=l.$createParagraphNode();c.insertAfter(e),e.selectEnd()}else{const e=l.$getRoot();e.append(c),e.append(l.$createParagraphNode())}},{discrete:!0}),this}insertElementAtCursor(e){return this.insertHTML(e.outerHTML)}replaceSelectionWithElement(e){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:t,lexicalHtml:i}=this.lx;return this.lexicalEditor.update(()=>{const o=t.$getSelection();if(!t.$isRangeSelection(o))return;o.removeText();const l=(new DOMParser).parseFromString(e.outerHTML,"text/html"),r=i.$generateNodesFromDOM(this.lexicalEditor,l);t.$insertNodes(r)},{discrete:!0}),this}wrapSelection(e,t,i=null){if("visual"!==this.mode||!this.lexicalEditor)return this;const o=i||this.getSelectedText();return o?this.insertHTML(e+o+t):this}getSelection(){if("visual"!==this.mode||!this.lexicalEditor)return null;let e=null;const{lexical:t}=this.lx;return this.lexicalEditor.getEditorState().read(()=>{const i=t.$getSelection();t.$isRangeSelection(i)&&!i.isCollapsed()&&(e={text:i.getTextContent(),html:i.getTextContent(),selection:i})}),e}getSelectedText(){let e="";if("visual"!==this.mode||!this.lexicalEditor)return e;const{lexical:t}=this.lx;return this.lexicalEditor.getEditorState().read(()=>{const i=t.$getSelection();t.$isRangeSelection(i)&&(e=i.getTextContent())}),e}getSelectedHTML(){return this.getSelectedText()}selectAll(){if("visual"===this.mode&&this.lexicalEditor){const{lexical:e}=this.lx;this.lexicalEditor.update(()=>{e.$selectAll()},{discrete:!0})}else if(this.monacoEditor){const e=this.monacoEditor.getModel();e&&this.monacoEditor.setSelection(e.getFullModelRange())}return this}replaceSelection(e){return this.insertHTML(e)}deleteSelection(){return"visual"===this.mode?this.lexicalCmd("DELETE_CHARACTER_COMMAND",!1):this.monacoEditor&&this.monacoEditor.trigger("keyboard","deleteAllLeft",null),this}getValueWithSelectionMarkers(){if("visual"!==this.mode||!this.lexicalEditor)return{html:this.value,hasCursor:!1,hasSelection:!1,selectedText:""};let e={html:this.exportHtmlFromLexical(),hasCursor:!1,hasSelection:!1,selectedText:""};const{lexical:t}=this.lx;return this.lexicalEditor.getEditorState().read(()=>{const i=t.$getSelection();t.$isRangeSelection(i)&&(i.isCollapsed()?e.hasCursor=!0:(e.hasSelection=!0,e.selectedText=i.getTextContent()))}),e}setValueFromSelectionMarkers(e){return this.setValue(e)}captureSelection(){return this.selection}restoreSavedSelection(){return!1}clearSavedSelection(){this.savedSelection=null}createLink(e){if("visual"!==this.mode||!this.lexicalEditor)return this;const{link:t}=this.lx;return this.lexicalEditor.update(()=>{t.$toggleLink(e)},{discrete:!0}),this}createLinkWithText(e,t){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:i,link:o}=this.lx;return this.lexicalEditor.update(()=>{const l=i.$getSelection();i.$isRangeSelection(l)&&!l.isCollapsed()&&l.removeText();const r=o.$createLinkNode(e);r.append(i.$createTextNode(t)),i.$insertNodes([r])},{discrete:!0}),this}unlink(){if("visual"!==this.mode||!this.lexicalEditor)return this;const{lexical:e,link:t}=this.lx;return this.lexicalEditor.update(()=>{const i=e.$getSelection();e.$isRangeSelection(i)&&i.getNodes().forEach(e=>{const i=e.getParent();i&&t.$isLinkNode?.(i)&&(i.getChildren().forEach(e=>i.insertBefore(e)),i.remove())})},{discrete:!0}),this}insertImage(e){return this.insertHTML(`<img src="${encodeURI(e)}" />`)}undo(){return"code"===this.mode?this.monacoEditor?.trigger("toolbar","undo"):this.lexicalCmd("UNDO_COMMAND",void 0),this}redo(){return"code"===this.mode?this.monacoEditor?.trigger("toolbar","redo"):this.lexicalCmd("REDO_COMMAND",void 0),this}copyToClipboard(){return navigator.clipboard.writeText(this.getValue()),this}setEditorTheme(e){return["auto","light","dark"].includes(e)&&(this.editorTheme=e),this.monacoEditor&&window.monaco.editor.setTheme(this.resolveMonacoTheme()),this}openFind(){return this.monacoEditor?.getAction("actions.find")?.run(),this}foldAll(){return this.monacoEditor?.getAction("editor.foldAll")?.run(),this}unfoldAll(){return this.monacoEditor?.getAction("editor.unfoldAll")?.run(),this}enterFullscreen(){return this.fullscreen=!0,document.body.classList.add("no-scroll"),this.dispatchEvent(new CustomEvent("fullscreen-changed",{detail:{fullscreen:!0},bubbles:!0})),this}exitFullscreen(){return this.fullscreen=!1,document.body.classList.remove("no-scroll"),this.dispatchEvent(new CustomEvent("fullscreen-changed",{detail:{fullscreen:!1},bubbles:!0})),this}toggleFullscreen(){return this.fullscreen?this.exitFullscreen():this.enterFullscreen()}increaseFontSize(){return this.fontSize=Math.min(this.fontSize+2,40),this.monacoEditor?.updateOptions({fontSize:this.fontSize}),this}decreaseFontSize(){return this.fontSize=Math.max(this.fontSize-2,8),this.monacoEditor?.updateOptions({fontSize:this.fontSize}),this}setWordWrap(e){return this.wordWrap=e,this.monacoEditor?.updateOptions({wordWrap:e?"on":"off"}),this}setMinimap(e){return this.minimapEnabled=e,this.monacoEditor?.updateOptions({minimap:{enabled:e}}),this}resolveMonacoTheme(){return"dark"===this.editorTheme?"vs-dark":"light"===this.editorTheme?"vs":"dark"===r()?"vs-dark":"vs"}lexicalCmd(e,t){if("visual"!==this.mode||!this.lexicalEditor)return;const i=this.lx.lexical?.[e];i&&this.lexicalEditor.dispatchCommand(i,t)}lexicalFormat(e){if("visual"!==this.mode||!this.lexicalEditor)return;const{lexical:t}=this.lx;this.lexicalEditor.update(()=>{const i=t.$getSelection();t.$isRangeSelection(i)&&i.formatText(e)},{discrete:!0})}lexicalFormatElement(e){"visual"===this.mode&&this.lexicalEditor&&this.lexicalEditor.dispatchCommand(this.lx.lexical.FORMAT_ELEMENT_COMMAND,e)}render(){const t=this.constructor.controlSets[this.controls]??{};return e`
|
|
2
2
|
${this.hasTopToolbar?e`
|
|
3
3
|
<div class="toolbar-top bb">
|
|
4
4
|
<div class="toolbar-start">
|
|
@@ -12,7 +12,7 @@ import{html as e,css as t}from"../lit-all.min.js";import i from"./ShadowComponen
|
|
|
12
12
|
<div class="editor-container">
|
|
13
13
|
<div
|
|
14
14
|
class="lexical-editor"
|
|
15
|
-
contenteditable
|
|
15
|
+
contenteditable=${this.disabled||this.readonly?"false":"true"}
|
|
16
16
|
?hidden=${"visual"!==this.mode}
|
|
17
17
|
></div>
|
|
18
18
|
<div
|
|
@@ -46,6 +46,26 @@ import{html as e,css as t}from"../lit-all.min.js";import i from"./ShadowComponen
|
|
|
46
46
|
height: 100vh !important;
|
|
47
47
|
z-index: 10000;
|
|
48
48
|
}
|
|
49
|
+
:host([disabled]) {
|
|
50
|
+
opacity: 0.6;
|
|
51
|
+
}
|
|
52
|
+
/* disabled blocks all interaction -- toolbar AND editor. Lexical
|
|
53
|
+
and Monaco both prevent typing on their own; pointer-events: none
|
|
54
|
+
also stops focus / cursor placement, matching native form control
|
|
55
|
+
semantics. */
|
|
56
|
+
:host([disabled]) .toolbar-top,
|
|
57
|
+
:host([disabled]) .toolbar-bottom,
|
|
58
|
+
:host([disabled]) .editor-container {
|
|
59
|
+
pointer-events: none;
|
|
60
|
+
}
|
|
61
|
+
/* readonly keeps the editor interactive (so users can place a cursor
|
|
62
|
+
to select / copy) but mutes the toolbar so its buttons can't
|
|
63
|
+
mutate the document. */
|
|
64
|
+
:host([readonly]) .toolbar-top,
|
|
65
|
+
:host([readonly]) .toolbar-bottom {
|
|
66
|
+
pointer-events: none;
|
|
67
|
+
opacity: 0.5;
|
|
68
|
+
}
|
|
49
69
|
.toolbar-top,
|
|
50
70
|
.toolbar-bottom {
|
|
51
71
|
display: flex;
|
|
@@ -80,7 +100,7 @@ import{html as e,css as t}from"../lit-all.min.js";import i from"./ShadowComponen
|
|
|
80
100
|
overflow: auto;
|
|
81
101
|
}
|
|
82
102
|
.lexical-editor {
|
|
83
|
-
padding: 1rem;
|
|
103
|
+
padding: var(--editor_padding, 1rem);
|
|
84
104
|
border: 1px solid var(--border-color);
|
|
85
105
|
background: var(--bg-primary);
|
|
86
106
|
color: var(--text-primary);
|
|
@@ -88,6 +108,25 @@ import{html as e,css as t}from"../lit-all.min.js";import i from"./ShadowComponen
|
|
|
88
108
|
font-size: inherit;
|
|
89
109
|
line-height: 1.5;
|
|
90
110
|
outline: none;
|
|
111
|
+
/* Always show a slim scrollbar when content overflows (macOS would
|
|
112
|
+
otherwise hide overlay scrollbars when not actively scrolling,
|
|
113
|
+
leaving users unaware they can scroll back). */
|
|
114
|
+
scrollbar-width: thin;
|
|
115
|
+
scrollbar-color: var(--c_border, rgba(128,128,128,0.4)) transparent;
|
|
116
|
+
}
|
|
117
|
+
.lexical-editor::-webkit-scrollbar {
|
|
118
|
+
width: 8px;
|
|
119
|
+
height: 8px;
|
|
120
|
+
}
|
|
121
|
+
.lexical-editor::-webkit-scrollbar-track {
|
|
122
|
+
background: transparent;
|
|
123
|
+
}
|
|
124
|
+
.lexical-editor::-webkit-scrollbar-thumb {
|
|
125
|
+
background: var(--c_border, rgba(128,128,128,0.4));
|
|
126
|
+
border-radius: 4px;
|
|
127
|
+
}
|
|
128
|
+
.lexical-editor::-webkit-scrollbar-thumb:hover {
|
|
129
|
+
background: rgba(128,128,128,0.7);
|
|
91
130
|
}
|
|
92
131
|
.lexical-editor:focus {
|
|
93
132
|
border-color: var(--primary-color);
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import{html as t,css as e,nothing as i}from"../lit-all.min.js";import s from"./ShadowComponent.js";import l from"../utils/renderMarkdown.js";import o,{STRIP_COMPLETELY as a}from"../utils/sanitizeHtml.js";import"./Resize.js";import"./Tabs.js";export default class n extends s{static formAssociated=!0;static properties={value:{type:String},name:{type:String,reflect:!0},placeholder:{type:String,reflect:!0},disabled:{type:Boolean,reflect:!0},required:{type:Boolean,reflect:!0},readonly:{type:Boolean,reflect:!0},mode:{type:String,reflect:!0},allowedTags:{type:String,reflect:!0,attribute:"allowed-tags"},disallowedTags:{type:String,reflect:!0,attribute:"disallowed-tags"},scriptsEnabled:{type:Boolean,reflect:!0,attribute:"scripts-enabled"},controls:{type:String,reflect:!0}};#t="";constructor(){super(),this.internals=this.attachInternals(),this.value="",this.name="",this.placeholder="",this.disabled=!1,this.required=!1,this.readonly=!1,this.mode="write",this.allowedTags="",this.disallowedTags="",this.scriptsEnabled=!1,this.controls="",this.#e=!1}#e=!1;async loadControls(){if(this.#e)return;this.#e=!0;const t=new URL("./markdownEditorControls/",import.meta.url).href;await Promise.all([import(`${t}Bold.js`),import(`${t}Italic.js`),import(`${t}Strikethrough.js`),import(`${t}Heading.js`),import(`${t}Code.js`),import(`${t}Link.js`),import(`${t}Image.js`),import(`${t}Table.js`),import(`${t}BulletList.js`),import(`${t}NumberedList.js`),import(`${t}Quote.js`),import(`${t}Menu.js`),import(`${t}FormatBlock.js`),import(`${t}SpeechToText.js`)]),this.requestUpdate()}connectedCallback(){super.connectedCallback(),this.hasAttribute("value")&&(this.#t=this.getAttribute("value")),!this.value&&this.#t&&(this.value=this.#t)}updated(t){super.updated(t),t.has("value")&&this.internals.setFormValue(this.value),t.has("controls")&&this.controls&&"none"!==this.controls&&this.loadControls(),this.#i()}formResetCallback(){this.value=this.#t,this.mode="write"}formStateRestoreCallback(t){"string"==typeof t&&(this.value=t)}formDisabledCallback(t){this.disabled=t}focus(){"write"!==this.mode&&(this.mode="write"),this.updateComplete.then(()=>{this.shadowRoot?.querySelector("textarea")?.focus()})}blur(){this.shadowRoot?.querySelector("textarea")?.blur()}clear(){this.value=""}setMode(t){"write"!==t&&"preview"!==t||(this.mode=t)}togglePreview(){this.mode="write"===this.mode?"preview":"write"}get textarea(){return this.shadowRoot?.querySelector("textarea")||null}getSelection(){const t=this.textarea;return t?{start:t.selectionStart,end:t.selectionEnd,text:t.value.substring(t.selectionStart,t.selectionEnd)}:{start:0,end:0,text:""}}replaceSelection(t,{selectInserted:e=!0}={}){const i=this.textarea;i&&("write"!==this.mode&&(this.mode="write"),this.updateComplete.then(()=>{i.focus();const{selectionStart:s,selectionEnd:l}=i,o=i.value.substring(0,s),a=i.value.substring(l),n=o+t+a;i.value=n;const r=s+t.length;i.selectionStart=e?s:r,i.selectionEnd=r,this.value=n,i.dispatchEvent(new Event("input",{bubbles:!0}))}))}wrapSelection(t,e=t,i=""){const s=this.textarea;s?("write"!==this.mode&&(this.mode="write"),this.updateComplete.then(()=>{s.focus();const{selectionStart:l,selectionEnd:o,value:a}=s,n=a.substring(l,o);if(n.length>=t.length+e.length&&n.startsWith(t)&&n.endsWith(e)){const i=n.slice(t.length,n.length-e.length),r=a.substring(0,l)+i+a.substring(o);return s.value=r,s.selectionStart=l,s.selectionEnd=l+i.length,this.value=r,void s.dispatchEvent(new Event("input",{bubbles:!0}))}const r=a.substring(Math.max(0,l-t.length),l),d=a.substring(o,Math.min(a.length,o+e.length));if(r===t&&d===e){const i=a.substring(0,l-t.length)+n+a.substring(o+e.length);return s.value=i,s.selectionStart=l-t.length,s.selectionEnd=s.selectionStart+n.length,this.value=i,void s.dispatchEvent(new Event("input",{bubbles:!0}))}const m=n||i,c=a.substring(0,l)+t+m+e+a.substring(o);s.value=c;const h=l+t.length;s.selectionStart=h,s.selectionEnd=h+m.length,this.value=c,s.dispatchEvent(new Event("input",{bubbles:!0}))})):this.replaceSelection(t+i+e)}insertAtCursor(t){this.replaceSelection(t,{selectInserted:!1})}replaceInSelectedLines(t,e=""){const i=this.textarea;i&&("write"!==this.mode&&(this.mode="write"),this.updateComplete.then(()=>{i.focus();const{selectionStart:s,selectionEnd:l,value:o}=i,a=o.lastIndexOf("\n",s-1)+1,n=o.indexOf("\n",l),r=-1===n?o.length:n,d=o.substring(a,r),m=d.split("\n").map(i=>i.replace(t,e)).join("\n");if(m===d)return;const c=o.substring(0,a)+m+o.substring(r);i.value=c,i.selectionStart=a,i.selectionEnd=a+m.length,this.value=c,i.dispatchEvent(new Event("input",{bubbles:!0}))}))}insertLinePrefix(t,e=null){const i=this.textarea;i&&("write"!==this.mode&&(this.mode="write"),this.updateComplete.then(()=>{i.focus();const{selectionStart:s,selectionEnd:l,value:o}=i,a=o.lastIndexOf("\n",s-1)+1,n=o.indexOf("\n",l),r=-1===n?o.length:n,d=o.substring(a,r).split("\n"),m=d.filter(t=>t.length>0),c=m.length>0&&m.every(e=>e.startsWith(t)),h=d.map(i=>0===i.length?i:c?i.startsWith(t)?i.slice(t.length):i:i.startsWith(t)?i:e&&e.test(i)?i.replace(e,t):t+i).join("\n"),u=o.substring(0,a)+h+o.substring(r);i.value=u,i.selectionStart=a,i.selectionEnd=a+h.length,this.value=u,i.dispatchEvent(new Event("input",{bubbles:!0}))}))}get isEmpty(){return!(this.value||"").trim()}get renderedHtml(){const t={},e=this.#s;if(e&&(t.allowedTags=e),this.scriptsEnabled){const e=new Set(a);e.delete("SCRIPT"),t.stripCompletely=e}return o(l(this.value||""),t)}get#s(){const t=(this.allowedTags||"").trim(),e=(this.disallowedTags||"").trim();if(t&&e&&console.warn("[k-markdown-editor] `allowed-tags` and `disallowed-tags` are mutually exclusive; using `allowed-tags`."),t)return"*"===t?{has:()=>!0}:new Set(t.split(",").map(t=>t.trim().toUpperCase()).filter(Boolean));if(e){const t=new Set(e.split(",").map(t=>t.trim().toUpperCase()).filter(Boolean));return{has:e=>!t.has(e)}}return null}#i=()=>{const t=this.shadowRoot?.querySelector("textarea");this.required&&this.isEmpty?this.internals.setValidity({valueMissing:!0},"Please fill out this field.",t):this.internals.setValidity({})};handleInput=t=>{this.value=t.target.value,this.dispatchEvent(new CustomEvent("input",{detail:{value:this.value},bubbles:!0}))};handleChange=()=>{this.dispatchEvent(new CustomEvent("change",{detail:{value:this.value},bubbles:!0}))};handleTabChange=t=>{const e=t.detail?.tab;e&&e!==this.mode&&(this.mode=e,this.dispatchEvent(new CustomEvent("mode-change",{detail:{mode:e},bubbles:!0})))};render(){const e=this.constructor.controlSets[this.controls]??this.constructor.controlSets[""];return t`
|
|
2
|
+
<k-resize dimension="height" ?disabled=${this.disabled}>
|
|
3
|
+
<div class="frame">
|
|
4
|
+
<k-tabs fixed-height active=${this.mode} @tab=${this.handleTabChange}>
|
|
5
|
+
<k-tab slot="tabs" for="write">Write</k-tab>
|
|
6
|
+
<k-tab slot="tabs" for="preview">Preview</k-tab>
|
|
7
|
+
<k-tab-spacer slot="tabs"></k-tab-spacer>
|
|
8
|
+
<div slot="tabs" class="controls-top">
|
|
9
|
+
<slot name="controls-top">${e.top??i}</slot>
|
|
10
|
+
</div>
|
|
11
|
+
<k-tab-content name="write">
|
|
12
|
+
<textarea
|
|
13
|
+
class="editor"
|
|
14
|
+
.value=${this.value}
|
|
15
|
+
placeholder=${this.placeholder}
|
|
16
|
+
?disabled=${this.disabled}
|
|
17
|
+
?readonly=${this.readonly}
|
|
18
|
+
aria-label=${this.name||this.placeholder}
|
|
19
|
+
@input=${this.handleInput}
|
|
20
|
+
@blur=${this.handleChange}
|
|
21
|
+
></textarea>
|
|
22
|
+
</k-tab-content>
|
|
23
|
+
<k-tab-content name="preview">
|
|
24
|
+
<div
|
|
25
|
+
class="preview"
|
|
26
|
+
role="article"
|
|
27
|
+
.innerHTML=${this.isEmpty?'<p class="preview-empty">Nothing to preview</p>':this.renderedHtml}
|
|
28
|
+
></div>
|
|
29
|
+
</k-tab-content>
|
|
30
|
+
</k-tabs>
|
|
31
|
+
<div class="footer">
|
|
32
|
+
<slot name="controls-bottom">${e.bottom??i}</slot>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</k-resize>
|
|
36
|
+
`}static styles=e`
|
|
37
|
+
:host {
|
|
38
|
+
--padding: 0.5rem 0.75rem;
|
|
39
|
+
display: block;
|
|
40
|
+
}
|
|
41
|
+
:host([disabled]) {
|
|
42
|
+
opacity: 0.6;
|
|
43
|
+
}
|
|
44
|
+
:host([disabled]) k-tabs {
|
|
45
|
+
pointer-events: none;
|
|
46
|
+
}
|
|
47
|
+
/* readonly: textarea is read-only via the native attribute, but the
|
|
48
|
+
toolbar buttons mutate value programmatically, bypassing it. Mute
|
|
49
|
+
the controls-top wrapper and the footer so their buttons can't
|
|
50
|
+
fire. The Write/Preview tabs themselves stay interactive. */
|
|
51
|
+
:host([readonly]) .controls-top,
|
|
52
|
+
:host([readonly]) .footer {
|
|
53
|
+
pointer-events: none;
|
|
54
|
+
opacity: 0.5;
|
|
55
|
+
}
|
|
56
|
+
.controls-top {
|
|
57
|
+
display: inline-flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
flex-wrap: wrap;
|
|
60
|
+
}
|
|
61
|
+
k-resize {
|
|
62
|
+
width: 100%;
|
|
63
|
+
height: var(--height, 14rem);
|
|
64
|
+
background: var(--c_bg);
|
|
65
|
+
}
|
|
66
|
+
.frame {
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-direction: column;
|
|
69
|
+
height: 100%;
|
|
70
|
+
width: 100%;
|
|
71
|
+
min-height: 0;
|
|
72
|
+
box-sizing: border-box;
|
|
73
|
+
}
|
|
74
|
+
k-tabs {
|
|
75
|
+
flex: 1 1 auto;
|
|
76
|
+
min-height: 0;
|
|
77
|
+
display: flex;
|
|
78
|
+
flex-direction: column;
|
|
79
|
+
}
|
|
80
|
+
k-tab-content {
|
|
81
|
+
--spacer: 0;
|
|
82
|
+
}
|
|
83
|
+
k-tab-content[active] {
|
|
84
|
+
display: flex;
|
|
85
|
+
flex-direction: column;
|
|
86
|
+
padding: var(--spacer_q);
|
|
87
|
+
}
|
|
88
|
+
k-tab-content:not([active]) {
|
|
89
|
+
display: none;
|
|
90
|
+
}
|
|
91
|
+
.editor,
|
|
92
|
+
.preview {
|
|
93
|
+
box-sizing: border-box;
|
|
94
|
+
flex: 1 1 0;
|
|
95
|
+
min-height: 0;
|
|
96
|
+
width: 100%;
|
|
97
|
+
padding: var(--padding);
|
|
98
|
+
background: transparent;
|
|
99
|
+
color: var(--tc);
|
|
100
|
+
font: inherit;
|
|
101
|
+
line-height: 1.5;
|
|
102
|
+
overflow-y: auto;
|
|
103
|
+
scrollbar-width: thin;
|
|
104
|
+
resize: none;
|
|
105
|
+
}
|
|
106
|
+
.preview > :first-child { margin-top: 0; }
|
|
107
|
+
.preview > :last-child { margin-bottom: 0; }
|
|
108
|
+
.preview-empty {
|
|
109
|
+
color: var(--tc_muted);
|
|
110
|
+
font-style: italic;
|
|
111
|
+
}
|
|
112
|
+
.footer {
|
|
113
|
+
display: flex;
|
|
114
|
+
align-items: center;
|
|
115
|
+
gap: 0.25rem;
|
|
116
|
+
padding: 0.25rem 0.5rem;
|
|
117
|
+
flex: 0 0 auto;
|
|
118
|
+
border-top: 1px solid var(--c_border);
|
|
119
|
+
box-sizing: border-box;
|
|
120
|
+
}
|
|
121
|
+
.footer:not(:has(::slotted(*))) {
|
|
122
|
+
display: none;
|
|
123
|
+
}
|
|
124
|
+
`;static controlSets={"":{top:null,bottom:null},none:{top:null,bottom:null},minimal:{top:t`
|
|
125
|
+
<k-md-menu label="Heading">
|
|
126
|
+
<k-icon slot="trigger" name="text_fields"></k-icon>
|
|
127
|
+
<k-md-format-block tag="h1"></k-md-format-block>
|
|
128
|
+
<k-md-format-block tag="h3"></k-md-format-block>
|
|
129
|
+
<k-md-format-block tag="h5"></k-md-format-block>
|
|
130
|
+
</k-md-menu>
|
|
131
|
+
<k-md-bold></k-md-bold>
|
|
132
|
+
<k-md-italic></k-md-italic>
|
|
133
|
+
<k-md-bullet-list></k-md-bullet-list>
|
|
134
|
+
<k-md-numbered-list></k-md-numbered-list>
|
|
135
|
+
`,bottom:null},normal:{top:t`
|
|
136
|
+
<k-md-menu label="Heading">
|
|
137
|
+
<k-icon slot="trigger" name="text_fields"></k-icon>
|
|
138
|
+
<k-md-format-block tag="h1"></k-md-format-block>
|
|
139
|
+
<k-md-format-block tag="h2"></k-md-format-block>
|
|
140
|
+
<k-md-format-block tag="h3"></k-md-format-block>
|
|
141
|
+
<k-md-format-block tag="h4"></k-md-format-block>
|
|
142
|
+
<k-md-format-block tag="h5"></k-md-format-block>
|
|
143
|
+
<k-md-format-block tag="h6"></k-md-format-block>
|
|
144
|
+
</k-md-menu>
|
|
145
|
+
<k-md-bold></k-md-bold>
|
|
146
|
+
<k-md-italic></k-md-italic>
|
|
147
|
+
<k-md-quote></k-md-quote>
|
|
148
|
+
<k-md-code></k-md-code>
|
|
149
|
+
<k-md-link></k-md-link>
|
|
150
|
+
<k-md-bullet-list></k-md-bullet-list>
|
|
151
|
+
<k-md-numbered-list></k-md-numbered-list>
|
|
152
|
+
`,bottom:null},full:{top:t`
|
|
153
|
+
<k-md-menu label="Heading">
|
|
154
|
+
<k-icon slot="trigger" name="text_fields"></k-icon>
|
|
155
|
+
<k-md-format-block tag="h1"></k-md-format-block>
|
|
156
|
+
<k-md-format-block tag="h2"></k-md-format-block>
|
|
157
|
+
<k-md-format-block tag="h3"></k-md-format-block>
|
|
158
|
+
<k-md-format-block tag="h4"></k-md-format-block>
|
|
159
|
+
<k-md-format-block tag="h5"></k-md-format-block>
|
|
160
|
+
<k-md-format-block tag="h6"></k-md-format-block>
|
|
161
|
+
</k-md-menu>
|
|
162
|
+
<k-md-bold></k-md-bold>
|
|
163
|
+
<k-md-italic></k-md-italic>
|
|
164
|
+
<k-md-strikethrough></k-md-strikethrough>
|
|
165
|
+
<k-md-quote></k-md-quote>
|
|
166
|
+
<k-md-code></k-md-code>
|
|
167
|
+
<k-md-link></k-md-link>
|
|
168
|
+
<k-md-image></k-md-image>
|
|
169
|
+
<k-md-table></k-md-table>
|
|
170
|
+
<k-md-bullet-list></k-md-bullet-list>
|
|
171
|
+
<k-md-numbered-list></k-md-numbered-list>
|
|
172
|
+
<k-md-speech-to-text></k-md-speech-to-text>
|
|
173
|
+
`,bottom:null}}}customElements.define("k-markdown-editor",n);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{LitElement as e,html as t,css as i}from"../lit-all.min.js";import
|
|
1
|
+
import{LitElement as e,html as t,css as i}from"../lit-all.min.js";import s from"./ShadowComponent.js";import r from"./Icon.js";import d from"../utils/drag.js";export default class n extends s{static properties={resizing:{type:String,reflect:!0},dimention:{type:String,reflect:!0},disabled:{type:Boolean,reflect:!0}};constructor(){super(),this.resizing="",this.dimension="",this.disabled=!1,this.startSize={width:0,height:0},this.cleanupFuncs=[]}firstUpdated(){super.firstUpdated(),this.setupDragHandlers()}disconnectedCallback(){super.disconnectedCallback(),this.cleanupFuncs.forEach(e=>e())}dragStartHandler=({element:e})=>{if(this.disabled)return;const{width:t,height:i}=this.getBoundingClientRect();this.startSize.width=t,this.startSize.height=i,this.resizing=e.id};dragEndHandler=()=>{this.resizing=""};dragSideHandler=({x:e})=>{this.disabled||(this.style.width=this.startSize.width+e+"px")};dragBottomHandler=({y:e})=>{this.disabled||(this.style.height=this.startSize.height+e+"px")};dragCornerHandler=({x:e,y:t})=>{this.disabled||(this.style.width=this.startSize.width+e+"px",this.style.height=this.startSize.height+t+"px")};setupDragHandlers(){const e=this.shadowRoot.getElementById("side"),t=this.shadowRoot.getElementById("bottom"),i=this.shadowRoot.getElementById("corner");e&&this.cleanupFuncs.push(d({element:e,startCallback:this.dragStartHandler,endCallback:this.dragEndHandler,callback:this.dragSideHandler,preventScroll:!0})),t&&this.cleanupFuncs.push(d({element:t,startCallback:this.dragStartHandler,endCallback:this.dragEndHandler,callback:this.dragBottomHandler,preventScroll:!0})),i&&this.cleanupFuncs.push(d({element:i,startCallback:this.dragStartHandler,endCallback:this.dragEndHandler,callback:this.dragCornerHandler,preventScroll:!0}))}static styles=i`
|
|
2
2
|
:host {
|
|
3
3
|
--handle_size: 1rem;
|
|
4
4
|
|
|
@@ -75,6 +75,16 @@ import{LitElement as e,html as t,css as i}from"../lit-all.min.js";import r from"
|
|
|
75
75
|
:host([dimension="width"]) #corner {
|
|
76
76
|
display: none;
|
|
77
77
|
}
|
|
78
|
+
/* Disabled: handles can't capture pointer events, so a drag can't
|
|
79
|
+
start in the first place; visual cue: handles fade out and the
|
|
80
|
+
cursor reverts to default. */
|
|
81
|
+
:host([disabled]) #side,
|
|
82
|
+
:host([disabled]) #bottom,
|
|
83
|
+
:host([disabled]) #corner {
|
|
84
|
+
pointer-events: none;
|
|
85
|
+
cursor: default;
|
|
86
|
+
opacity: 0.4;
|
|
87
|
+
}
|
|
78
88
|
`;render(){return t`
|
|
79
89
|
<div id="main">
|
|
80
90
|
<slot></slot>
|
|
@@ -94,4 +104,4 @@ import{LitElement as e,html as t,css as i}from"../lit-all.min.js";import r from"
|
|
|
94
104
|
<path fill="currentColor" d="m12.735 3.0095e-4c-0.77288 0.011702-1.6098 0.36368-2.2773 1.0313l-9.4258 9.4258c-1.1868 1.1868-1.3772 2.908-0.42773 3.8574 0.94943 0.94943 2.6687 0.7571 3.8555-0.42969l9.4258-9.4258c1.1868-1.1868 1.3772-2.906 0.42774-3.8555-0.41537-0.41537-0.977-0.61262-1.5781-0.60352zm0 8.5684c-0.77288 0.011702-1.6098 0.36564-2.2773 1.0332l-0.85744 0.85546c-1.1868 1.1868-1.3772 2.908-0.42773 3.8574 0.94943 0.94943 2.6687 0.7571 3.8555-0.42969l0.85742-0.85742c1.1868-1.1868 1.3772-2.906 0.42773-3.8555-0.41537-0.41537-0.977-0.61262-1.5781-0.60352z" />
|
|
95
105
|
</svg>
|
|
96
106
|
</div>
|
|
97
|
-
`}}customElements.define("k-resize",
|
|
107
|
+
`}}customElements.define("k-resize",n);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{html as
|
|
1
|
+
import{html as t,css as e}from"../lit-all.min.js";import n from"./ShadowComponent.js";import{getVoice as s}from"../utils/voice.js";import"./Icon.js";export default class i extends n{static properties={text:{type:String,reflect:!0},voice:{type:String,reflect:!0},language:{type:String,reflect:!0},rate:{type:Number,reflect:!0},pitch:{type:Number,reflect:!0},volume:{type:Number,reflect:!0},disabled:{type:Boolean,reflect:!0},speaking:{type:Boolean,reflect:!0}};#t=!1;#e=null;constructor(){super(),this.text="",this.voice="",this.language="",this.rate=1,this.pitch=1,this.volume=1,this.disabled=!1,this.speaking=!1}connectedCallback(){super.connectedCallback(),"undefined"!=typeof window&&window.speechSynthesis&&"function"==typeof window.SpeechSynthesisUtterance||(this.#t=!0)}disconnectedCallback(){super.disconnectedCallback(),this.stop()}get effectiveText(){return this.text?this.text:this.textContent.trim()}resolveVoice=()=>{if(this.#t)return null;const t=this.voice||s()||"";if(!t)return null;const e=window.speechSynthesis.getVoices(),n=t.split(",").map(t=>t.trim()).filter(Boolean);for(const t of n){const n=e.find(e=>e.name===t);if(n)return n}return null};speak=t=>{if(this.disabled||this.#t)return;const e=(void 0!==t?String(t):this.effectiveText).trim();if(!e)return;window.speechSynthesis.cancel();const n=new window.SpeechSynthesisUtterance(e);n.rate=this.rate,n.pitch=this.pitch,n.volume=this.volume,this.language&&(n.lang=this.language);const s=this.resolveVoice();s&&(n.voice=s),n.onstart=this.handleStart,n.onend=this.handleEnd,n.onerror=this.handleError,this.#e=n,window.speechSynthesis.speak(n)};stop=()=>{this.#t||window.speechSynthesis.cancel()};toggle=()=>{this.speaking?this.stop():this.speak()};handleStart=()=>{this.speaking=!0,this.dispatchEvent(new CustomEvent("start",{bubbles:!0}))};handleEnd=()=>{this.speaking=!1,this.#e=null,this.dispatchEvent(new CustomEvent("end",{bubbles:!0}))};handleError=t=>{this.speaking=!1,this.#e=null,this.dispatchEvent(new CustomEvent("error",{detail:{error:t.error||"unknown"},bubbles:!0}))};handleClick=()=>{this.disabled||this.toggle()};render(){const e=!this.#t,n=e?this.speaking?"Stop speaking":"Speak":"Text-to-speech not supported in this browser";return t`
|
|
2
2
|
<button
|
|
3
3
|
type="button"
|
|
4
4
|
class="no-btn btn"
|
|
5
|
-
?disabled=${this.disabled||!
|
|
5
|
+
?disabled=${this.disabled||!e}
|
|
6
6
|
@click=${this.handleClick}
|
|
7
7
|
aria-label=${n}
|
|
8
8
|
title=${n}
|
|
@@ -10,7 +10,7 @@ import{html as e,css as t}from"../lit-all.min.js";import n from"./ShadowComponen
|
|
|
10
10
|
<k-icon name=${this.speaking?"stop":"record_voice_over"}></k-icon>
|
|
11
11
|
</button>
|
|
12
12
|
<slot style="display:none"></slot>
|
|
13
|
-
`}static styles=
|
|
13
|
+
`}static styles=e`
|
|
14
14
|
:host {
|
|
15
15
|
--btn_size: 2.5rem;
|
|
16
16
|
--btn_bg: var(--c_bg);
|
|
@@ -53,4 +53,4 @@ import{html as e,css as t}from"../lit-all.min.js";import n from"./ShadowComponen
|
|
|
53
53
|
.btn:disabled {
|
|
54
54
|
cursor: not-allowed;
|
|
55
55
|
}
|
|
56
|
-
`}customElements.define("k-text-to-speech",
|
|
56
|
+
`}customElements.define("k-text-to-speech",i);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import{html as e,css as t}from"../lit-all.min.js";import s from"./ShadowComponent.js";import{getVoice as n,setVoice as i,subscribeToVoice as a,subscribeToAvailableVoices as l}from"../utils/voice.js";const o=e=>(e||"").split("-")[0].toLowerCase();let c=null;try{if("undefined"!=typeof Intl&&Intl.DisplayNames){const e="undefined"!=typeof navigator&&navigator.language||"en";c=new Intl.DisplayNames([e],{type:"language"})}}catch(e){}const r=new Map,u=e=>{let t=e;if(c)try{t=c.of(e)||e}catch(e){}const s=(e=>{if(r.has(e))return r.get(e);let t="";try{"undefined"!=typeof Intl&&Intl.DisplayNames&&(t=new Intl.DisplayNames([e],{type:"language"}).of(e)||"")}catch(e){}return t&&(t=t.charAt(0).toUpperCase()+t.slice(1)),r.set(e,t),t})(e);return s&&s.toLowerCase()!==t.toLowerCase()?`${t} (${s})`:t};export default class h extends s{static properties={language:{type:String,reflect:!0},placeholder:{type:String,reflect:!0},disabled:{type:Boolean,reflect:!0},voices:{state:!0},selected:{state:!0},currentLang:{state:!0}};#e=null;#t=null;#s=!1;#n=!1;constructor(){super(),this.language="",this.placeholder="Browser default",this.disabled=!1,this.voices=[],this.selected="",this.currentLang=""}connectedCallback(){super.connectedCallback(),"undefined"!=typeof window&&window.speechSynthesis?(this.#e=l(e=>{this.voices=e,this.#i()}),this.#t=a(e=>{if(this.selected=e||"",e){const t=this.voices.find(t=>t.name===e);t&&t.lang&&(this.currentLang=o(t.lang))}})):this.#s=!0}disconnectedCallback(){super.disconnectedCallback(),this.#e&&this.#e(),this.#t&&this.#t(),this.#e=null,this.#t=null}#i=()=>{if(this.#n||0===this.voices.length)return;this.#n=!0;const e=this.selected?this.voices.find(e=>e.name===this.selected):null;if(e&&e.lang)return void(this.currentLang=o(e.lang));if(this.language)return void(this.currentLang=o(this.language));const t="undefined"==typeof navigator?"en":o(navigator.language||"en")||"en",s=this.availableLanguages;s.includes(t)?this.currentLang=t:s.length>0&&(this.currentLang=s[0])};get availableLanguages(){const e=new Set;for(const t of this.voices)t.lang&&e.add(o(t.lang));return[...e].sort()}get voicesForCurrentLanguage(){return this.currentLang?this.voices.filter(e=>o(e.lang)===this.currentLang):this.voices}handleLanguageChange=e=>{if(this.disabled)return;this.currentLang=e.target.value;this.voicesForCurrentLanguage.some(e=>e.name===this.selected)||i("")};handleVoiceChange=e=>{if(this.disabled)return;const t=e.target.value;i(t),this.dispatchEvent(new CustomEvent("change",{detail:{voice:t,language:this.currentLang},bubbles:!0}))};refresh=()=>{this.#s||(this.voices=window.speechSynthesis.getVoices(),this.#i())};render(){if(this.#s)return e`
|
|
2
|
+
<select disabled aria-label="Voice selector (unsupported)">
|
|
3
|
+
<option>Speech synthesis not supported</option>
|
|
4
|
+
</select>
|
|
5
|
+
`;const t=this.availableLanguages,s=this.voicesForCurrentLanguage;return e`
|
|
6
|
+
<div class="row">
|
|
7
|
+
<div class="col" style="min-width: 10rem; flex: 0 0 auto">
|
|
8
|
+
<select
|
|
9
|
+
class="lang"
|
|
10
|
+
aria-label="Language"
|
|
11
|
+
?disabled=${this.disabled||0===t.length}
|
|
12
|
+
.value=${this.currentLang}
|
|
13
|
+
@change=${this.handleLanguageChange}
|
|
14
|
+
>
|
|
15
|
+
${t.map(t=>e`
|
|
16
|
+
<option value=${t} ?selected=${t===this.currentLang}>${u(t)}</option>
|
|
17
|
+
`)}
|
|
18
|
+
</select>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="col">
|
|
21
|
+
<select
|
|
22
|
+
class="voice"
|
|
23
|
+
aria-label="Voice"
|
|
24
|
+
?disabled=${this.disabled}
|
|
25
|
+
.value=${this.selected}
|
|
26
|
+
@change=${this.handleVoiceChange}
|
|
27
|
+
>
|
|
28
|
+
<option value="">${this.placeholder}</option>
|
|
29
|
+
${s.map(t=>{return e`
|
|
30
|
+
<option
|
|
31
|
+
value=${t.name}
|
|
32
|
+
?selected=${t.name===this.selected}
|
|
33
|
+
>${s=t.name,s.split("(")[0].trim()||s}</option>
|
|
34
|
+
`;var s})}
|
|
35
|
+
</select>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
`}static styles=t`
|
|
39
|
+
:host {
|
|
40
|
+
display: block;
|
|
41
|
+
}
|
|
42
|
+
:host([disabled]) {
|
|
43
|
+
opacity: 0.5;
|
|
44
|
+
pointer-events: none;
|
|
45
|
+
}
|
|
46
|
+
select {
|
|
47
|
+
width: 100%;
|
|
48
|
+
}
|
|
49
|
+
`}customElements.define("k-voice-selector",h);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import t from"./MarkdownEditorControl.js";import{html as l}from"../../lit-all.min.js";import"../Icon.js";export default class o extends t{constructor(){super(),this.label="Bold (Cmd/Ctrl+B)"}command(){this.wrapSelection("**","**","bold text")}render(){return l`
|
|
2
|
+
<button
|
|
3
|
+
type="button"
|
|
4
|
+
class=${this.btnClass}
|
|
5
|
+
title=${this.label}
|
|
6
|
+
aria-label=${this.label}
|
|
7
|
+
@click=${this.handleClick}
|
|
8
|
+
>
|
|
9
|
+
<k-icon name="format_bold"></k-icon>
|
|
10
|
+
</button>
|
|
11
|
+
`}}customElements.define("k-md-bold",o);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import t from"./MarkdownEditorControl.js";import{html as l}from"../../lit-all.min.js";import"../Icon.js";export default class e extends t{constructor(){super(),this.label="Bulleted list"}command(){this.insertLinePrefix("- ")}render(){return l`
|
|
2
|
+
<button
|
|
3
|
+
type="button"
|
|
4
|
+
class=${this.btnClass}
|
|
5
|
+
title=${this.label}
|
|
6
|
+
aria-label=${this.label}
|
|
7
|
+
@click=${this.handleClick}
|
|
8
|
+
>
|
|
9
|
+
<k-icon name="format_list_bulleted"></k-icon>
|
|
10
|
+
</button>
|
|
11
|
+
`}}customElements.define("k-md-bullet-list",e);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import t from"./MarkdownEditorControl.js";import{html as e}from"../../lit-all.min.js";import"../Icon.js";export default class o extends t{constructor(){super(),this.label="Code"}command(){this.getSelection().text.includes("\n")?this.wrapSelection("```\n","\n```","code"):this.wrapSelection("`","`","code")}render(){return e`
|
|
2
|
+
<button
|
|
3
|
+
type="button"
|
|
4
|
+
class=${this.btnClass}
|
|
5
|
+
title=${this.label}
|
|
6
|
+
aria-label=${this.label}
|
|
7
|
+
@click=${this.handleClick}
|
|
8
|
+
>
|
|
9
|
+
<k-icon name="code"></k-icon>
|
|
10
|
+
</button>
|
|
11
|
+
`}}customElements.define("k-md-code",o);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import t from"./MarkdownEditorControl.js";import{html as e,css as s}from"../../lit-all.min.js";import"../Icon.js";const a={h1:"Heading 1",h2:"Heading 2",h3:"Heading 3",h4:"Heading 4",h5:"Heading 5",h6:"Heading 6"},i={h1:"format_h1",h2:"format_h2",h3:"format_h3",h4:"format_h4",h5:"format_h5",h6:"format_h6"};export default class o extends t{static properties={...t.properties,tag:{type:String,reflect:!0}};constructor(){super(),this.tag="h2",this.label=""}command(){const t=(this.tag||"h2").toLowerCase();if(/^h[1-6]$/.test(t)){const e=parseInt(t.slice(1),10);this.insertLinePrefix("#".repeat(e)+" ",/^#{1,6} /)}}get#t(){return this.label?this.label:a[(this.tag||"h2").toLowerCase()]||this.tag}get#e(){return i[(this.tag||"h2").toLowerCase()]||"format_h2"}render(){return e`
|
|
2
|
+
<button
|
|
3
|
+
type="button"
|
|
4
|
+
class=${this.btnClass}
|
|
5
|
+
title=${this.#t}
|
|
6
|
+
aria-label=${this.#t}
|
|
7
|
+
@click=${this.handleClick}
|
|
8
|
+
>
|
|
9
|
+
<k-icon name=${this.#e}></k-icon>
|
|
10
|
+
<span class="fb-label">${this.#t}</span>
|
|
11
|
+
</button>
|
|
12
|
+
`}static styles=[t.styles,s`
|
|
13
|
+
:host {
|
|
14
|
+
display: inline-flex;
|
|
15
|
+
}
|
|
16
|
+
.ctrl {
|
|
17
|
+
gap: 0.5rem;
|
|
18
|
+
justify-content: flex-start;
|
|
19
|
+
white-space: nowrap;
|
|
20
|
+
}
|
|
21
|
+
/* When this is slotted into something that stretches it (a menu),
|
|
22
|
+
the button fills the row instead of staying compact. */
|
|
23
|
+
:host([slot]) .ctrl,
|
|
24
|
+
:host(*) .ctrl {
|
|
25
|
+
width: 100%;
|
|
26
|
+
}
|
|
27
|
+
.fb-label {
|
|
28
|
+
font-size: 0.875rem;
|
|
29
|
+
}
|
|
30
|
+
`]}customElements.define("k-md-format-block",o);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import t from"./MarkdownEditorControl.js";import{html as e}from"../../lit-all.min.js";import"../Icon.js";export default class i extends t{static properties={...t.properties,level:{type:Number,reflect:!0}};constructor(){super(),this.level=2,this.label="Heading"}command(){const t=Math.min(6,Math.max(1,Number(this.level)||2));this.insertLinePrefix("#".repeat(t)+" ",/^#{1,6} /)}render(){const t=Math.min(6,Math.max(1,Number(this.level)||2));return e`
|
|
2
|
+
<button
|
|
3
|
+
type="button"
|
|
4
|
+
class=${this.btnClass}
|
|
5
|
+
title="${this.label} ${t}"
|
|
6
|
+
aria-label="${this.label} ${t}"
|
|
7
|
+
@click=${this.handleClick}
|
|
8
|
+
>
|
|
9
|
+
<k-icon name="format_h${t}"></k-icon>
|
|
10
|
+
</button>
|
|
11
|
+
`}}customElements.define("k-md-heading",i);
|