UNPKG

simple-syntax-highlighter

Version:
318 lines (317 loc) 18.2 kB
import { ref as S, useSlots as _, computed as j, openBlock as $, createElementBlock as C, normalizeClass as G, renderSlot as W, createTextVNode as R, createCommentVNode as V, createElementVNode as K } from "vue"; const Y = ["data-type", "data-label"], X = ["contenteditable", "innerHTML"], J = { __name: "simple-syntax-highlighter", props: { language: { type: String, default: "" }, label: { type: [String, Boolean], default: !1 }, // The characters that should be inserted on tab key press. tab: { type: [Boolean, String], default: " " }, dark: { type: Boolean, default: !1 }, copyButton: { type: Boolean, default: !1 }, editable: { type: Boolean, default: !1 } }, emits: ["copied"], setup(m, { emit: A }) { const o = { quote: /("(?:\\"|[^"])*")|('(?:\\'|[^'])*')/, // Match simple and double quotes by pair. comment: /(\/\/.*?(?:\n|$)|\/\*.*?(?:\*\/|$))/, // Trailing (// ...) or blocks (/* ... */) comments. doctype: /(&lt;!DOCTYPE.*?&gt;)/, // Doctype is case insensitive. // A tag captures everything between < and >, and handles: <tag>, or <tag/>, or </tag>. // The part `(?:[^&]|&(?:lt|amp|quot|apos|gt);)*?)` makes sure there can be &amp; inside // quoted attribute value (`attr="& < >"` or `attr='& < >'`). htmlTag: /&lt;(?:([a-z][\w:-]*)((?:[\w\- ]+=(?:"[^"]*"|'[^']*'))*|(?:[^&]|&(?:lt|amp|quot|apos|gt);)*?)(\s*\/?)|(\/?)([a-z][\w:-]*))&gt;/, htmlentity: /(&amp;(?:[a-z]+|#x?\d+);)/, // Punctuation outside of html tags, quotes and comments. punctuation: new RegExp("(!==?|(?:[[\\](){}.:,+\\-?=!])+|(?<!&(?:[a-z]+|#x?\\d+));|\\|\\||&lt;|&gt;|&amp;)"), number: /(-?(?:\.\d+|\d+(?:\.\d+)?))/, boolean: /\b(true|false)\b/ }, h = { shell: { quote: o.quote, comment: /(#.*?)(?:\n|$)/, keyword: /\b(p?npm|yarn|i(?:nstall)?|run|audit|outdated|update|publish|whoami|cd|sudo|chmod|chown|ls|cat|vim?|scp|rm|mv|mkdir|ln|open|cwd|which|clear|curl|ping|systemctl|echo|export|open|bash|exit)\b/, param: /( --(?:save|save-dev))(?:\s|$)/ }, xml: { doctype: o.doctype, quote: o.quote, comment: /(&lt;!--.*?(?:--&gt;|$))/, htmlentity: o.htmlentity, tag: o.htmlTag }, html: { doctype: o.doctype, quote: o.quote, comment: /(&lt;!--.*?(?:--&gt;|$))/, htmlentity: o.htmlentity, tag: o.htmlTag }, "html-vue": { doctype: o.doctype, quote: o.quote, comment: /(&lt;!--.*?(?:--&gt;|$))/, htmlentity: o.htmlentity, tag: /&lt;(?:([a-z][\w:-]*)((?:[\w-]+\s*=(?:"[^"]*"|'[^']*'))*|(?:[^&]|&(?:lt|amp|quot|apos|gt);)*?)(\s*\/?)|(\/?)([a-z][\w:-]*))&gt;/ }, // @todo: support Pug inline tags like `#[em italic]`. pug: { // Text match for this syntax: // tag // | text text: /((?:^|\n)[ \t]*|^)\|([ \t]*)([^\n]+(?=\s*(?:\n|$)))/, // Text match for this syntax: // tag. // text // See caveat #3 (backreferences). text2: /([ \t]*)([.#\w-]+(?:\([^)]*\))*)\.\n((?:\n+(?=\4[ \t]+)|(?=\4[ \t]+).+?(?:\n|$)*?)*)(?=\s*(?:\n|$))/, // text2: /^([ \t]*)([.#\w-]+(?:\([^)]*\))*)\.\n((?:(?:^\4[ \t]+)(?:[^\n]*)\n)*)/, quote: o.quote, // See caveat #3 (backreferences). comment: /(^|\n)([ \t]*|^)(\/\/-[ \t]*(?:[^\n]*?(?:\n\10[ \t]+[^\n]*)+|[^\n]+(?=\n|$)))/, // A tag captures everything like `tag`, `.tag(attrs)`, `#tag(attrs)`, `div.tag(attrs)`. // 6 groups: 1. tag, 2. class & id, 3. attrs, 4. dot or not, 5. indent before content, 6. inner html. // The part `(?:[@:]?[a-z][\w-]+(?:=(?:"[^"]*"|'[^']*'))*\s*)*` matches all the attributes together. // It's a repeated group (0+) of attribute key-value pairs or key only. If there is an attribute // value, it makes sure the attribute is formed with matching quotes or double quotes and any character // inside. tag: /([a-z][\w:-]*|)([.#][a-z][.\w-]*|)\b(?:\((\s*(?:[#@:]?[a-z][\w-.:]+(?:=(?:"[^"]*"|'[^']*'))*\s*)*)\))?(\.?)([ \t]*)([^\n]+)?(?=\n|$)/, "inline-tag": /#\[([^[\]]+)\]/ // Only performed inside tags inner texts. // htmlentity: regexBasics.htmlentity // Only performed inside tags inner texts. }, css: { comment: /(\/\*.+?(?:\*\/|$))/, // Blocks comments (/* ... */). quote: /("(?:\\"|[^"])*")|('(?:\\'|[^'])*')/, pseudo: /(:(?:hover|active|focus|visited|not|before|after|(?:first|last|nth)-child))/, "selector keyword vendor": /(@-(?:moz|o|webkit|ms)-(?=keyframes\s))/, "selector keyword": /((?:@(?:import|media|font-face|keyframes)|screen|print|and)(?=[\s({])|keyframes|\s(?:ul|ol|li|table|div|pre|p|a|img|br|hr|h[1-6]|em|strong|span|html|body|iframe|video|audio|input|button|form|label|fieldset|small|abbr|i|dd|dt)\b)(?=.*\{})/, variable: /(--[a-z0-9-]+)/, // Any part before '{'. selector: /((?:[.#-\w*+ >:,[\]="~\n]|&gt;)+)(?=\s*\{)/, // Any part before '{'. "attribute keyword vendor": /(-(?:moz|o|webkit|ms)-(?=transform|transition|user-select|tap-highlight-color|animation|background-size|box-shadow))/, "attribute keyword": /\b(content|float|display|position|top|left|right|bottom|(?:(?:max|min)-)?width|(?:(?:max|min|line)-)?height|font(?:-(?:family|style|size|weight|variant|stretch))?|vertical-align|color|opacity|visibility|z-index|pointer-events|user-select|transform(?:-(?:origin|style|delay|duration|property|timing-function))?|transition(?:-(?:delay|duration))?|animation(?:-(?:name|delay|duration|direction|fill-mode))?|backface-visibility|backdrop-filter|background(?:-(?:color|position|image|repeat|size|attachment|origin|clip|blend-mode))?|(?:padding|margin|border)(?:-(?:top|left|right|bottom))?|border(?:-(?:radius|color|width|style|spacing))|white-space|text-(?:align|transform|decoration|shadow|indent)|overflow(?:-(?:x|y))?|(?:letter|word)-spacing|word-break|box-(?:sizing|shadow)|stroke(?:-(?:width|opacity|dasharray|dashoffset|linecap|linejoin))?|fill|speak|outline|user-select|cursor|flex(?:-(?:direction|flow|grow|shrink|basis|wrap))?|(?:justify|align)-(?:content|self|items))(?=\s*:)/, "value keyword vendor": /(-(?:moz|o|webkit|ms)-(?=linear-gradient))/, "value keyword": /\b(inherit|initial|normal|none|unset|auto|inline(?:-(?:block|flex))?|block|flex|absolute|relative|static|fixed|sticky|hidden|visible|top|left|right|bottom|center|middle|baseline|solid|dotted|dashed|line-through|(?:over|under)line|wavy|double|(?:pre-|no)?wrap|pre|break-word|(?:upper|lower)case|capitalize|italic|bold|attr\(.*?\)|linear|ease(?:-in)?(?:-out)?|all|infinite|cubic-bezier|(?:translate|rotate)(?:[X-Z]|3d)?|skew[XY]?|scale|(?:no-)?repeat|repeat(?:-x|-y)|contain|cover|url|(?:repeating-)?(?:linear|radial)-gradient|inset|pointer|(?:flex-)?(?:start|end)|space-(?:between|evenly|around)|stretch|revert|row(?:-reverse)?|column(?:-reverse)?)(?=\s*[,;}(]|\s+[\da-z!])/, "value keyword important": /( ?!important)/, number: o.number, color: /(transparent|#(?:[\da-f]{8}|[\da-f]{6}|[\da-f]{3})|rgba?\([\d., ]*\))/, htmlentity: o.htmlentity, punctuation: /([:,;{}@#()!]+)/, attribute: /([a-z-]+)(?=\s*:)/, unit: /(px|pt|cm|%|r?em|m?s|deg|vh|vw|vmin|vmax)(?=(?:\s*[;,{}})]|\s+[-\da-z#]))/, error: /([:,;{}@#()!]+|&lt;|&gt;|&amp;)/ }, json: { quote: /("(?:\\"|[^"])*")/, // Only match double quotes pairs. number: o.number, boolean: o.boolean, punctuation: /([[\](){}:,]+)/, // Override default to simplify. error: /(&(:?lt|gt|amp);|(?!\s).)/ }, js: { quote: o.quote, comment: o.comment, number: /\b(\d+(?:\.\d+)?|null)\b/, boolean: o.boolean, this: /\b(this)(?=\W)/, keyword: /\b(new|getElementsBy(?:Tag|Class|)Name|getElementById|querySelector|querySelectorAll|arguments|if|else|do|return|case|default|(?:f|F)unction|typeof|instanceof|undefined|document(?:Element)?|window|console|while|for|forEach|switch|in|break|continue|delete|length|var|let|const|export|import|as|require|from|Class|constructor|Number|Boolean|String|Array|Object|RegExp|Integer|Date|Promise|Proxy|WeakMap|WeakSet|Symbol|SyncManager|File(?:Reader)?|DataTransfer|DocumentFragment|async|await|(?:clear|set)(?:Timeout|Interval)|parse(?:Int|Float)|Math(?=\.)|isNaN|atob|btoa|getComputedStyle)(?=\W)/, htmlentity: o.htmlentity, punctuation: /(!==?|[[\]!(){}:;,+\-%*/?=]+|\.+(?![a-z])|\|\||&lt;|&gt;|&amp;)/, // Override default since '.' can be part of js variable. variable: /(\.?[a-z_]\w*)/, "external-var": /(\$|jQuery|JSON)(?=\W|$)/ // jQuery or $ or JSON. }, php: { quote: o.quote, comment: o.comment, special: /(&lt;\?(?:php)?|\?&gt;|__(?:DIR|FILE|LINE|CLASS|METHOD|FUNCTION|NAMESPACE|TRAIT)__)/, punctuation: o.punctuation, number: o.number, boolean: o.boolean, variable: /(\$\w+)/, keyword: /\b(define|echo|die|exit|print_r|var_dump|if|else|elseif|do|return|case|default|function|\$this|while|foreach|for|switch|in|break|continue|empty|isset|unset|parse_ini_file|session_(?:start|destroy|id)|header|json_(?:encode|decode)|error_log|(require|include)(:?_once)?|try|throw|new|Exception|catch|finally|preg_(?:match|replace)|list|strlen|substr|str_replace|array_(?:keys|values))(?=\W|$)/ }, sql: { quote: o.quote, comment: /((?:--|#)\s.*?(?:\n|$)|\/\*.*?\*\/)/, punctuation: o.punctuation, number: /\b(\d+(?:\.\d+)?|null)\b/, boolean: o.boolean, keyword: /\b(\*|DECLARE|BEGIN|END|RETURNS|FUNCTION|CREATE|DATABASE|TABLE|VIEW|COLUMN|INDEX|GRANT|REVOKE|ALL|PRIVILEGES|IDENTIFIED|FLUSH|ALTER|MODIFY|DROP|TRUNCATE|CONSTRAINT|ADD|CHECK|(?:(?:PRIMARY|FOREIGN|UNIQUE) )?KEY|REFERENCES|AUTO_INCREMENT|COMMENT|DEFAULT|UNSIGNED|CHARSET|COLLATE|CHARACTER|ENGINE|SQL_MODE|USE|IF|THEN|NULL|EXISTS|UNIQUE|SELECT|UPDATE|DELETE|(?:INSERT|REPLACE)(?: INTO)?|VALUES|SET|FROM|WHERE|(?:ORDER|GROUP) BY|LIMIT|(?:(?:LEFT|RIGHT|INNER|OUTER|CROSS) |)JOIN|AS|ON|COUNT|AVG|SUM|MIN|MAX|CASE|TO|WHEN|BETWEEN|AND|OR|NOT|IN|LIKE|IS|CONCAT|SUBSTRING|CURRENT_(?:DATE|TIMESTAMP)|USING|HAVING?)(?=\W|$)/, "var-type": /\b((?:var)?char|(?:tiny|small|medium|big)?int|decimal|float|double|real|bit|boolean|date(?:time)?|time(?:stamp)?|year|(?:tiny|medium|long)?(?:text|blob)|enum)\b/ } }, w = { xml: /(\s*)([a-z\d\-:]+)(?:=("|')(.*?)\3)?/g, html: /(\s*)([a-z-]+)(?:=("|')(.*?)\3)?/gs, "html-vue": /(\s*)([@:#]?[a-z\d-]+)(?:(?:=("|')(.*?)\3)|)/gs, pug: /(\s*|,)([@:#]?[a-z\d-]+)(?:(?:=("|')(.*?)\3)|)/g }, q = { shell: { quote: 2 }, xml: { quote: 2, tag: 5 }, html: { quote: 2, tag: 5 }, "html-vue": { quote: 2, tag: 5 }, pug: { text: 3, text2: 3, quote: 2, comment: 3, tag: 6 }, json: {}, php: { quote: 2 }, sql: { quote: 2 }, css: { quote: 2 }, js: { quote: 2 } }, u = m, L = A, x = S(Object.keys(h)), b = S(null), T = _(), y = (t) => t.map((e) => { if (!e.children || typeof e.children == "string") return e.children || ""; if (Array.isArray(e.children)) return y(e.children); if (e.children.default) return y(e.children.default()); }).join(""), f = (t) => t.replace(/[<>]/g, (e) => ({ "<": "&lt;", ">": "&gt;" })[e]), O = (t) => { if (t === "transparent") return { dark: !1, alpha: 0 }; const e = (s) => s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4); let l = 0, c = 0, n = 0, a = 1; const i = t.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/), r = t.match(/^#([\da-f]{3,8})$/i); if (i) l = parseInt(i[1]) / 255, c = parseInt(i[2]) / 255, n = parseInt(i[3]) / 255, i[4] && (a = parseFloat(i[4])); else if (r) { const s = r[1], d = s.length === 3 || s.length === 4; l = parseInt(d ? s[0] + s[0] : s.substring(0, 2), 16) / 255, c = parseInt(d ? s[1] + s[1] : s.substring(2, 4), 16) / 255, n = parseInt(d ? s[2] + s[2] : s.substring(4, 6), 16) / 255, (s.length === 4 || s.length === 8) && (a = parseInt(d ? s[3] + s[3] : s.substring(6, 8), 16) / 255); } const g = 0.2126 * e(l) + 0.7152 * e(c) + 0.0722 * e(n); return { dark: a * g + (1 - a) * 1 <= 0.45, alpha: a }; }, z = () => { let t = ""; const e = []; for (const l in h[u.language]) { const c = q[u.language][l] || 1; for (let n = 0; n < c; n++) e.push(l); t += (t ? "|" : "") + h[u.language][l].source; } return [t, e]; }, N = (t) => { const e = (l, c, n, a, i) => ( // `attribute-name` `${c}<span class="attribute">${n}</span>` + // `=` (a || i ? '<span class="punctuation">=</span>' : "") + // `"attribute value"` (a || i ? `<span class="quote">${a || ""}${i || ""}${a || ""}</span>` : "") ); if (u.language === "pug") { let [l, c = "", n = "", a = "", i = "", r = ""] = t; return c = c.replace(/#[\w-]+/g, (g) => `<span class="id">${g}</span>`).replace(/\.[\w-]+/g, (g) => `<span class="class">${g}</span>`), n && (n = n.replace(w.pug, e), n = '<span class="punctuation">(</span>' + n + '<span class="punctuation">)</span>'), r && (r = v(r)), // The tag-name + attributes list if any. `<span class="tag-name">${l}</span>${c}${n}` + (a ? '<span class="punctuation">.</span>' : "") + (i || "") + (r ? `<span class="text">${r}</span>` : ""); } else { const [l, c = "", n = "", a = "", i] = t, r = c.replace(w[u.language], e); return ( // The tag opening: `</` or `<`. `<span class="punctuation">&lt;${a}</span><span class="tag-name">${l || i}</span>` + r + // The tag end `>` or `/>`. `<span class="punctuation">${n}&gt;</span>` ); } }, v = (t) => t.replace(new RegExp(h.pug["inline-tag"], "gsi"), (e, l) => '<span class="inline-tag">#[</span>' + l.replace(new RegExp(h.pug.tag, "si"), (c, ...n) => (n = n.slice(0, n.length - 2), N(n))) + '<span class="inline-tag">]</span>'), M = () => { if (x.value.includes(u.language)) { const t = D(); b.value.innerHTML = E(b.value.innerText), I(b.value.childNodes, t); } }, D = () => { const t = window.getSelection(); t.collapseToEnd(); const e = new Range(); return e.setStart(b.value, 0), e.setEnd(t.extentNode, t.extentOffset), e.toString().length; }, I = (t, e) => { var c, n; let l = 0; for (const a of t) { const i = ((c = a.innerText) == null ? void 0 : c.length) || a.length; if (l + i >= e) { a.childNodes.length > 1 ? I(a.childNodes, e - l) : document.getSelection().setPosition(((n = a.childNodes) == null ? void 0 : n[0]) || a, e - l); break; } l += i; } }, U = (t) => { switch (t.which) { case 9: k(u.tab), t.preventDefault(); break; case 13: k(` `), t.preventDefault(); break; } }, k = (t) => { const e = window.getSelection(), l = e.getRangeAt(0), c = document.createTextNode(t); l.insertNode(c), e.collapseToEnd(); }, E = (t) => { if (!x.value.includes(u.language)) return t; const [e, l] = z(); return f(t.replace(/&/g, "&amp;")).replace(new RegExp(e, "gsi"), (c, ...n) => { n = n.slice(0, n.length - 2); let a; const i = u.language === "pug"; let r = n.find((p, s) => p && (a = l[s]) && p); if (["punctuation", "quote", "htmlentity"].includes(a)) r = f(r); else if (a === "comment") if (i) { const [p, s, d] = n.slice(l.indexOf("comment")); r = `${p}${s}${f(d)}`; } else r = f(r); else if (a === "text" && i) { let [p, s, d] = n; return d = v(d), `${p}<span class="punctuation">|</span>${s}<span class="text">${d}</span>`; } else if (a === "text2" && i) { const [, , , p, s, d] = n, P = E(s); return `${p}${P}<span class="punctuation">.</span> <span class="text">${d}</span>`; } else { if (a === "tag" && ["xml", "html", "html-vue", "pug"].includes(u.language)) return N(n.slice(l.indexOf("tag"))); if (a === "variable" && r[0] === "." && u.language === "js") return `<span class="punctuation">.</span><span class="obj-attr">${r.substr(1)}</span>`; } let g = ""; if (a === "color" && u.language === "css") { const { dark: p, alpha: s } = O(r); a += ` color--${p ? "dark" : "light"}${s < 0.4 || r === "transparent" ? " color--transparent" : ""}`, g = ` style="background-color: ${r}"`; } return a && `<span class="${a}"${g}>${r}</span>` || ""; }); }, B = () => T.default && y(T.default()) || "", H = j(() => E(B())), F = (t) => { t.target.insertAdjacentHTML( "afterend", `<textarea id="clipboard-textarea">${b.value.innerText}</textarea>` ); const e = document.getElementById("clipboard-textarea"); e.select(), e.setSelectionRange(0, 99999), document.execCommand("copy"), e.remove(), L("copied", b.value.innerText); }; return (t, e) => ($(), C("div", { class: G(["ssh-pre", { "ssh-pre--dark": m.dark }]), "data-type": m.language, "data-label": m.label || null }, [ m.copyButton ? ($(), C("button", { key: 0, class: "ssh-pre__copy", onClick: F }, [ W(t.$slots, "copy-button", {}, () => [ e[1] || (e[1] = R("Copy", -1)) ]) ])) : V("", !0), e[2] || (e[2] = R()), K("pre", { ref_key: "codeEl", ref: b, class: "ssh-pre__content", contenteditable: m.editable ? "true" : "false", innerHTML: H.value, onKeydown: e[0] || (e[0] = (l) => m.editable && U(l)), onInput: M }, null, 40, X) ], 10, Y)); } }; export { J as default };