From cae2962a199d8e75fad2d151d251c2b70151f4b5 Mon Sep 17 00:00:00 2001 From: Aleksey Fomkin Date: Fri, 27 Aug 2021 11:33:31 +0300 Subject: [PATCH 1/2] close #31: add dynamic wc-content --- src/wc-codemirror.js | 72 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/src/wc-codemirror.js b/src/wc-codemirror.js index 6f860be..396d684 100644 --- a/src/wc-codemirror.js +++ b/src/wc-codemirror.js @@ -46,11 +46,39 @@ export class WCCodeMirror extends HTMLElement { get value () { return this.editor.getValue() } set value (value) { - this.setValue(value) + if (this.__initialized) { + this.setValueForced(value) + } else { + // Save to pre init + this.__preInitValue = value + } } constructor () { super() + + const observerConfig = { + childList: true, + characterData: true, + subtree: true + } + + const linkChanged = (e) => { + return e.type === "childList" && + (Array.from(e.addedNodes).some((e) => e.tagName === "LINK") || + Array.from(e.removedNodes).some((e) => e.tagName === "LINK")) + } + + this.__observer = new MutationObserver((mutationsList, observer) => { + if (mutationsList.some(linkChanged)) { + this.refreshStyles(); + } + this.lookupInnerScript((data) => { + this.value = data + }) + }) + this.__observer.observe(this, observerConfig) + this.__initialized = false this.__element = null this.editor = null @@ -60,7 +88,7 @@ export class WCCodeMirror extends HTMLElement { // Create template const shadow = this.attachShadow({ mode: 'open' }) const template = document.createElement('template') - const stylesheet = document.createElement("style") + const stylesheet = document.createElement('style') stylesheet.innerHTML = CODE_MIRROR_CSS_CONTENT template.innerHTML = WCCodeMirror.template() shadow.appendChild(stylesheet) @@ -78,14 +106,9 @@ export class WCCodeMirror extends HTMLElement { this.refreshStyles() - let content = '' - const innerScriptTag = this.querySelector('script') - if (innerScriptTag) { - if (innerScriptTag.getAttribute('type') === 'wc-content') { - content = WCCodeMirror.dedentText(innerScriptTag.innerHTML) - content = content.replace(/<(\/?script)(.*?)>/g, '<$1$2>') - } - } + this.lookupInnerScript((data) => { + this.value = data + }) let viewportMargin = CodeMirror.defaults.viewportMargin if (this.hasAttribute('viewport-margin')) { @@ -102,20 +125,23 @@ export class WCCodeMirror extends HTMLElement { }) if (this.hasAttribute('src')) { - this.setSrc(this.getAttribute('src')) - } else { - // delay until editor initializes - await new Promise(resolve => setTimeout(resolve, 50)) - this.value = content + this.setSrc() } + // delay until editor initializes + await new Promise(resolve => setTimeout(resolve, 50)) this.__initialized = true + + if (this.__preInitValue !== undefined) { + this.setValueForced(this.__preInitValue) + } } disconnectedCallback () { this.editor && this.editor.toTextArea() this.editor = null this.__initialized = false + this.__observer.disconnect() } async setSrc () { @@ -124,7 +150,10 @@ export class WCCodeMirror extends HTMLElement { this.value = contents } - async setValue (value) { + /** + * Set value without initialization check + */ + async setValueForced (value) { this.editor.swapDoc(CodeMirror.Doc(value, this.getAttribute('mode'))) this.editor.refresh() } @@ -155,6 +184,17 @@ export class WCCodeMirror extends HTMLElement { ` } + lookupInnerScript (callback) { + const innerScriptTag = this.querySelector('script') + if (innerScriptTag) { + if (innerScriptTag.getAttribute('type') === 'wc-content') { + let data = WCCodeMirror.dedentText(innerScriptTag.innerHTML) + data = data.replace(/<(\/?script)(.*?)>/g, '<$1$2>') + callback(data) + } + } + } + /** * De-dents the code by getting the padding from the first line, * then removes the same indent amount padding from the rest of the lines From 7a04c2be34d27ad40574d2c2d3ae4f1f21eea787 Mon Sep 17 00:00:00 2001 From: Aleksey Fomkin Date: Sat, 28 Aug 2021 10:43:03 +0300 Subject: [PATCH 2/2] close #35: add the way to contol text marks from DOM --- README.md | 13 +++++ src/wc-codemirror.js | 115 +++++++++++++++++++++++++++++++++---------- 2 files changed, 102 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index cb41cf7..9a0c121 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,19 @@ Then specify the language with the `mode` attribute ``` +### Text-marking + +Can be used to mark a range of text with a specific CSS class name. + +```html + + + + +``` + ### Theming Theming requires importing an editor theme stylesheet within `wc-codemirror` tag. You can import few themes this way and switch them with the `theme` attribute. diff --git a/src/wc-codemirror.js b/src/wc-codemirror.js index 396d684..e68cdf3 100644 --- a/src/wc-codemirror.js +++ b/src/wc-codemirror.js @@ -63,22 +63,61 @@ export class WCCodeMirror extends HTMLElement { subtree: true } - const linkChanged = (e) => { - return e.type === "childList" && - (Array.from(e.addedNodes).some((e) => e.tagName === "LINK") || - Array.from(e.removedNodes).some((e) => e.tagName === "LINK")) + const nodeListContainsTag = (nodeList, tag) => { + const checkThatTag = (e) => e.tagName === tag + const removed = Array.from(nodeList) + return removed.some(checkThatTag) } + const mutContainsRemovedTag = (tag) => (record) => { + return nodeListContainsTag(record.removedNodes, tag) + } + + const mutContainsAddedTag = (tag) => (record) => { + return nodeListContainsTag(record.addedNodes, tag) + } + + const mutContainsTag = (tag) => { + const containsAdded = mutContainsAddedTag(tag) + const containsRemoved = mutContainsRemovedTag(tag) + return (record) => containsAdded(record) || containsRemoved(record) + } + + const mutContainsLink = mutContainsTag('LINK') + const mutContainsMarkText = mutContainsTag('MARK-TEXT') + const mutContainsRemovedScript = mutContainsRemovedTag('SCRIPT') + const mutContainsAddedScript = mutContainsAddedTag('SCRIPT') + this.__observer = new MutationObserver((mutationsList, observer) => { - if (mutationsList.some(linkChanged)) { - this.refreshStyles(); - } - this.lookupInnerScript((data) => { - this.value = data + let doRefreshMarks = false + mutationsList.forEach((record) => { + if (record.type === 'childList') { + if (mutContainsLink(record)) { + this.refreshStyleLinks() + } + if (mutContainsRemovedScript(record)) { + this.value = '' + } + if (mutContainsAddedScript(record)) { + this.refrestWcContent() + } + if (mutContainsMarkText(record)) { + doRefreshMarks = true + } + } else if (record.type === 'characterData') { + // Text data had been chaged. It's a reason to + // check wc-content + this.refrestWcContent() + } }) + if (doRefreshMarks) { + this.refreshMarkText() + } }) + this.__observer.observe(this, observerConfig) + this.__textMarks = [] this.__initialized = false this.__element = null this.editor = null @@ -104,12 +143,6 @@ export class WCCodeMirror extends HTMLElement { if (readOnly === '') readOnly = true else if (readOnly !== 'nocursor') readOnly = false - this.refreshStyles() - - this.lookupInnerScript((data) => { - this.value = data - }) - let viewportMargin = CodeMirror.defaults.viewportMargin if (this.hasAttribute('viewport-margin')) { const viewportMarginAttr = this.getAttribute('viewport-margin').toLowerCase() @@ -124,6 +157,9 @@ export class WCCodeMirror extends HTMLElement { viewportMargin }) + this.refreshStyleLinks() + this.refrestWcContent() + if (this.hasAttribute('src')) { this.setSrc() } @@ -135,6 +171,9 @@ export class WCCodeMirror extends HTMLElement { if (this.__preInitValue !== undefined) { this.setValueForced(this.__preInitValue) } + + // This should be invoked after text set + this.refreshMarkText() } disconnectedCallback () { @@ -163,7 +202,7 @@ export class WCCodeMirror extends HTMLElement { return response.text() } - refreshStyles () { + refreshStyleLinks () { // Remove all element in shadow root Array.from(this.shadowRoot.children).forEach(element => { if (element.tagName === 'LINK' && element.getAttribute('rel') === 'stylesheet') { @@ -178,23 +217,47 @@ export class WCCodeMirror extends HTMLElement { }) } - static template () { - return ` - - ` - } - - lookupInnerScript (callback) { + refrestWcContent () { const innerScriptTag = this.querySelector('script') if (innerScriptTag) { if (innerScriptTag.getAttribute('type') === 'wc-content') { - let data = WCCodeMirror.dedentText(innerScriptTag.innerHTML) - data = data.replace(/<(\/?script)(.*?)>/g, '<$1$2>') - callback(data) + const data = WCCodeMirror.dedentText(innerScriptTag.innerHTML) + this.value = data.replace(/<(\/?script)(.*?)>/g, '<$1$2>') } } } + refreshMarkText () { + // Remove all old marks + this.__textMarks.forEach(element => { + element.clear() + }) + this.__textMarks = Array.from(this.children) + .filter(element => element.tagName === 'MARK-TEXT') + .map(element => { + try { + const fromLine = parseInt(element.getAttribute('from-line')) + const fromChar = parseInt(element.getAttribute('from-char')) + const toLine = parseInt(element.getAttribute('to-line')) + const toChar = parseInt(element.getAttribute('to-char')) + const options = JSON.parse(element.getAttribute('options').replace(/'/g, '"')) + const from = { line: fromLine, ch: fromChar } + const to = { line: toLine, ch: toChar } + return this.editor.markText(from, to, options) + } catch (error) { + console.error(error) + // Return ermpty descriptor + return { clear: () => {} } + } + }) + } + + static template () { + return ` + + ` + } + /** * De-dents the code by getting the padding from the first line, * then removes the same indent amount padding from the rest of the lines