diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 3495570ff..d677dc65e 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -38,15 +38,30 @@ const themes = { default: defaultCM5Theme, ...cm5Themes, darkbrewery }; const themeCompartment = new Compartment(); const highlightCompartment = new Compartment(); -console.log(themes); - import { generalKeymap, markdownKeymap } from './extensions/customKeyMaps.js'; import foldOnPages from './extensions/customFolding.js'; import { customHighlightStyle, tokenizeCustomMarkdown, tokenizeCustomCSS } from './extensions/customHighlight.js'; import { legacyCustomHighlightStyle, legacyTokenizeCustomMarkdown } from './extensions/legacyCustomHighlight.js'; +import { syntaxTree } from '@codemirror/language'; const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m; +function getUrl(node, doc) { + let url = null; + + const cursor = node.node.cursor(); + + if (cursor.firstChild()) { + do { + if (cursor.name === "URL") { + url = doc.sliceString(cursor.from, cursor.to); + break; + } + } while (cursor.nextSibling()); + } + + return url; +} const createHighlightPlugin = (renderer, tab)=>{ //this function takes the custom tokens created in the tokenize function in customhighlight files //takes the tokens defined by that function and assigns classes to them @@ -76,21 +91,84 @@ const createHighlightPlugin = (renderer, tab)=>{ let pageCount = 1; let snippetCount = 0; + const tree = syntaxTree(view.state); +tree.iterate({ + enter: (node) => { + if (node.name === "Image") { + const url = getUrl(node, view.state.doc); + + if (!url) return; + + decos.push( + Decoration.mark({ + class: "cm-image", + attributes: { + "style": `--preview-img:url(${url});` + } + }).range(node.from, node.to) + ); + } + } +}); + + tokens.forEach((tok)=>{ const line = view.state.doc.line(tok.line + 1); + // INLINE TOKENS (marks) if(tok.from != null && tok.to != null && tok.from < tok.to) { - decos.push(Decoration.mark({ class: `cm-${tok.type}` }).range(line.from + tok.from, line.from + tok.to)); - } else { - decos.push(Decoration.line({ class: `cm-${tok.type}` }).range(line.from)); - if(tok.type === 'pageLine' && tab === 'brewText') { - pageCount++; - line.from === 0 && pageCount--; - decos.push(Decoration.line({ attributes: { 'data-page-number': pageCount } }).range(line.from)); + const from = line.from + tok.from; + const to = line.from + tok.to; + + const attrs = {}; + console.log(tok); + // attach URL only for links + if(tok.type === 'Image' && tok.url) { + + attrs['data-url'] = tok.url; } + + decos.push( + Decoration.mark({ + class : `cm-${tok.type}`, + ...(Object.keys(attrs).length + ? { attributes: attrs } + : {}) + }).range(from, to) + ); + } + + // LINE TOKENS + else { + decos.push( + Decoration.line({ + class : `cm-${tok.type}` + }).range(line.from) + ); + + if(tok.type === 'pageLine' && tab === 'brewText') { + pageCount++; + if(line.from === 0) pageCount--; + + decos.push( + Decoration.line({ + attributes : { + 'data-page-number' : pageCount + } + }).range(line.from) + ); + } + if(tok.type === 'snippetLine' && tab === 'brewSnippets') { snippetCount++; - decos.push(Decoration.line({ attributes: { 'data-page-number': snippetCount } }).range(line.from)); + + decos.push( + Decoration.line({ + attributes : { + 'data-page-number' : snippetCount + } + }).range(line.from) + ); } } }); @@ -99,7 +177,9 @@ const createHighlightPlugin = (renderer, tab)=>{ return Decoration.set(decos); } }, - { decorations: (v)=>v.decorations } + { + decorations : (v)=>v.decorations + } ); }; diff --git a/client/components/codeEditor/codeEditor.less b/client/components/codeEditor/codeEditor.less index 84ed8058d..3594742fd 100644 --- a/client/components/codeEditor/codeEditor.less +++ b/client/components/codeEditor/codeEditor.less @@ -157,6 +157,44 @@ outline : 1px inset #00000055 !important; } + .cm-image[style] { + position: relative; + + &::before, &::after { + content:""; + width:100px; + height:100px; + position:absolute; + bottom:0; + left:0; + translate:0 100%; + display:block; + z-index:1000; + pointer-events: none; + opacity:0; + transition:0.2s opacity; + } + + &::before{ + background-color: #fff; + border-radius:10px; + border:3px solid grey; + } + + &::after { + background-image: var(--preview-img); + background-size:80%; + background-repeat:no-repeat; + background-position:center; + filter:drop-shadow(0 0 5px #0008); + } + + &:hover::before, + &:hover::after { + opacity:1; + } + } + /* Tab character visualization (optional) */ //.cm-tab { // background: url(...) no-repeat right; diff --git a/client/components/codeEditor/extensions/customHighlight.js b/client/components/codeEditor/extensions/customHighlight.js index 8797704cc..92761a78b 100644 --- a/client/components/codeEditor/extensions/customHighlight.js +++ b/client/components/codeEditor/extensions/customHighlight.js @@ -182,13 +182,15 @@ export function tokenizeCustomMarkdown(text) { } if(lineText.includes('{') && lineText.includes('}')) { - const injectionRegex = /(?:^|[^{\n])({(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\2})/gm; + const injectionRegex = /(?:^|[^{\n])({(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\2})/gmd; let match; + while ((match = injectionRegex.exec(lineText)) !== null) { + console.log(match.indices[1][0]); tokens.push({ line : lineNumber, - from : match.index, - to : match.index + match[1].length, + from : match.indices[1][0], + to : match.indices[1][0] + match[1].length, type : customTags.injection, }); }