diff options
Diffstat (limited to 'compat')
32 files changed, 0 insertions, 6954 deletions
diff --git a/compat/lustre_animation/.gitignore b/compat/lustre_animation/.gitignore deleted file mode 100644 index be616fc..0000000 --- a/compat/lustre_animation/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -build -node_modules -dist/main.js -dist/assets/ diff --git a/compat/lustre_animation/README.md b/compat/lustre_animation/README.md deleted file mode 100644 index 338e9c1..0000000 --- a/compat/lustre_animation/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Lustre Animation - -The basic usage is - * keep 1 `Animations` value - * add animations by some trigger - * call `animation.cmd()` on your value and let your lustre `update()` return it. - This will cause a dispatch on the next JS animation frame, unless all animations have finished (and auto-removed) - * update the animations with the new time. - * evaluate for each animation you are interested in. - -e.g. like this: -```gleam -import lustre/animation as a - -pub type Msg { - Trigger - Tick(Float) -} - -pub fn update(model: Model, msg: Msg) { - let m = case msg of { - Trigger -> { - let new_anim = a.add(model.animations, "id", 1.0, 2.0, 0.250) - Model(1.0, animations: new_anim) - } - Tick(t) -> { - let new_anim = a.tick(model.animations, t) - let new_value = a.value(new_anim, "id", model.value) - Model(new_value, new_anim) - } - } - #(m, animation.cmd(m.animations, Tick)) -} -``` - -In the above `type Model`, `init` and `render` have been omitted. - -There are fully functional examples animations in the `test/` directory, -which you can build by -```bash -gleam test -npx vite -```` - -and then pointing your browser to the URL that vite indicates. - -## TODO -* `every(Minute)`, etc -* `after(seconds: 2.5)` -* quadratic, cubic, etc interpolators -* Compose animations - `from("anim", 1.0) |> after(0.250) |> cubic(3.0, 0.250) |> after(0.250) |> quadr(2.0, 0.250)`
\ No newline at end of file diff --git a/compat/lustre_animation/gleam.toml b/compat/lustre_animation/gleam.toml deleted file mode 100644 index 182203e..0000000 --- a/compat/lustre_animation/gleam.toml +++ /dev/null @@ -1,14 +0,0 @@ -name = "lustre_animation" -version = "0.2.0" -description = "Animations for lustre, utilizing JS requestAnimationFrame" -target = "javascript" -licenses = ["MIT"] -repository = { type = "custom", url = "https://git.chmeee.org/lustre_animation" } - -[dependencies] -gleam_stdlib = "~> 0.30" -lustre = { path = "../../lib" } - - -[dev-dependencies] -gleeunit = "~> 0.10" diff --git a/compat/lustre_animation/highlight.js b/compat/lustre_animation/highlight.js deleted file mode 100644 index aa0170b..0000000 --- a/compat/lustre_animation/highlight.js +++ /dev/null @@ -1,5008 +0,0 @@ -/*! - Highlight.js v11.7.0 (git: 2f3d5ff18e) - (c) 2006-2023 undefined and other contributors - License: BSD-3-Clause - */ -var hljs = (function () { - 'use strict'; - - var deepFreezeEs6 = {exports: {}}; - - function deepFreeze(obj) { - if (obj instanceof Map) { - obj.clear = obj.delete = obj.set = function () { - throw new Error('map is read-only'); - }; - } else if (obj instanceof Set) { - obj.add = obj.clear = obj.delete = function () { - throw new Error('set is read-only'); - }; - } - - // Freeze self - Object.freeze(obj); - - Object.getOwnPropertyNames(obj).forEach(function (name) { - var prop = obj[name]; - - // Freeze prop if it is an object - if (typeof prop == 'object' && !Object.isFrozen(prop)) { - deepFreeze(prop); - } - }); - - return obj; - } - - deepFreezeEs6.exports = deepFreeze; - deepFreezeEs6.exports.default = deepFreeze; - - /** @typedef {import('highlight.js').CallbackResponse} CallbackResponse */ - /** @typedef {import('highlight.js').CompiledMode} CompiledMode */ - /** @implements CallbackResponse */ - - class Response { - /** - * @param {CompiledMode} mode - */ - constructor(mode) { - // eslint-disable-next-line no-undefined - if (mode.data === undefined) mode.data = {}; - - this.data = mode.data; - this.isMatchIgnored = false; - } - - ignoreMatch() { - this.isMatchIgnored = true; - } - } - - /** - * @param {string} value - * @returns {string} - */ - function escapeHTML(value) { - return value - .replace(/&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); - } - - /** - * performs a shallow merge of multiple objects into one - * - * @template T - * @param {T} original - * @param {Record<string,any>[]} objects - * @returns {T} a single new object - */ - function inherit$1(original, ...objects) { - /** @type Record<string,any> */ - const result = Object.create(null); - - for (const key in original) { - result[key] = original[key]; - } - objects.forEach(function(obj) { - for (const key in obj) { - result[key] = obj[key]; - } - }); - return /** @type {T} */ (result); - } - - /** - * @typedef {object} Renderer - * @property {(text: string) => void} addText - * @property {(node: Node) => void} openNode - * @property {(node: Node) => void} closeNode - * @property {() => string} value - */ - - /** @typedef {{scope?: string, language?: string, sublanguage?: boolean}} Node */ - /** @typedef {{walk: (r: Renderer) => void}} Tree */ - /** */ - - const SPAN_CLOSE = '</span>'; - - /** - * Determines if a node needs to be wrapped in <span> - * - * @param {Node} node */ - const emitsWrappingTags = (node) => { - // rarely we can have a sublanguage where language is undefined - // TODO: track down why - return !!node.scope || (node.sublanguage && node.language); - }; - - /** - * - * @param {string} name - * @param {{prefix:string}} options - */ - const scopeToCSSClass = (name, { prefix }) => { - if (name.includes(".")) { - const pieces = name.split("."); - return [ - `${prefix}${pieces.shift()}`, - ...(pieces.map((x, i) => `${x}${"_".repeat(i + 1)}`)) - ].join(" "); - } - return `${prefix}${name}`; - }; - - /** @type {Renderer} */ - class HTMLRenderer { - /** - * Creates a new HTMLRenderer - * - * @param {Tree} parseTree - the parse tree (must support `walk` API) - * @param {{classPrefix: string}} options - */ - constructor(parseTree, options) { - this.buffer = ""; - this.classPrefix = options.classPrefix; - parseTree.walk(this); - } - - /** - * Adds texts to the output stream - * - * @param {string} text */ - addText(text) { - this.buffer += escapeHTML(text); - } - - /** - * Adds a node open to the output stream (if needed) - * - * @param {Node} node */ - openNode(node) { - if (!emitsWrappingTags(node)) return; - - let className = ""; - if (node.sublanguage) { - className = `language-${node.language}`; - } else { - className = scopeToCSSClass(node.scope, { prefix: this.classPrefix }); - } - this.span(className); - } - - /** - * Adds a node close to the output stream (if needed) - * - * @param {Node} node */ - closeNode(node) { - if (!emitsWrappingTags(node)) return; - - this.buffer += SPAN_CLOSE; - } - - /** - * returns the accumulated buffer - */ - value() { - return this.buffer; - } - - // helpers - - /** - * Builds a span element - * - * @param {string} className */ - span(className) { - this.buffer += `<span class="${className}">`; - } - } - - /** @typedef {{scope?: string, language?: string, sublanguage?: boolean, children: Node[]} | string} Node */ - /** @typedef {{scope?: string, language?: string, sublanguage?: boolean, children: Node[]} } DataNode */ - /** @typedef {import('highlight.js').Emitter} Emitter */ - /** */ - - /** @returns {DataNode} */ - const newNode = (opts = {}) => { - /** @type DataNode */ - const result = { children: [] }; - Object.assign(result, opts); - return result; - }; - - class TokenTree { - constructor() { - /** @type DataNode */ - this.rootNode = newNode(); - this.stack = [this.rootNode]; - } - - get top() { - return this.stack[this.stack.length - 1]; - } - - get root() { return this.rootNode; } - - /** @param {Node} node */ - add(node) { - this.top.children.push(node); - } - - /** @param {string} scope */ - openNode(scope) { - /** @type Node */ - const node = newNode({ scope }); - this.add(node); - this.stack.push(node); - } - - closeNode() { - if (this.stack.length > 1) { - return this.stack.pop(); - } - // eslint-disable-next-line no-undefined - return undefined; - } - - closeAllNodes() { - while (this.closeNode()); - } - - toJSON() { - return JSON.stringify(this.rootNode, null, 4); - } - - /** - * @typedef { import("./html_renderer").Renderer } Renderer - * @param {Renderer} builder - */ - walk(builder) { - // this does not - return this.constructor._walk(builder, this.rootNode); - // this works - // return TokenTree._walk(builder, this.rootNode); - } - - /** - * @param {Renderer} builder - * @param {Node} node - */ - static _walk(builder, node) { - if (typeof node === "string") { - builder.addText(node); - } else if (node.children) { - builder.openNode(node); - node.children.forEach((child) => this._walk(builder, child)); - builder.closeNode(node); - } - return builder; - } - - /** - * @param {Node} node - */ - static _collapse(node) { - if (typeof node === "string") return; - if (!node.children) return; - - if (node.children.every(el => typeof el === "string")) { - // node.text = node.children.join(""); - // delete node.children; - node.children = [node.children.join("")]; - } else { - node.children.forEach((child) => { - TokenTree._collapse(child); - }); - } - } - } - - /** - Currently this is all private API, but this is the minimal API necessary - that an Emitter must implement to fully support the parser. - - Minimal interface: - - - addKeyword(text, scope) - - addText(text) - - addSublanguage(emitter, subLanguageName) - - finalize() - - openNode(scope) - - closeNode() - - closeAllNodes() - - toHTML() - - */ - - /** - * @implements {Emitter} - */ - class TokenTreeEmitter extends TokenTree { - /** - * @param {*} options - */ - constructor(options) { - super(); - this.options = options; - } - - /** - * @param {string} text - * @param {string} scope - */ - addKeyword(text, scope) { - if (text === "") { return; } - - this.openNode(scope); - this.addText(text); - this.closeNode(); - } - - /** - * @param {string} text - */ - addText(text) { - if (text === "") { return; } - - this.add(text); - } - - /** - * @param {Emitter & {root: DataNode}} emitter - * @param {string} name - */ - addSublanguage(emitter, name) { - /** @type DataNode */ - const node = emitter.root; - node.sublanguage = true; - node.language = name; - this.add(node); - } - - toHTML() { - const renderer = new HTMLRenderer(this, this.options); - return renderer.value(); - } - - finalize() { - return true; - } - } - - /** - * @param {string} value - * @returns {RegExp} - * */ - - /** - * @param {RegExp | string } re - * @returns {string} - */ - function source(re) { - if (!re) return null; - if (typeof re === "string") return re; - - return re.source; - } - - /** - * @param {RegExp | string } re - * @returns {string} - */ - function lookahead(re) { - return concat('(?=', re, ')'); - } - - /** - * @param {RegExp | string } re - * @returns {string} - */ - function anyNumberOfTimes(re) { - return concat('(?:', re, ')*'); - } - - /** - * @param {RegExp | string } re - * @returns {string} - */ - function optional(re) { - return concat('(?:', re, ')?'); - } - - /** - * @param {...(RegExp | string) } args - * @returns {string} - */ - function concat(...args) { - const joined = args.map((x) => source(x)).join(""); - return joined; - } - - /** - * @param { Array<string | RegExp | Object> } args - * @returns {object} - */ - function stripOptionsFromArgs(args) { - const opts = args[args.length - 1]; - - if (typeof opts === 'object' && opts.constructor === Object) { - args.splice(args.length - 1, 1); - return opts; - } else { - return {}; - } - } - - /** @typedef { {capture?: boolean} } RegexEitherOptions */ - - /** - * Any of the passed expresssions may match - * - * Creates a huge this | this | that | that match - * @param {(RegExp | string)[] | [...(RegExp | string)[], RegexEitherOptions]} args - * @returns {string} - */ - function either(...args) { - /** @type { object & {capture?: boolean} } */ - const opts = stripOptionsFromArgs(args); - const joined = '(' - + (opts.capture ? "" : "?:") - + args.map((x) => source(x)).join("|") + ")"; - return joined; - } - - /** - * @param {RegExp | string} re - * @returns {number} - */ - function countMatchGroups(re) { - return (new RegExp(re.toString() + '|')).exec('').length - 1; - } - - /** - * Does lexeme start with a regular expression match at the beginning - * @param {RegExp} re - * @param {string} lexeme - */ - function startsWith(re, lexeme) { - const match = re && re.exec(lexeme); - return match && match.index === 0; - } - - // BACKREF_RE matches an open parenthesis or backreference. To avoid - // an incorrect parse, it additionally matches the following: - // - [...] elements, where the meaning of parentheses and escapes change - // - other escape sequences, so we do not misparse escape sequences as - // interesting elements - // - non-matching or lookahead parentheses, which do not capture. These - // follow the '(' with a '?'. - const BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./; - - // **INTERNAL** Not intended for outside usage - // join logically computes regexps.join(separator), but fixes the - // backreferences so they continue to match. - // it also places each individual regular expression into it's own - // match group, keeping track of the sequencing of those match groups - // is currently an exercise for the caller. :-) - /** - * @param {(string | RegExp)[]} regexps - * @param {{joinWith: string}} opts - * @returns {string} - */ - function _rewriteBackreferences(regexps, { joinWith }) { - let numCaptures = 0; - - return regexps.map((regex) => { - numCaptures += 1; - const offset = numCaptures; - let re = source(regex); - let out = ''; - - while (re.length > 0) { - const match = BACKREF_RE.exec(re); - if (!match) { - out += re; - break; - } - out += re.substring(0, match.index); - re = re.substring(match.index + match[0].length); - if (match[0][0] === '\\' && match[1]) { - // Adjust the backreference. - out += '\\' + String(Number(match[1]) + offset); - } else { - out += match[0]; - if (match[0] === '(') { - numCaptures++; - } - } - } - return out; - }).map(re => `(${re})`).join(joinWith); - } - - /** @typedef {import('highlight.js').Mode} Mode */ - /** @typedef {import('highlight.js').ModeCallback} ModeCallback */ - - // Common regexps - const MATCH_NOTHING_RE = /\b\B/; - const IDENT_RE$1 = '[a-zA-Z]\\w*'; - const UNDERSCORE_IDENT_RE = '[a-zA-Z_]\\w*'; - const NUMBER_RE = '\\b\\d+(\\.\\d+)?'; - const C_NUMBER_RE = '(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float - const BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b... - const RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~'; - - /** - * @param { Partial<Mode> & {binary?: string | RegExp} } opts - */ - const SHEBANG = (opts = {}) => { - const beginShebang = /^#![ ]*\//; - if (opts.binary) { - opts.begin = concat( - beginShebang, - /.*\b/, - opts.binary, - /\b.*/); - } - return inherit$1({ - scope: 'meta', - begin: beginShebang, - end: /$/, - relevance: 0, - /** @type {ModeCallback} */ - "on:begin": (m, resp) => { - if (m.index !== 0) resp.ignoreMatch(); - } - }, opts); - }; - - // Common modes - const BACKSLASH_ESCAPE = { - begin: '\\\\[\\s\\S]', relevance: 0 - }; - const APOS_STRING_MODE = { - scope: 'string', - begin: '\'', - end: '\'', - illegal: '\\n', - contains: [BACKSLASH_ESCAPE] - }; - const QUOTE_STRING_MODE = { - scope: 'string', - begin: '"', - end: '"', - illegal: '\\n', - contains: [BACKSLASH_ESCAPE] - }; - const PHRASAL_WORDS_MODE = { - begin: /\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ - }; - /** - * Creates a comment mode - * - * @param {string | RegExp} begin - * @param {string | RegExp} end - * @param {Mode | {}} [modeOptions] - * @returns {Partial<Mode>} - */ - const COMMENT = function(begin, end, modeOptions = {}) { - const mode = inherit$1( - { - scope: 'comment', - begin, - end, - contains: [] - }, - modeOptions - ); - mode.contains.push({ - scope: 'doctag', - // hack to avoid the space from being included. the space is necessary to - // match here to prevent the plain text rule below from gobbling up doctags - begin: '[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)', - end: /(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/, - excludeBegin: true, - relevance: 0 - }); - const ENGLISH_WORD = either( - // list of common 1 and 2 letter words in English - "I", - "a", - "is", - "so", - "us", - "to", - "at", - "if", - "in", - "it", - "on", - // note: this is not an exhaustive list of contractions, just popular ones - /[A-Za-z]+['](d|ve|re|ll|t|s|n)/, // contractions - can't we'd they're let's, etc - /[A-Za-z]+[-][a-z]+/, // `no-way`, etc. - /[A-Za-z][a-z]{2,}/ // allow capitalized words at beginning of sentences - ); - // looking like plain text, more likely to be a comment - mode.contains.push( - { - // TODO: how to include ", (, ) without breaking grammars that use these for - // comment delimiters? - // begin: /[ ]+([()"]?([A-Za-z'-]{3,}|is|a|I|so|us|[tT][oO]|at|if|in|it|on)[.]?[()":]?([.][ ]|[ ]|\))){3}/ - // --- - - // this tries to find sequences of 3 english words in a row (without any - // "programming" type syntax) this gives us a strong signal that we've - // TRULY found a comment - vs perhaps scanning with the wrong language. - // It's possible to find something that LOOKS like the start of the - // comment - but then if there is no readable text - good chance it is a - // false match and not a comment. - // - // for a visual example please see: - // https://github.com/highlightjs/highlight.js/issues/2827 - - begin: concat( - /[ ]+/, // necessary to prevent us gobbling up doctags like /* @author Bob Mcgill */ - '(', - ENGLISH_WORD, - /[.]?[:]?([.][ ]|[ ])/, - '){3}') // look for 3 words in a row - } - ); - return mode; - }; - const C_LINE_COMMENT_MODE = COMMENT('//', '$'); - const C_BLOCK_COMMENT_MODE = COMMENT('/\\*', '\\*/'); - const HASH_COMMENT_MODE = COMMENT('#', '$'); - const NUMBER_MODE = { - scope: 'number', - begin: NUMBER_RE, - relevance: 0 - }; - const C_NUMBER_MODE = { - scope: 'number', - begin: C_NUMBER_RE, - relevance: 0 - }; - const BINARY_NUMBER_MODE = { - scope: 'number', - begin: BINARY_NUMBER_RE, - relevance: 0 - }; - const REGEXP_MODE = { - // this outer rule makes sure we actually have a WHOLE regex and not simply - // an expression such as: - // - // 3 / something - // - // (which will then blow up when regex's `illegal` sees the newline) - begin: /(?=\/[^/\n]*\/)/, - contains: [{ - scope: 'regexp', - begin: /\//, - end: /\/[gimuy]*/, - illegal: /\n/, - contains: [ - BACKSLASH_ESCAPE, - { - begin: /\[/, - end: /\]/, - relevance: 0, - contains: [BACKSLASH_ESCAPE] - } - ] - }] - }; - const TITLE_MODE = { - scope: 'title', - begin: IDENT_RE$1, - relevance: 0 - }; - const UNDERSCORE_TITLE_MODE = { - scope: 'title', - begin: UNDERSCORE_IDENT_RE, - relevance: 0 - }; - const METHOD_GUARD = { - // excludes method names from keyword processing - begin: '\\.\\s*' + UNDERSCORE_IDENT_RE, - relevance: 0 - }; - - /** - * Adds end same as begin mechanics to a mode - * - * Your mode must include at least a single () match group as that first match - * group is what is used for comparison - * @param {Partial<Mode>} mode - */ - const END_SAME_AS_BEGIN = function(mode) { - return Object.assign(mode, - { - /** @type {ModeCallback} */ - 'on:begin': (m, resp) => { resp.data._beginMatch = m[1]; }, - /** @type {ModeCallback} */ - 'on:end': (m, resp) => { if (resp.data._beginMatch !== m[1]) resp.ignoreMatch(); } - }); - }; - - var MODES$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - MATCH_NOTHING_RE: MATCH_NOTHING_RE, - IDENT_RE: IDENT_RE$1, - UNDERSCORE_IDENT_RE: UNDERSCORE_IDENT_RE, - NUMBER_RE: NUMBER_RE, - C_NUMBER_RE: C_NUMBER_RE, - BINARY_NUMBER_RE: BINARY_NUMBER_RE, - RE_STARTERS_RE: RE_STARTERS_RE, - SHEBANG: SHEBANG, - BACKSLASH_ESCAPE: BACKSLASH_ESCAPE, - APOS_STRING_MODE: APOS_STRING_MODE, - QUOTE_STRING_MODE: QUOTE_STRING_MODE, - PHRASAL_WORDS_MODE: PHRASAL_WORDS_MODE, - COMMENT: COMMENT, - C_LINE_COMMENT_MODE: C_LINE_COMMENT_MODE, - C_BLOCK_COMMENT_MODE: C_BLOCK_COMMENT_MODE, - HASH_COMMENT_MODE: HASH_COMMENT_MODE, - NUMBER_MODE: NUMBER_MODE, - C_NUMBER_MODE: C_NUMBER_MODE, - BINARY_NUMBER_MODE: BINARY_NUMBER_MODE, - REGEXP_MODE: REGEXP_MODE, - TITLE_MODE: TITLE_MODE, - UNDERSCORE_TITLE_MODE: UNDERSCORE_TITLE_MODE, - METHOD_GUARD: METHOD_GUARD, - END_SAME_AS_BEGIN: END_SAME_AS_BEGIN - }); - - /** - @typedef {import('highlight.js').CallbackResponse} CallbackResponse - @typedef {import('highlight.js').CompilerExt} CompilerExt - */ - - // Grammar extensions / plugins - // See: https://github.com/highlightjs/highlight.js/issues/2833 - - // Grammar extensions allow "syntactic sugar" to be added to the grammar modes - // without requiring any underlying changes to the compiler internals. - - // `compileMatch` being the perfect small example of now allowing a grammar - // author to write `match` when they desire to match a single expression rather - // than being forced to use `begin`. The extension then just moves `match` into - // `begin` when it runs. Ie, no features have been added, but we've just made - // the experience of writing (and reading grammars) a little bit nicer. - - // ------ - - // TODO: We need negative look-behind support to do this properly - /** - * Skip a match if it has a preceding dot - * - * This is used for `beginKeywords` to prevent matching expressions such as - * `bob.keyword.do()`. The mode compiler automatically wires this up as a - * special _internal_ 'on:begin' callback for modes with `beginKeywords` - * @param {RegExpMatchArray} match - * @param {CallbackResponse} response - */ - function skipIfHasPrecedingDot(match, response) { - const before = match.input[match.index - 1]; - if (before === ".") { - response.ignoreMatch(); - } - } - - /** - * - * @type {CompilerExt} - */ - function scopeClassName(mode, _parent) { - // eslint-disable-next-line no-undefined - if (mode.className !== undefined) { - mode.scope = mode.className; - delete mode.className; - } - } - - /** - * `beginKeywords` syntactic sugar - * @type {CompilerExt} - */ - function beginKeywords(mode, parent) { - if (!parent) return; - if (!mode.beginKeywords) return; - - // for languages with keywords that include non-word characters checking for - // a word boundary is not sufficient, so instead we check for a word boundary - // or whitespace - this does no harm in any case since our keyword engine - // doesn't allow spaces in keywords anyways and we still check for the boundary - // first - mode.begin = '\\b(' + mode.beginKeywords.split(' ').join('|') + ')(?!\\.)(?=\\b|\\s)'; - mode.__beforeBegin = skipIfHasPrecedingDot; - mode.keywords = mode.keywords || mode.beginKeywords; - delete mode.beginKeywords; - - // prevents double relevance, the keywords themselves provide - // relevance, the mode doesn't need to double it - // eslint-disable-next-line no-undefined - if (mode.relevance === undefined) mode.relevance = 0; - } - - /** - * Allow `illegal` to contain an array of illegal values - * @type {CompilerExt} - */ - function compileIllegal(mode, _parent) { - if (!Array.isArray(mode.illegal)) return; - - mode.illegal = either(...mode.illegal); - } - - /** - * `match` to match a single expression for readability - * @type {CompilerExt} - */ - function compileMatch(mode, _parent) { - if (!mode.match) return; - if (mode.begin || mode.end) throw new Error("begin & end are not supported with match"); - - mode.begin = mode.match; - delete mode.match; - } - - /** - * provides the default 1 relevance to all modes - * @type {CompilerExt} - */ - function compileRelevance(mode, _parent) { - // eslint-disable-next-line no-undefined - if (mode.relevance === undefined) mode.relevance = 1; - } - - // allow beforeMatch to act as a "qualifier" for the match - // the full match begin must be [beforeMatch][begin] - const beforeMatchExt = (mode, parent) => { - if (!mode.beforeMatch) return; - // starts conflicts with endsParent which we need to make sure the child - // rule is not matched multiple times - if (mode.starts) throw new Error("beforeMatch cannot be used with starts"); - - const originalMode = Object.assign({}, mode); - Object.keys(mode).forEach((key) => { delete mode[key]; }); - - mode.keywords = originalMode.keywords; - mode.begin = concat(originalMode.beforeMatch, lookahead(originalMode.begin)); - mode.starts = { - relevance: 0, - contains: [ - Object.assign(originalMode, { endsParent: true }) - ] - }; - mode.relevance = 0; - - delete originalMode.beforeMatch; - }; - - // keywords that should have no default relevance value - const COMMON_KEYWORDS = [ - 'of', - 'and', - 'for', - 'in', - 'not', - 'or', - 'if', - 'then', - 'parent', // common variable name - 'list', // common variable name - 'value' // common variable name - ]; - - const DEFAULT_KEYWORD_SCOPE = "keyword"; - - /** - * Given raw keywords from a language definition, compile them. - * - * @param {string | Record<string,string|string[]> | Array<string>} rawKeywords - * @param {boolean} caseInsensitive - */ - function compileKeywords(rawKeywords, caseInsensitive, scopeName = DEFAULT_KEYWORD_SCOPE) { - /** @type {import("highlight.js/private").KeywordDict} */ - const compiledKeywords = Object.create(null); - - // input can be a string of keywords, an array of keywords, or a object with - // named keys representing scopeName (which can then point to a string or array) - if (typeof rawKeywords === 'string') { - compileList(scopeName, rawKeywords.split(" ")); - } else if (Array.isArray(rawKeywords)) { - compileList(scopeName, rawKeywords); - } else { - Object.keys(rawKeywords).forEach(function(scopeName) { - // collapse all our objects back into the parent object - Object.assign( - compiledKeywords, - compileKeywords(rawKeywords[scopeName], caseInsensitive, scopeName) - ); - }); - } - return compiledKeywords; - - // --- - - /** - * Compiles an individual list of keywords - * - * Ex: "for if when while|5" - * - * @param {string} scopeName - * @param {Array<string>} keywordList - */ - function compileList(scopeName, keywordList) { - if (caseInsensitive) { - keywordList = keywordList.map(x => x.toLowerCase()); - } - keywordList.forEach(function(keyword) { - const pair = keyword.split('|'); - compiledKeywords[pair[0]] = [scopeName, scoreForKeyword(pair[0], pair[1])]; - }); - } - } - - /** - * Returns the proper score for a given keyword - * - * Also takes into account comment keywords, which will be scored 0 UNLESS - * another score has been manually assigned. - * @param {string} keyword - * @param {string} [providedScore] - */ - function scoreForKeyword(keyword, providedScore) { - // manual scores always win over common keywords - // so you can force a score of 1 if you really insist - if (providedScore) { - return Number(providedScore); - } - - return commonKeyword(keyword) ? 0 : 1; - } - - /** - * Determines if a given keyword is common or not - * - * @param {string} keyword */ - function commonKeyword(keyword) { - return COMMON_KEYWORDS.includes(keyword.toLowerCase()); - } - - /* - - For the reasoning behind this please see: - https://github.com/highlightjs/highlight.js/issues/2880#issuecomment-747275419 - - */ - - /** - * @type {Record<string, boolean>} - */ - const seenDeprecations = {}; - - /** - * @param {string} message - */ - const error = (message) => { - console.error(message); - }; - - /** - * @param {string} message - * @param {any} args - */ - const warn = (message, ...args) => { - console.log(`WARN: ${message}`, ...args); - }; - - /** - * @param {string} version - * @param {string} message - */ - const deprecated = (version, message) => { - if (seenDeprecations[`${version}/${message}`]) return; - - console.log(`Deprecated as of ${version}. ${message}`); - seenDeprecations[`${version}/${message}`] = true; - }; - - /* eslint-disable no-throw-literal */ - - /** - @typedef {import('highlight.js').CompiledMode} CompiledMode - */ - - const MultiClassError = new Error(); - - /** - * Renumbers labeled scope names to account for additional inner match - * groups that otherwise would break everything. - * - * Lets say we 3 match scopes: - * - * { 1 => ..., 2 => ..., 3 => ... } - * - * So what we need is a clean match like this: - * - * (a)(b)(c) => [ "a", "b", "c" ] - * - * But this falls apart with inner match groups: - * - * (a)(((b)))(c) => ["a", "b", "b", "b", "c" ] - * - * Our scopes are now "out of alignment" and we're repeating `b` 3 times. - * What needs to happen is the numbers are remapped: - * - * { 1 => ..., 2 => ..., 5 => ... } - * - * We also need to know that the ONLY groups that should be output - * are 1, 2, and 5. This function handles this behavior. - * - * @param {CompiledMode} mode - * @param {Array<RegExp | string>} regexes - * @param {{key: "beginScope"|"endScope"}} opts - */ - function remapScopeNames(mode, regexes, { key }) { - let offset = 0; - const scopeNames = mode[key]; - /** @type Record<number,boolean> */ - const emit = {}; - /** @type Record<number,string> */ - const positions = {}; - - for (let i = 1; i <= regexes.length; i++) { - positions[i + offset] = scopeNames[i]; - emit[i + offset] = true; - offset += countMatchGroups(regexes[i - 1]); - } - // we use _emit to keep track of which match groups are "top-level" to avoid double - // output from inside match groups - mode[key] = positions; - mode[key]._emit = emit; - mode[key]._multi = true; - } - - /** - * @param {CompiledMode} mode - */ - function beginMultiClass(mode) { - if (!Array.isArray(mode.begin)) return; - - if (mode.skip || mode.excludeBegin || mode.returnBegin) { - error("skip, excludeBegin, returnBegin not compatible with beginScope: {}"); - throw MultiClassError; - } - - if (typeof mode.beginScope !== "object" || mode.beginScope === null) { - error("beginScope must be object"); - throw MultiClassError; - } - - remapScopeNames(mode, mode.begin, { key: "beginScope" }); - mode.begin = _rewriteBackreferences(mode.begin, { joinWith: "" }); - } - - /** - * @param {CompiledMode} mode - */ - function endMultiClass(mode) { - if (!Array.isArray(mode.end)) return; - - if (mode.skip || mode.excludeEnd || mode.returnEnd) { - error("skip, excludeEnd, returnEnd not compatible with endScope: {}"); - throw MultiClassError; - } - - if (typeof mode.endScope !== "object" || mode.endScope === null) { - error("endScope must be object"); - throw MultiClassError; - } - - remapScopeNames(mode, mode.end, { key: "endScope" }); - mode.end = _rewriteBackreferences(mode.end, { joinWith: "" }); - } - - /** - * this exists only to allow `scope: {}` to be used beside `match:` - * Otherwise `beginScope` would necessary and that would look weird - - { - match: [ /def/, /\w+/ ] - scope: { 1: "keyword" , 2: "title" } - } - - * @param {CompiledMode} mode - */ - function scopeSugar(mode) { - if (mode.scope && typeof mode.scope === "object" && mode.scope !== null) { - mode.beginScope = mode.scope; - delete mode.scope; - } - } - - /** - * @param {CompiledMode} mode - */ - function MultiClass(mode) { - scopeSugar(mode); - - if (typeof mode.beginScope === "string") { - mode.beginScope = { _wrap: mode.beginScope }; - } - if (typeof mode.endScope === "string") { - mode.endScope = { _wrap: mode.endScope }; - } - - beginMultiClass(mode); - endMultiClass(mode); - } - - /** - @typedef {import('highlight.js').Mode} Mode - @typedef {import('highlight.js').CompiledMode} CompiledMode - @typedef {import('highlight.js').Language} Language - @typedef {import('highlight.js').HLJSPlugin} HLJSPlugin - @typedef {import('highlight.js').CompiledLanguage} CompiledLanguage - */ - - // compilation - - /** - * Compiles a language definition result - * - * Given the raw result of a language definition (Language), compiles this so - * that it is ready for highlighting code. - * @param {Language} language - * @returns {CompiledLanguage} - */ - function compileLanguage(language) { - /** - * Builds a regex with the case sensitivity of the current language - * - * @param {RegExp | string} value - * @param {boolean} [global] - */ - function langRe(value, global) { - return new RegExp( - source(value), - 'm' - + (language.case_insensitive ? 'i' : '') - + (language.unicodeRegex ? 'u' : '') - + (global ? 'g' : '') - ); - } - - /** - Stores multiple regular expressions and allows you to quickly search for - them all in a string simultaneously - returning the first match. It does - this by creating a huge (a|b|c) regex - each individual item wrapped with () - and joined by `|` - using match groups to track position. When a match is - found checking which position in the array has content allows us to figure - out which of the original regexes / match groups triggered the match. - - The match object itself (the result of `Regex.exec`) is returned but also - enhanced by merging in any meta-data that was registered with the regex. - This is how we keep track of which mode matched, and what type of rule - (`illegal`, `begin`, end, etc). - */ - class MultiRegex { - constructor() { - this.matchIndexes = {}; - // @ts-ignore - this.regexes = []; - this.matchAt = 1; - this.position = 0; - } - - // @ts-ignore - addRule(re, opts) { - opts.position = this.position++; - // @ts-ignore - this.matchIndexes[this.matchAt] = opts; - this.regexes.push([opts, re]); - this.matchAt += countMatchGroups(re) + 1; - } - - compile() { - if (this.regexes.length === 0) { - // avoids the need to check length every time exec is called - // @ts-ignore - this.exec = () => null; - } - const terminators = this.regexes.map(el => el[1]); - this.matcherRe = langRe(_rewriteBackreferences(terminators, { joinWith: '|' }), true); - this.lastIndex = 0; - } - - /** @param {string} s */ - exec(s) { - this.matcherRe.lastIndex = this.lastIndex; - const match = this.matcherRe.exec(s); - if (!match) { return null; } - - // eslint-disable-next-line no-undefined - const i = match.findIndex((el, i) => i > 0 && el !== undefined); - // @ts-ignore - const matchData = this.matchIndexes[i]; - // trim off any earlier non-relevant match groups (ie, the other regex - // match groups that make up the multi-matcher) - match.splice(0, i); - - return Object.assign(match, matchData); - } - } - - /* - Created to solve the key deficiently with MultiRegex - there is no way to - test for multiple matches at a single location. Why would we need to do - that? In the future a more dynamic engine will allow certain matches to be - ignored. An example: if we matched say the 3rd regex in a large group but - decided to ignore it - we'd need to started testing again at the 4th - regex... but MultiRegex itself gives us no real way to do that. - - So what this class creates MultiRegexs on the fly for whatever search - position they are needed. - - NOTE: These additional MultiRegex objects are created dynamically. For most - grammars most of the time we will never actually need anything more than the - first MultiRegex - so this shouldn't have too much overhead. - - Say this is our search group, and we match regex3, but wish to ignore it. - - regex1 | regex2 | regex3 | regex4 | regex5 ' ie, startAt = 0 - - What we need is a new MultiRegex that only includes the remaining - possibilities: - - regex4 | regex5 ' ie, startAt = 3 - - This class wraps all that complexity up in a simple API... `startAt` decides - where in the array of expressions to start doing the matching. It - auto-increments, so if a match is found at position 2, then startAt will be - set to 3. If the end is reached startAt will return to 0. - - MOST of the time the parser will be setting startAt manually to 0. - */ - class ResumableMultiRegex { - constructor() { - // @ts-ignore - this.rules = []; - // @ts-ignore - this.multiRegexes = []; - this.count = 0; - - this.lastIndex = 0; - this.regexIndex = 0; - } - - // @ts-ignore - getMatcher(index) { - if (this.multiRegexes[index]) return this.multiRegexes[index]; - - const matcher = new MultiRegex(); - this.rules.slice(index).forEach(([re, opts]) => matcher.addRule(re, opts)); - matcher.compile(); - this.multiRegexes[index] = matcher; - return matcher; - } - - resumingScanAtSamePosition() { - return this.regexIndex !== 0; - } - - considerAll() { - this.regexIndex = 0; - } - - // @ts-ignore - addRule(re, opts) { - this.rules.push([re, opts]); - if (opts.type === "begin") this.count++; - } - - /** @param {string} s */ - exec(s) { - const m = this.getMatcher(this.regexIndex); - m.lastIndex = this.lastIndex; - let result = m.exec(s); - - // The following is because we have no easy way to say "resume scanning at the - // existing position but also skip the current rule ONLY". What happens is - // all prior rules are also skipped which can result in matching the wrong - // thing. Example of matching "booger": - - // our matcher is [string, "booger", number] - // - // ....booger.... - - // if "booger" is ignored then we'd really need a regex to scan from the - // SAME position for only: [string, number] but ignoring "booger" (if it - // was the first match), a simple resume would scan ahead who knows how - // far looking only for "number", ignoring potential string matches (or - // future "booger" matches that might be valid.) - - // So what we do: We execute two matchers, one resuming at the same - // position, but the second full matcher starting at the position after: - - // /--- resume first regex match here (for [number]) - // |/---- full match here for [string, "booger", number] - // vv - // ....booger.... - - // Which ever results in a match first is then used. So this 3-4 step - // process essentially allows us to say "match at this position, excluding - // a prior rule that was ignored". - // - // 1. Match "booger" first, ignore. Also proves that [string] does non match. - // 2. Resume matching for [number] - // 3. Match at index + 1 for [string, "booger", number] - // 4. If #2 and #3 result in matches, which came first? - if (this.resumingScanAtSamePosition()) { - if (result && result.index === this.lastIndex) ; else { // use the second matcher result - const m2 = this.getMatcher(0); - m2.lastIndex = this.lastIndex + 1; - result = m2.exec(s); - } - } - - if (result) { - this.regexIndex += result.position + 1; - if (this.regexIndex === this.count) { - // wrap-around to considering all matches again - this.considerAll(); - } - } - - return result; - } - } - - /** - * Given a mode, builds a huge ResumableMultiRegex that can be used to walk - * the content and find matches. - * - * @param {CompiledMode} mode - * @returns {ResumableMultiRegex} - */ - function buildModeRegex(mode) { - const mm = new ResumableMultiRegex(); - - mode.contains.forEach(term => mm.addRule(term.begin, { rule: term, type: "begin" })); - - if (mode.terminatorEnd) { - mm.addRule(mode.terminatorEnd, { type: "end" }); - } - if (mode.illegal) { - mm.addRule(mode.illegal, { type: "illegal" }); - } - - return mm; - } - - /** skip vs abort vs ignore - * - * @skip - The mode is still entered and exited normally (and contains rules apply), - * but all content is held and added to the parent buffer rather than being - * output when the mode ends. Mostly used with `sublanguage` to build up - * a single large buffer than can be parsed by sublanguage. - * - * - The mode begin ands ends normally. - * - Content matched is added to the parent mode buffer. - * - The parser cursor is moved forward normally. - * - * @abort - A hack placeholder until we have ignore. Aborts the mode (as if it - * never matched) but DOES NOT continue to match subsequent `contains` - * modes. Abort is bad/suboptimal because it can result in modes - * farther down not getting applied because an earlier rule eats the - * content but then aborts. - * - * - The mode does not begin. - * - Content matched by `begin` is added to the mode buffer. - * - The parser cursor is moved forward accordingly. - * - * @ignore - Ignores the mode (as if it never matched) and continues to match any - * subsequent `contains` modes. Ignore isn't technically possible with - * the current parser implementation. - * - * - The mode does not begin. - * - Content matched by `begin` is ignored. - * - The parser cursor is not moved forward. - */ - - /** - * Compiles an individual mode - * - * This can raise an error if the mode contains certain detectable known logic - * issues. - * @param {Mode} mode - * @param {CompiledMode | null} [parent] - * @returns {CompiledMode | never} - */ - function compileMode(mode, parent) { - const cmode = /** @type CompiledMode */ (mode); - if (mode.isCompiled) return cmode; - - [ - scopeClassName, - // do this early so compiler extensions generally don't have to worry about - // the distinction between match/begin - compileMatch, - MultiClass, - beforeMatchExt - ].forEach(ext => ext(mode, parent)); - - language.compilerExtensions.forEach(ext => ext(mode, parent)); - - // __beforeBegin is considered private API, internal use only - mode.__beforeBegin = null; - - [ - beginKeywords, - // do this later so compiler extensions that come earlier have access to the - // raw array if they wanted to perhaps manipulate it, etc. - compileIllegal, - // default to 1 relevance if not specified - compileRelevance - ].forEach(ext => ext(mode, parent)); - - mode.isCompiled = true; - - let keywordPattern = null; - if (typeof mode.keywords === "object" && mode.keywords.$pattern) { - // we need a copy because keywords might be compiled multiple times - // so we can't go deleting $pattern from the original on the first - // pass - mode.keywords = Object.assign({}, mode.keywords); - keywordPattern = mode.keywords.$pattern; - delete mode.keywords.$pattern; - } - keywordPattern = keywordPattern || /\w+/; - - if (mode.keywords) { - mode.keywords = compileKeywords(mode.keywords, language.case_insensitive); - } - - cmode.keywordPatternRe = langRe(keywordPattern, true); - - if (parent) { - if (!mode.begin) mode.begin = /\B|\b/; - cmode.beginRe = langRe(cmode.begin); - if (!mode.end && !mode.endsWithParent) mode.end = /\B|\b/; - if (mode.end) cmode.endRe = langRe(cmode.end); - cmode.terminatorEnd = source(cmode.end) || ''; - if (mode.endsWithParent && parent.terminatorEnd) { - cmode.terminatorEnd += (mode.end ? '|' : '') + parent.terminatorEnd; - } - } - if (mode.illegal) cmode.illegalRe = langRe(/** @type {RegExp | string} */ (mode.illegal)); - if (!mode.contains) mode.contains = []; - - mode.contains = [].concat(...mode.contains.map(function(c) { - return expandOrCloneMode(c === 'self' ? mode : c); - })); - mode.contains.forEach(function(c) { compileMode(/** @type Mode */ (c), cmode); }); - - if (mode.starts) { - compileMode(mode.starts, parent); - } - - cmode.matcher = buildModeRegex(cmode); - return cmode; - } - - if (!language.compilerExtensions) language.compilerExtensions = []; - - // self is not valid at the top-level - if (language.contains && language.contains.includes('self')) { - throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation."); - } - - // we need a null object, which inherit will guarantee - language.classNameAliases = inherit$1(language.classNameAliases || {}); - - return compileMode(/** @type Mode */ (language)); - } - - /** - * Determines if a mode has a dependency on it's parent or not - * - * If a mode does have a parent dependency then often we need to clone it if - * it's used in multiple places so that each copy points to the correct parent, - * where-as modes without a parent can often safely be re-used at the bottom of - * a mode chain. - * - * @param {Mode | null} mode - * @returns {boolean} - is there a dependency on the parent? - * */ - function dependencyOnParent(mode) { - if (!mode) return false; - - return mode.endsWithParent || dependencyOnParent(mode.starts); - } - - /** - * Expands a mode or clones it if necessary - * - * This is necessary for modes with parental dependenceis (see notes on - * `dependencyOnParent`) and for nodes that have `variants` - which must then be - * exploded into their own individual modes at compile time. - * - * @param {Mode} mode - * @returns {Mode | Mode[]} - * */ - function expandOrCloneMode(mode) { - if (mode.variants && !mode.cachedVariants) { - mode.cachedVariants = mode.variants.map(function(variant) { - return inherit$1(mode, { variants: null }, variant); - }); - } - - // EXPAND - // if we have variants then essentially "replace" the mode with the variants - // this happens in compileMode, where this function is called from - if (mode.cachedVariants) { - return mode.cachedVariants; - } - - // CLONE - // if we have dependencies on parents then we need a unique - // instance of ourselves, so we can be reused with many - // different parents without issue - if (dependencyOnParent(mode)) { - return inherit$1(mode, { starts: mode.starts ? inherit$1(mode.starts) : null }); - } - - if (Object.isFrozen(mode)) { - return inherit$1(mode); - } - - // no special dependency issues, just return ourselves - return mode; - } - - var version = "11.7.0"; - - class HTMLInjectionError extends Error { - constructor(reason, html) { - super(reason); - this.name = "HTMLInjectionError"; - this.html = html; - } - } - - /* - Syntax highlighting with language autodetection. - https://highlightjs.org/ - */ - - /** - @typedef {import('highlight.js').Mode} Mode - @typedef {import('highlight.js').CompiledMode} CompiledMode - @typedef {import('highlight.js').CompiledScope} CompiledScope - @typedef {import('highlight.js').Language} Language - @typedef {import('highlight.js').HLJSApi} HLJSApi - @typedef {import('highlight.js').HLJSPlugin} HLJSPlugin - @typedef {import('highlight.js').PluginEvent} PluginEvent - @typedef {import('highlight.js').HLJSOptions} HLJSOptions - @typedef {import('highlight.js').LanguageFn} LanguageFn - @typedef {import('highlight.js').HighlightedHTMLElement} HighlightedHTMLElement - @typedef {import('highlight.js').BeforeHighlightContext} BeforeHighlightContext - @typedef {import('highlight.js/private').MatchType} MatchType - @typedef {import('highlight.js/private').KeywordData} KeywordData - @typedef {import('highlight.js/private').EnhancedMatch} EnhancedMatch - @typedef {import('highlight.js/private').AnnotatedError} AnnotatedError - @typedef {import('highlight.js').AutoHighlightResult} AutoHighlightResult - @typedef {import('highlight.js').HighlightOptions} HighlightOptions - @typedef {import('highlight.js').HighlightResult} HighlightResult - */ - - - const escape = escapeHTML; - const inherit = inherit$1; - const NO_MATCH = Symbol("nomatch"); - const MAX_KEYWORD_HITS = 7; - - /** - * @param {any} hljs - object that is extended (legacy) - * @returns {HLJSApi} - */ - const HLJS = function(hljs) { - // Global internal variables used within the highlight.js library. - /** @type {Record<string, Language>} */ - const languages = Object.create(null); - /** @type {Record<string, string>} */ - const aliases = Object.create(null); - /** @type {HLJSPlugin[]} */ - const plugins = []; - - // safe/production mode - swallows more errors, tries to keep running - // even if a single syntax or parse hits a fatal error - let SAFE_MODE = true; - const LANGUAGE_NOT_FOUND = "Could not find the language '{}', did you forget to load/include a language module?"; - /** @type {Language} */ - const PLAINTEXT_LANGUAGE = { disableAutodetect: true, name: 'Plain text', contains: [] }; - - // Global options used when within external APIs. This is modified when - // calling the `hljs.configure` function. - /** @type HLJSOptions */ - let options = { - ignoreUnescapedHTML: false, - throwUnescapedHTML: false, - noHighlightRe: /^(no-?highlight)$/i, - languageDetectRe: /\blang(?:uage)?-([\w-]+)\b/i, - classPrefix: 'hljs-', - cssSelector: 'pre code', - languages: null, - // beta configuration options, subject to change, welcome to discuss - // https://github.com/highlightjs/highlight.js/issues/1086 - __emitter: TokenTreeEmitter - }; - - /* Utility functions */ - - /** - * Tests a language name to see if highlighting should be skipped - * @param {string} languageName - */ - function shouldNotHighlight(languageName) { - return options.noHighlightRe.test(languageName); - } - - /** - * @param {HighlightedHTMLElement} block - the HTML element to determine language for - */ - function blockLanguage(block) { - let classes = block.className + ' '; - - classes += block.parentNode ? block.parentNode.className : ''; - - // language-* takes precedence over non-prefixed class names. - const match = options.languageDetectRe.exec(classes); - if (match) { - const language = getLanguage(match[1]); - if (!language) { - warn(LANGUAGE_NOT_FOUND.replace("{}", match[1])); - warn("Falling back to no-highlight mode for this block.", block); - } - return language ? match[1] : 'no-highlight'; - } - - return classes - .split(/\s+/) - .find((_class) => shouldNotHighlight(_class) || getLanguage(_class)); - } - - /** - * Core highlighting function. - * - * OLD API - * highlight(lang, code, ignoreIllegals, continuation) - * - * NEW API - * highlight(code, {lang, ignoreIllegals}) - * - * @param {string} codeOrLanguageName - the language to use for highlighting - * @param {string | HighlightOptions} optionsOrCode - the code to highlight - * @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail - * - * @returns {HighlightResult} Result - an object that represents the result - * @property {string} language - the language name - * @property {number} relevance - the relevance score - * @property {string} value - the highlighted HTML code - * @property {string} code - the original raw code - * @property {CompiledMode} top - top of the current mode stack - * @property {boolean} illegal - indicates whether any illegal matches were found - */ - function highlight(codeOrLanguageName, optionsOrCode, ignoreIllegals) { - let code = ""; - let languageName = ""; - if (typeof optionsOrCode === "object") { - code = codeOrLanguageName; - ignoreIllegals = optionsOrCode.ignoreIllegals; - languageName = optionsOrCode.language; - } else { - // old API - deprecated("10.7.0", "highlight(lang, code, ...args) has been deprecated."); - deprecated("10.7.0", "Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"); - languageName = codeOrLanguageName; - code = optionsOrCode; - } - - // https://github.com/highlightjs/highlight.js/issues/3149 - // eslint-disable-next-line no-undefined - if (ignoreIllegals === undefined) { ignoreIllegals = true; } - - /** @type {BeforeHighlightContext} */ - const context = { - code, - language: languageName - }; - // the plugin can change the desired language or the code to be highlighted - // just be changing the object it was passed - fire("before:highlight", context); - - // a before plugin can usurp the result completely by providing it's own - // in which case we don't even need to call highlight - const result = context.result - ? context.result - : _highlight(context.language, context.code, ignoreIllegals); - - result.code = context.code; - // the plugin can change anything in result to suite it - fire("after:highlight", result); - - return result; - } - - /** - * private highlight that's used internally and does not fire callbacks - * - * @param {string} languageName - the language to use for highlighting - * @param {string} codeToHighlight - the code to highlight - * @param {boolean?} [ignoreIllegals] - whether to ignore illegal matches, default is to bail - * @param {CompiledMode?} [continuation] - current continuation mode, if any - * @returns {HighlightResult} - result of the highlight operation - */ - function _highlight(languageName, codeToHighlight, ignoreIllegals, continuation) { - const keywordHits = Object.create(null); - - /** - * Return keyword data if a match is a keyword - * @param {CompiledMode} mode - current mode - * @param {string} matchText - the textual match - * @returns {KeywordData | false} - */ - function keywordData(mode, matchText) { - return mode.keywords[matchText]; - } - - function processKeywords() { - if (!top.keywords) { - emitter.addText(modeBuffer); - return; - } - - let lastIndex = 0; - top.keywordPatternRe.lastIndex = 0; - let match = top.keywordPatternRe.exec(modeBuffer); - let buf = ""; - - while (match) { - buf += modeBuffer.substring(lastIndex, match.index); - const word = language.case_insensitive ? match[0].toLowerCase() : match[0]; - const data = keywordData(top, word); - if (data) { - const [kind, keywordRelevance] = data; - emitter.addText(buf); - buf = ""; - - keywordHits[word] = (keywordHits[word] || 0) + 1; - if (keywordHits[word] <= MAX_KEYWORD_HITS) relevance += keywordRelevance; - if (kind.startsWith("_")) { - // _ implied for relevance only, do not highlight - // by applying a class name - buf += match[0]; - } else { - const cssClass = language.classNameAliases[kind] || kind; - emitter.addKeyword(match[0], cssClass); - } - } else { - buf += match[0]; - } - lastIndex = top.keywordPatternRe.lastIndex; - match = top.keywordPatternRe.exec(modeBuffer); - } - buf += modeBuffer.substring(lastIndex); - emitter.addText(buf); - } - - function processSubLanguage() { - if (modeBuffer === "") return; - /** @type HighlightResult */ - let result = null; - - if (typeof top.subLanguage === 'string') { - if (!languages[top.subLanguage]) { - emitter.addText(modeBuffer); - return; - } - result = _highlight(top.subLanguage, modeBuffer, true, continuations[top.subLanguage]); - continuations[top.subLanguage] = /** @type {CompiledMode} */ (result._top); - } else { - result = highlightAuto(modeBuffer, top.subLanguage.length ? top.subLanguage : null); - } - - // Counting embedded language score towards the host language may be disabled - // with zeroing the containing mode relevance. Use case in point is Markdown that - // allows XML everywhere and makes every XML snippet to have a much larger Markdown - // score. - if (top.relevance > 0) { - relevance += result.relevance; - } - emitter.addSublanguage(result._emitter, result.language); - } - - function processBuffer() { - if (top.subLanguage != null) { - processSubLanguage(); - } else { - processKeywords(); - } - modeBuffer = ''; - } - - /** - * @param {CompiledScope} scope - * @param {RegExpMatchArray} match - */ - function emitMultiClass(scope, match) { - let i = 1; - const max = match.length - 1; - while (i <= max) { - if (!scope._emit[i]) { i++; continue; } - const klass = language.classNameAliases[scope[i]] || scope[i]; - const text = match[i]; - if (klass) { - emitter.addKeyword(text, klass); - } else { - modeBuffer = text; - processKeywords(); - modeBuffer = ""; - } - i++; - } - } - - /** - * @param {CompiledMode} mode - new mode to start - * @param {RegExpMatchArray} match - */ - function startNewMode(mode, match) { - if (mode.scope && typeof mode.scope === "string") { - emitter.openNode(language.classNameAliases[mode.scope] || mode.scope); - } - if (mode.beginScope) { - // beginScope just wraps the begin match itself in a scope - if (mode.beginScope._wrap) { - emitter.addKeyword(modeBuffer, language.classNameAliases[mode.beginScope._wrap] || mode.beginScope._wrap); - modeBuffer = ""; - } else if (mode.beginScope._multi) { - // at this point modeBuffer should just be the match - emitMultiClass(mode.beginScope, match); - modeBuffer = ""; - } - } - - top = Object.create(mode, { parent: { value: top } }); - return top; - } - - /** - * @param {CompiledMode } mode - the mode to potentially end - * @param {RegExpMatchArray} match - the latest match - * @param {string} matchPlusRemainder - match plus remainder of content - * @returns {CompiledMode | void} - the next mode, or if void continue on in current mode - */ - function endOfMode(mode, match, matchPlusRemainder) { - let matched = startsWith(mode.endRe, matchPlusRemainder); - - if (matched) { - if (mode["on:end"]) { - const resp = new Response(mode); - mode["on:end"](match, resp); - if (resp.isMatchIgnored) matched = false; - } - - if (matched) { - while (mode.endsParent && mode.parent) { - mode = mode.parent; - } - return mode; - } - } - // even if on:end fires an `ignore` it's still possible - // that we might trigger the end node because of a parent mode - if (mode.endsWithParent) { - return endOfMode(mode.parent, match, matchPlusRemainder); - } - } - - /** - * Handle matching but then ignoring a sequence of text - * - * @param {string} lexeme - string containing full match text - */ - function doIgnore(lexeme) { - if (top.matcher.regexIndex === 0) { - // no more regexes to potentially match here, so we move the cursor forward one - // space - modeBuffer += lexeme[0]; - return 1; - } else { - // no need to move the cursor, we still have additional regexes to try and - // match at this very spot - resumeScanAtSamePosition = true; - return 0; - } - } - - /** - * Handle the start of a new potential mode match - * - * @param {EnhancedMatch} match - the current match - * @returns {number} how far to advance the parse cursor - */ - function doBeginMatch(match) { - const lexeme = match[0]; - const newMode = match.rule; - - const resp = new Response(newMode); - // first internal before callbacks, then the public ones - const beforeCallbacks = [newMode.__beforeBegin, newMode["on:begin"]]; - for (const cb of beforeCallbacks) { - if (!cb) continue; - cb(match, resp); - if (resp.isMatchIgnored) return doIgnore(lexeme); - } - - if (newMode.skip) { - modeBuffer += lexeme; - } else { - if (newMode.excludeBegin) { - modeBuffer += lexeme; - } - processBuffer(); - if (!newMode.returnBegin && !newMode.excludeBegin) { - modeBuffer = lexeme; - } - } - startNewMode(newMode, match); - return newMode.returnBegin ? 0 : lexeme.length; - } - - /** - * Handle the potential end of mode - * - * @param {RegExpMatchArray} match - the current match - */ - function doEndMatch(match) { - const lexeme = match[0]; - const matchPlusRemainder = codeToHighlight.substring(match.index); - - const endMode = endOfMode(top, match, matchPlusRemainder); - if (!endMode) { return NO_MATCH; } - - const origin = top; - if (top.endScope && top.endScope._wrap) { - processBuffer(); - emitter.addKeyword(lexeme, top.endScope._wrap); - } else if (top.endScope && top.endScope._multi) { - processBuffer(); - emitMultiClass(top.endScope, match); - } else if (origin.skip) { - modeBuffer += lexeme; - } else { - if (!(origin.returnEnd || origin.excludeEnd)) { - modeBuffer += lexeme; - } - processBuffer(); - if (origin.excludeEnd) { - modeBuffer = lexeme; - } - } - do { - if (top.scope) { - emitter.closeNode(); - } - if (!top.skip && !top.subLanguage) { - relevance += top.relevance; - } - top = top.parent; - } while (top !== endMode.parent); - if (endMode.starts) { - startNewMode(endMode.starts, match); - } - return origin.returnEnd ? 0 : lexeme.length; - } - - function processContinuations() { - const list = []; - for (let current = top; current !== language; current = current.parent) { - if (current.scope) { - list.unshift(current.scope); - } - } - list.forEach(item => emitter.openNode(item)); - } - - /** @type {{type?: MatchType, index?: number, rule?: Mode}}} */ - let lastMatch = {}; - - /** - * Process an individual match - * - * @param {string} textBeforeMatch - text preceding the match (since the last match) - * @param {EnhancedMatch} [match] - the match itself - */ - function processLexeme(textBeforeMatch, match) { - const lexeme = match && match[0]; - - // add non-matched text to the current mode buffer - modeBuffer += textBeforeMatch; - - if (lexeme == null) { - processBuffer(); - return 0; - } - - // we've found a 0 width match and we're stuck, so we need to advance - // this happens when we have badly behaved rules that have optional matchers to the degree that - // sometimes they can end up matching nothing at all - // Ref: https://github.com/highlightjs/highlight.js/issues/2140 - if (lastMatch.type === "begin" && match.type === "end" && lastMatch.index === match.index && lexeme === "") { - // spit the "skipped" character that our regex choked on back into the output sequence - modeBuffer += codeToHighlight.slice(match.index, match.index + 1); - if (!SAFE_MODE) { - /** @type {AnnotatedError} */ - const err = new Error(`0 width match regex (${languageName})`); - err.languageName = languageName; - err.badRule = lastMatch.rule; - throw err; - } - return 1; - } - lastMatch = match; - - if (match.type === "begin") { - return doBeginMatch(match); - } else if (match.type === "illegal" && !ignoreIllegals) { - // illegal match, we do not continue processing - /** @type {AnnotatedError} */ - const err = new Error('Illegal lexeme "' + lexeme + '" for mode "' + (top.scope || '<unnamed>') + '"'); - err.mode = top; - throw err; - } else if (match.type === "end") { - const processed = doEndMatch(match); - if (processed !== NO_MATCH) { - return processed; - } - } - - // edge case for when illegal matches $ (end of line) which is technically - // a 0 width match but not a begin/end match so it's not caught by the - // first handler (when ignoreIllegals is true) - if (match.type === "illegal" && lexeme === "") { - // advance so we aren't stuck in an infinite loop - return 1; - } - - // infinite loops are BAD, this is a last ditch catch all. if we have a - // decent number of iterations yet our index (cursor position in our - // parsing) still 3x behind our index then something is very wrong - // so we bail - if (iterations > 100000 && iterations > match.index * 3) { - const err = new Error('potential infinite loop, way more iterations than matches'); - throw err; - } - - /* - Why might be find ourselves here? An potential end match that was - triggered but could not be completed. IE, `doEndMatch` returned NO_MATCH. - (this could be because a callback requests the match be ignored, etc) - - This causes no real harm other than stopping a few times too many. - */ - - modeBuffer += lexeme; - return lexeme.length; - } - - const language = getLanguage(languageName); - if (!language) { - error(LANGUAGE_NOT_FOUND.replace("{}", languageName)); - throw new Error('Unknown language: "' + languageName + '"'); - } - - const md = compileLanguage(language); - let result = ''; - /** @type {CompiledMode} */ - let top = continuation || md; - /** @type Record<string,CompiledMode> */ - const continuations = {}; // keep continuations for sub-languages - const emitter = new options.__emitter(options); - processContinuations(); - let modeBuffer = ''; - let relevance = 0; - let index = 0; - let iterations = 0; - let resumeScanAtSamePosition = false; - - try { - top.matcher.considerAll(); - - for (;;) { - iterations++; - if (resumeScanAtSamePosition) { - // only regexes not matched previously will now be - // considered for a potential match - resumeScanAtSamePosition = false; - } else { - top.matcher.considerAll(); - } - top.matcher.lastIndex = index; - - const match = top.matcher.exec(codeToHighlight); - // console.log("match", match[0], match.rule && match.rule.begin) - - if (!match) break; - - const beforeMatch = codeToHighlight.substring(index, match.index); - const processedCount = processLexeme(beforeMatch, match); - index = match.index + processedCount; - } - processLexeme(codeToHighlight.substring(index)); - emitter.closeAllNodes(); - emitter.finalize(); - result = emitter.toHTML(); - - return { - language: languageName, - value: result, - relevance: relevance, - illegal: false, - _emitter: emitter, - _top: top - }; - } catch (err) { - if (err.message && err.message.includes('Illegal')) { - return { - language: languageName, - value: escape(codeToHighlight), - illegal: true, - relevance: 0, - _illegalBy: { - message: err.message, - index: index, - context: codeToHighlight.slice(index - 100, index + 100), - mode: err.mode, - resultSoFar: result - }, - _emitter: emitter - }; - } else if (SAFE_MODE) { - return { - language: languageName, - value: escape(codeToHighlight), - illegal: false, - relevance: 0, - errorRaised: err, - _emitter: emitter, - _top: top - }; - } else { - throw err; - } - } - } - - /** - * returns a valid highlight result, without actually doing any actual work, - * auto highlight starts with this and it's possible for small snippets that - * auto-detection may not find a better match - * @param {string} code - * @returns {HighlightResult} - */ - function justTextHighlightResult(code) { - const result = { - value: escape(code), - illegal: false, - relevance: 0, - _top: PLAINTEXT_LANGUAGE, - _emitter: new options.__emitter(options) - }; - result._emitter.addText(code); - return result; - } - - /** - Highlighting with language detection. Accepts a string with the code to - highlight. Returns an object with the following properties: - - - language (detected language) - - relevance (int) - - value (an HTML string with highlighting markup) - - secondBest (object with the same structure for second-best heuristically - detected language, may be absent) - - @param {string} code - @param {Array<string>} [languageSubset] - @returns {AutoHighlightResult} - */ - function highlightAuto(code, languageSubset) { - languageSubset = languageSubset || options.languages || Object.keys(languages); - const plaintext = justTextHighlightResult(code); - - const results = languageSubset.filter(getLanguage).filter(autoDetection).map(name => - _highlight(name, code, false) - ); - results.unshift(plaintext); // plaintext is always an option - - const sorted = results.sort((a, b) => { - // sort base on relevance - if (a.relevance !== b.relevance) return b.relevance - a.relevance; - - // always award the tie to the base language - // ie if C++ and Arduino are tied, it's more likely to be C++ - if (a.language && b.language) { - if (getLanguage(a.language).supersetOf === b.language) { - return 1; - } else if (getLanguage(b.language).supersetOf === a.language) { - return -1; - } - } - - // otherwise say they are equal, which has the effect of sorting on - // relevance while preserving the original ordering - which is how ties - // have historically been settled, ie the language that comes first always - // wins in the case of a tie - return 0; - }); - - const [best, secondBest] = sorted; - - /** @type {AutoHighlightResult} */ - const result = best; - result.secondBest = secondBest; - - return result; - } - - /** - * Builds new class name for block given the language name - * - * @param {HTMLElement} element - * @param {string} [currentLang] - * @param {string} [resultLang] - */ - function updateClassName(element, currentLang, resultLang) { - const language = (currentLang && aliases[currentLang]) || resultLang; - - element.classList.add("hljs"); - element.classList.add(`language-${language}`); - } - - /** - * Applies highlighting to a DOM node containing code. - * - * @param {HighlightedHTMLElement} element - the HTML element to highlight - */ - function highlightElement(element) { - /** @type HTMLElement */ - let node = null; - const language = blockLanguage(element); - - if (shouldNotHighlight(language)) return; - - fire("before:highlightElement", - { el: element, language: language }); - - // we should be all text, no child nodes (unescaped HTML) - this is possibly - // an HTML injection attack - it's likely too late if this is already in - // production (the code has likely already done its damage by the time - // we're seeing it)... but we yell loudly about this so that hopefully it's - // more likely to be caught in development before making it to production - if (element.children.length > 0) { - if (!options.ignoreUnescapedHTML) { - console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."); - console.warn("https://github.com/highlightjs/highlight.js/wiki/security"); - console.warn("The element with unescaped HTML:"); - console.warn(element); - } - if (options.throwUnescapedHTML) { - const err = new HTMLInjectionError( - "One of your code blocks includes unescaped HTML.", - element.innerHTML - ); - throw err; - } - } - - node = element; - const text = node.textContent; - const result = language ? highlight(text, { language, ignoreIllegals: true }) : highlightAuto(text); - - element.innerHTML = result.value; - updateClassName(element, language, result.language); - element.result = { - language: result.language, - // TODO: remove with version 11.0 - re: result.relevance, - relevance: result.relevance - }; - if (result.secondBest) { - element.secondBest = { - language: result.secondBest.language, - relevance: result.secondBest.relevance - }; - } - - fire("after:highlightElement", { el: element, result, text }); - } - - /** - * Updates highlight.js global options with the passed options - * - * @param {Partial<HLJSOptions>} userOptions - */ - function configure(userOptions) { - options = inherit(options, userOptions); - } - - // TODO: remove v12, deprecated - const initHighlighting = () => { - highlightAll(); - deprecated("10.6.0", "initHighlighting() deprecated. Use highlightAll() now."); - }; - - // TODO: remove v12, deprecated - function initHighlightingOnLoad() { - highlightAll(); - deprecated("10.6.0", "initHighlightingOnLoad() deprecated. Use highlightAll() now."); - } - - let wantsHighlight = false; - - /** - * auto-highlights all pre>code elements on the page - */ - function highlightAll() { - // if we are called too early in the loading process - if (document.readyState === "loading") { - wantsHighlight = true; - return; - } - - const blocks = document.querySelectorAll(options.cssSelector); - blocks.forEach(highlightElement); - } - - function boot() { - // if a highlight was requested before DOM was loaded, do now - if (wantsHighlight) highlightAll(); - } - - // make sure we are in the browser environment - if (typeof window !== 'undefined' && window.addEventListener) { - window.addEventListener('DOMContentLoaded', boot, false); - } - - /** - * Register a language grammar module - * - * @param {string} languageName - * @param {LanguageFn} languageDefinition - */ - function registerLanguage(languageName, languageDefinition) { - let lang = null; - try { - lang = languageDefinition(hljs); - } catch (error$1) { - error("Language definition for '{}' could not be registered.".replace("{}", languageName)); - // hard or soft error - if (!SAFE_MODE) { throw error$1; } else { error(error$1); } - // languages that have serious errors are replaced with essentially a - // "plaintext" stand-in so that the code blocks will still get normal - // css classes applied to them - and one bad language won't break the - // entire highlighter - lang = PLAINTEXT_LANGUAGE; - } - // give it a temporary name if it doesn't have one in the meta-data - if (!lang.name) lang.name = languageName; - languages[languageName] = lang; - lang.rawDefinition = languageDefinition.bind(null, hljs); - - if (lang.aliases) { - registerAliases(lang.aliases, { languageName }); - } - } - - /** - * Remove a language grammar module - * - * @param {string} languageName - */ - function unregisterLanguage(languageName) { - delete languages[languageName]; - for (const alias of Object.keys(aliases)) { - if (aliases[alias] === languageName) { - delete aliases[alias]; - } - } - } - - /** - * @returns {string[]} List of language internal names - */ - function listLanguages() { - return Object.keys(languages); - } - - /** - * @param {string} name - name of the language to retrieve - * @returns {Language | undefined} - */ - function getLanguage(name) { - name = (name || '').toLowerCase(); - return languages[name] || languages[aliases[name]]; - } - - /** - * - * @param {string|string[]} aliasList - single alias or list of aliases - * @param {{languageName: string}} opts - */ - function registerAliases(aliasList, { languageName }) { - if (typeof aliasList === 'string') { - aliasList = [aliasList]; - } - aliasList.forEach(alias => { aliases[alias.toLowerCase()] = languageName; }); - } - - /** - * Determines if a given language has auto-detection enabled - * @param {string} name - name of the language - */ - function autoDetection(name) { - const lang = getLanguage(name); - return lang && !lang.disableAutodetect; - } - - /** - * Upgrades the old highlightBlock plugins to the new - * highlightElement API - * @param {HLJSPlugin} plugin - */ - function upgradePluginAPI(plugin) { - // TODO: remove with v12 - if (plugin["before:highlightBlock"] && !plugin["before:highlightElement"]) { - plugin["before:highlightElement"] = (data) => { - plugin["before:highlightBlock"]( - Object.assign({ block: data.el }, data) - ); - }; - } - if (plugin["after:highlightBlock"] && !plugin["after:highlightElement"]) { - plugin["after:highlightElement"] = (data) => { - plugin["after:highlightBlock"]( - Object.assign({ block: data.el }, data) - ); - }; - } - } - - /** - * @param {HLJSPlugin} plugin - */ - function addPlugin(plugin) { - upgradePluginAPI(plugin); - plugins.push(plugin); - } - - /** - * - * @param {PluginEvent} event - * @param {any} args - */ - function fire(event, args) { - const cb = event; - plugins.forEach(function(plugin) { - if (plugin[cb]) { - plugin[cb](args); - } - }); - } - - /** - * DEPRECATED - * @param {HighlightedHTMLElement} el - */ - function deprecateHighlightBlock(el) { - deprecated("10.7.0", "highlightBlock will be removed entirely in v12.0"); - deprecated("10.7.0", "Please use highlightElement now."); - - return highlightElement(el); - } - - /* Interface definition */ - Object.assign(hljs, { - highlight, - highlightAuto, - highlightAll, - highlightElement, - // TODO: Remove with v12 API - highlightBlock: deprecateHighlightBlock, - configure, - initHighlighting, - initHighlightingOnLoad, - registerLanguage, - unregisterLanguage, - listLanguages, - getLanguage, - registerAliases, - autoDetection, - inherit, - addPlugin - }); - - hljs.debugMode = function() { SAFE_MODE = false; }; - hljs.safeMode = function() { SAFE_MODE = true; }; - hljs.versionString = version; - - hljs.regex = { - concat: concat, - lookahead: lookahead, - either: either, - optional: optional, - anyNumberOfTimes: anyNumberOfTimes - }; - - for (const key in MODES$1) { - // @ts-ignore - if (typeof MODES$1[key] === "object") { - // @ts-ignore - deepFreezeEs6.exports(MODES$1[key]); - } - } - - // merge all the modes/regexes into our main object - Object.assign(hljs, MODES$1); - - return hljs; - }; - - // export an "instance" of the highlighter - var HighlightJS = HLJS({}); - - /* - Language: Gleam - Description: Gleam is a general-purpose functional language, which is compiled to both Erlang and JS - Category: functional - */ - - /** @type LanguageFn */ - function gleam (hljs) { - const KEYWORDS = - "as assert case const external fn if import let " + - "use opaque pub todo try tuple type"; - const STRING = { - className: "string", - variants: [{ begin: /"/, end: /"/ }], - contains: [hljs.BACKSLASH_ESCAPE], - relevance: 0, - }; - const NAME = { - className: "variable", - begin: "\\b[a-z][a-z0-9_]*\\b", - relevance: 0, - }; - const DISCARD_NAME = { - className: "comment", - begin: "\\b_[a-z][a-z0-9_]*\\b", - relevance: 0, - }; - const NUMBER = { - className: "number", - variants: [ - { - // binary - begin: "\\b0[bB](?:_?[01]+)+", - }, - { - // octal - begin: "\\b0[oO](?:_?[0-7]+)+", - }, - { - // hex - begin: "\\b0[xX](?:_?[0-9a-fA-F]+)+", - }, - { - // dec, float - begin: "\\b\\d(?:_?\\d+)*(?:\\.(?:\\d(?:_?\\d+)*)*)?", - }, - ], - relevance: 0, - }; - - return { - name: "Gleam", - aliases: ["gleam"], - contains: [ - hljs.C_LINE_COMMENT_MODE, - STRING, - { - // bit string - begin: "<<", - end: ">>", - contains: [ - { - className: "keyword", - beginKeywords: - "binary bytes int float bit_string bits utf8 utf16 utf32 " + - "utf8_codepoint utf16_codepoint utf32_codepoint signed unsigned " + - "big little native unit size", - }, - KEYWORDS, - STRING, - NAME, - DISCARD_NAME, - NUMBER, - ], - relevance: 10, - }, - { - className: "function", - beginKeywords: "fn", - end: "\\(", - excludeEnd: true, - contains: [ - { - className: "title", - begin: "[a-z][a-z0-9_]*\\w*", - relevance: 0, - }, - ], - }, - { - className: "keyword", - beginKeywords: KEYWORDS, - }, - { - // Type names and constructors - className: "title", - begin: "\\b[A-Z][A-Za-z0-9]*\\b", - relevance: 0, - }, - { - className: "operator", - begin: "[+\\-*/%!=<>&|.]+", - relevance: 0, - }, - NAME, - DISCARD_NAME, - NUMBER, - ], - }; - } - - /* - Language: Erlang - Description: Erlang is a general-purpose functional language, with strict evaluation, single assignment, and dynamic typing. - Author: Nikolay Zakharov <nikolay.desh@gmail.com>, Dmitry Kovega <arhibot@gmail.com> - Website: https://www.erlang.org - Category: functional - */ - - /** @type LanguageFn */ - function erlang(hljs) { - const BASIC_ATOM_RE = '[a-z\'][a-zA-Z0-9_\']*'; - const FUNCTION_NAME_RE = '(' + BASIC_ATOM_RE + ':' + BASIC_ATOM_RE + '|' + BASIC_ATOM_RE + ')'; - const ERLANG_RESERVED = { - keyword: - 'after and andalso|10 band begin bnot bor bsl bzr bxor case catch cond div end fun if ' - + 'let not of orelse|10 query receive rem try when xor', - literal: - 'false true' - }; - - const COMMENT = hljs.COMMENT('%', '$'); - const NUMBER = { - className: 'number', - begin: '\\b(\\d+(_\\d+)*#[a-fA-F0-9]+(_[a-fA-F0-9]+)*|\\d+(_\\d+)*(\\.\\d+(_\\d+)*)?([eE][-+]?\\d+)?)', - relevance: 0 - }; - const NAMED_FUN = { begin: 'fun\\s+' + BASIC_ATOM_RE + '/\\d+' }; - const FUNCTION_CALL = { - begin: FUNCTION_NAME_RE + '\\(', - end: '\\)', - returnBegin: true, - relevance: 0, - contains: [ - { - begin: FUNCTION_NAME_RE, - relevance: 0 - }, - { - begin: '\\(', - end: '\\)', - endsWithParent: true, - returnEnd: true, - relevance: 0 - // "contains" defined later - } - ] - }; - const TUPLE = { - begin: /\{/, - end: /\}/, - relevance: 0 - // "contains" defined later - }; - const VAR1 = { - begin: '\\b_([A-Z][A-Za-z0-9_]*)?', - relevance: 0 - }; - const VAR2 = { - begin: '[A-Z][a-zA-Z0-9_]*', - relevance: 0 - }; - const RECORD_ACCESS = { - begin: '#' + hljs.UNDERSCORE_IDENT_RE, - relevance: 0, - returnBegin: true, - contains: [ - { - begin: '#' + hljs.UNDERSCORE_IDENT_RE, - relevance: 0 - }, - { - begin: /\{/, - end: /\}/, - relevance: 0 - // "contains" defined later - } - ] - }; - - const BLOCK_STATEMENTS = { - beginKeywords: 'fun receive if try case', - end: 'end', - keywords: ERLANG_RESERVED - }; - BLOCK_STATEMENTS.contains = [ - COMMENT, - NAMED_FUN, - hljs.inherit(hljs.APOS_STRING_MODE, { className: '' }), - BLOCK_STATEMENTS, - FUNCTION_CALL, - hljs.QUOTE_STRING_MODE, - NUMBER, - TUPLE, - VAR1, - VAR2, - RECORD_ACCESS - ]; - - const BASIC_MODES = [ - COMMENT, - NAMED_FUN, - BLOCK_STATEMENTS, - FUNCTION_CALL, - hljs.QUOTE_STRING_MODE, - NUMBER, - TUPLE, - VAR1, - VAR2, - RECORD_ACCESS - ]; - FUNCTION_CALL.contains[1].contains = BASIC_MODES; - TUPLE.contains = BASIC_MODES; - RECORD_ACCESS.contains[1].contains = BASIC_MODES; - - const DIRECTIVES = [ - "-module", - "-record", - "-undef", - "-export", - "-ifdef", - "-ifndef", - "-author", - "-copyright", - "-doc", - "-vsn", - "-import", - "-include", - "-include_lib", - "-compile", - "-define", - "-else", - "-endif", - "-file", - "-behaviour", - "-behavior", - "-spec" - ]; - - const PARAMS = { - className: 'params', - begin: '\\(', - end: '\\)', - contains: BASIC_MODES - }; - return { - name: 'Erlang', - aliases: [ 'erl' ], - keywords: ERLANG_RESERVED, - illegal: '(</|\\*=|\\+=|-=|/\\*|\\*/|\\(\\*|\\*\\))', - contains: [ - { - className: 'function', - begin: '^' + BASIC_ATOM_RE + '\\s*\\(', - end: '->', - returnBegin: true, - illegal: '\\(|#|//|/\\*|\\\\|:|;', - contains: [ - PARAMS, - hljs.inherit(hljs.TITLE_MODE, { begin: BASIC_ATOM_RE }) - ], - starts: { - end: ';|\\.', - keywords: ERLANG_RESERVED, - contains: BASIC_MODES - } - }, - COMMENT, - { - begin: '^-', - end: '\\.', - relevance: 0, - excludeEnd: true, - returnBegin: true, - keywords: { - $pattern: '-' + hljs.IDENT_RE, - keyword: DIRECTIVES.map(x => `${x}|1.5`).join(" ") - }, - contains: [ PARAMS ] - }, - NUMBER, - hljs.QUOTE_STRING_MODE, - RECORD_ACCESS, - VAR1, - VAR2, - TUPLE, - { begin: /\.$/ } // relevance booster - ] - }; - } - - /* - Language: HTML, XML - Website: https://www.w3.org/XML/ - Category: common, web - Audit: 2020 - */ - - /** @type LanguageFn */ - function xml(hljs) { - const regex = hljs.regex; - // XML names can have the following additional letters: https://www.w3.org/TR/xml/#NT-NameChar - // OTHER_NAME_CHARS = /[:\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]/; - // Element names start with NAME_START_CHAR followed by optional other Unicode letters, ASCII digits, hyphens, underscores, and periods - // const TAG_NAME_RE = regex.concat(/[A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/, regex.optional(/[A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*:/), /[A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*/);; - // const XML_IDENT_RE = /[A-Z_a-z:\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]+/; - // const TAG_NAME_RE = regex.concat(/[A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/, regex.optional(/[A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*:/), /[A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*/); - // however, to cater for performance and more Unicode support rely simply on the Unicode letter class - const TAG_NAME_RE = regex.concat(/[\p{L}_]/u, regex.optional(/[\p{L}0-9_.-]*:/u), /[\p{L}0-9_.-]*/u); - const XML_IDENT_RE = /[\p{L}0-9._:-]+/u; - const XML_ENTITIES = { - className: 'symbol', - begin: /&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/ - }; - const XML_META_KEYWORDS = { - begin: /\s/, - contains: [ - { - className: 'keyword', - begin: /#?[a-z_][a-z1-9_-]+/, - illegal: /\n/ - } - ] - }; - const XML_META_PAR_KEYWORDS = hljs.inherit(XML_META_KEYWORDS, { - begin: /\(/, - end: /\)/ - }); - const APOS_META_STRING_MODE = hljs.inherit(hljs.APOS_STRING_MODE, { className: 'string' }); - const QUOTE_META_STRING_MODE = hljs.inherit(hljs.QUOTE_STRING_MODE, { className: 'string' }); - const TAG_INTERNALS = { - endsWithParent: true, - illegal: /</, - relevance: 0, - contains: [ - { - className: 'attr', - begin: XML_IDENT_RE, - relevance: 0 - }, - { - begin: /=\s*/, - relevance: 0, - contains: [ - { - className: 'string', - endsParent: true, - variants: [ - { - begin: /"/, - end: /"/, - contains: [ XML_ENTITIES ] - }, - { - begin: /'/, - end: /'/, - contains: [ XML_ENTITIES ] - }, - { begin: /[^\s"'=<>`]+/ } - ] - } - ] - } - ] - }; - return { - name: 'HTML, XML', - aliases: [ - 'html', - 'xhtml', - 'rss', - 'atom', - 'xjb', - 'xsd', - 'xsl', - 'plist', - 'wsf', - 'svg' - ], - case_insensitive: true, - unicodeRegex: true, - contains: [ - { - className: 'meta', - begin: /<![a-z]/, - end: />/, - relevance: 10, - contains: [ - XML_META_KEYWORDS, - QUOTE_META_STRING_MODE, - APOS_META_STRING_MODE, - XML_META_PAR_KEYWORDS, - { - begin: /\[/, - end: /\]/, - contains: [ - { - className: 'meta', - begin: /<![a-z]/, - end: />/, - contains: [ - XML_META_KEYWORDS, - XML_META_PAR_KEYWORDS, - QUOTE_META_STRING_MODE, - APOS_META_STRING_MODE - ] - } - ] - } - ] - }, - hljs.COMMENT( - /<!--/, - /-->/, - { relevance: 10 } - ), - { - begin: /<!\[CDATA\[/, - end: /\]\]>/, - relevance: 10 - }, - XML_ENTITIES, - // xml processing instructions - { - className: 'meta', - end: /\?>/, - variants: [ - { - begin: /<\?xml/, - relevance: 10, - contains: [ - QUOTE_META_STRING_MODE - ] - }, - { - begin: /<\?[a-z][a-z0-9]+/, - } - ] - - }, - { - className: 'tag', - /* - The lookahead pattern (?=...) ensures that 'begin' only matches - '<style' as a single word, followed by a whitespace or an - ending bracket. - */ - begin: /<style(?=\s|>)/, - end: />/, - keywords: { name: 'style' }, - contains: [ TAG_INTERNALS ], - starts: { - end: /<\/style>/, - returnEnd: true, - subLanguage: [ - 'css', - 'xml' - ] - } - }, - { - className: 'tag', - // See the comment in the <style tag about the lookahead pattern - begin: /<script(?=\s|>)/, - end: />/, - keywords: { name: 'script' }, - contains: [ TAG_INTERNALS ], - starts: { - end: /<\/script>/, - returnEnd: true, - subLanguage: [ - 'javascript', - 'handlebars', - 'xml' - ] - } - }, - // we need this for now for jSX - { - className: 'tag', - begin: /<>|<\/>/ - }, - // open tag - { - className: 'tag', - begin: regex.concat( - /</, - regex.lookahead(regex.concat( - TAG_NAME_RE, - // <tag/> - // <tag> - // <tag ... - regex.either(/\/>/, />/, /\s/) - )) - ), - end: /\/?>/, - contains: [ - { - className: 'name', - begin: TAG_NAME_RE, - relevance: 0, - starts: TAG_INTERNALS - } - ] - }, - // close tag - { - className: 'tag', - begin: regex.concat( - /<\//, - regex.lookahead(regex.concat( - TAG_NAME_RE, />/ - )) - ), - contains: [ - { - className: 'name', - begin: TAG_NAME_RE, - relevance: 0 - }, - { - begin: />/, - relevance: 0, - endsParent: true - } - ] - } - ] - }; - } - - const MODES = (hljs) => { - return { - IMPORTANT: { - scope: 'meta', - begin: '!important' - }, - BLOCK_COMMENT: hljs.C_BLOCK_COMMENT_MODE, - HEXCOLOR: { - scope: 'number', - begin: /#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/ - }, - FUNCTION_DISPATCH: { - className: "built_in", - begin: /[\w-]+(?=\()/ - }, - ATTRIBUTE_SELECTOR_MODE: { - scope: 'selector-attr', - begin: /\[/, - end: /\]/, - illegal: '$', - contains: [ - hljs.APOS_STRING_MODE, - hljs.QUOTE_STRING_MODE - ] - }, - CSS_NUMBER_MODE: { - scope: 'number', - begin: hljs.NUMBER_RE + '(' + - '%|em|ex|ch|rem' + - '|vw|vh|vmin|vmax' + - '|cm|mm|in|pt|pc|px' + - '|deg|grad|rad|turn' + - '|s|ms' + - '|Hz|kHz' + - '|dpi|dpcm|dppx' + - ')?', - relevance: 0 - }, - CSS_VARIABLE: { - className: "attr", - begin: /--[A-Za-z][A-Za-z0-9_-]*/ - } - }; - }; - - const TAGS = [ - 'a', - 'abbr', - 'address', - 'article', - 'aside', - 'audio', - 'b', - 'blockquote', - 'body', - 'button', - 'canvas', - 'caption', - 'cite', - 'code', - 'dd', - 'del', - 'details', - 'dfn', - 'div', - 'dl', - 'dt', - 'em', - 'fieldset', - 'figcaption', - 'figure', - 'footer', - 'form', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'header', - 'hgroup', - 'html', - 'i', - 'iframe', - 'img', - 'input', - 'ins', - 'kbd', - 'label', - 'legend', - 'li', - 'main', - 'mark', - 'menu', - 'nav', - 'object', - 'ol', - 'p', - 'q', - 'quote', - 'samp', - 'section', - 'span', - 'strong', - 'summary', - 'sup', - 'table', - 'tbody', - 'td', - 'textarea', - 'tfoot', - 'th', - 'thead', - 'time', - 'tr', - 'ul', - 'var', - 'video' - ]; - - const MEDIA_FEATURES = [ - 'any-hover', - 'any-pointer', - 'aspect-ratio', - 'color', - 'color-gamut', - 'color-index', - 'device-aspect-ratio', - 'device-height', - 'device-width', - 'display-mode', - 'forced-colors', - 'grid', - 'height', - 'hover', - 'inverted-colors', - 'monochrome', - 'orientation', - 'overflow-block', - 'overflow-inline', - 'pointer', - 'prefers-color-scheme', - 'prefers-contrast', - 'prefers-reduced-motion', - 'prefers-reduced-transparency', - 'resolution', - 'scan', - 'scripting', - 'update', - 'width', - // TODO: find a better solution? - 'min-width', - 'max-width', - 'min-height', - 'max-height' - ]; - - // https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes - const PSEUDO_CLASSES = [ - 'active', - 'any-link', - 'blank', - 'checked', - 'current', - 'default', - 'defined', - 'dir', // dir() - 'disabled', - 'drop', - 'empty', - 'enabled', - 'first', - 'first-child', - 'first-of-type', - 'fullscreen', - 'future', - 'focus', - 'focus-visible', - 'focus-within', - 'has', // has() - 'host', // host or host() - 'host-context', // host-context() - 'hover', - 'indeterminate', - 'in-range', - 'invalid', - 'is', // is() - 'lang', // lang() - 'last-child', - 'last-of-type', - 'left', - 'link', - 'local-link', - 'not', // not() - 'nth-child', // nth-child() - 'nth-col', // nth-col() - 'nth-last-child', // nth-last-child() - 'nth-last-col', // nth-last-col() - 'nth-last-of-type', //nth-last-of-type() - 'nth-of-type', //nth-of-type() - 'only-child', - 'only-of-type', - 'optional', - 'out-of-range', - 'past', - 'placeholder-shown', - 'read-only', - 'read-write', - 'required', - 'right', - 'root', - 'scope', - 'target', - 'target-within', - 'user-invalid', - 'valid', - 'visited', - 'where' // where() - ]; - - // https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements - const PSEUDO_ELEMENTS = [ - 'after', - 'backdrop', - 'before', - 'cue', - 'cue-region', - 'first-letter', - 'first-line', - 'grammar-error', - 'marker', - 'part', - 'placeholder', - 'selection', - 'slotted', - 'spelling-error' - ]; - - const ATTRIBUTES = [ - 'align-content', - 'align-items', - 'align-self', - 'all', - 'animation', - 'animation-delay', - 'animation-direction', - 'animation-duration', - 'animation-fill-mode', - 'animation-iteration-count', - 'animation-name', - 'animation-play-state', - 'animation-timing-function', - 'backface-visibility', - 'background', - 'background-attachment', - 'background-blend-mode', - 'background-clip', - 'background-color', - 'background-image', - 'background-origin', - 'background-position', - 'background-repeat', - 'background-size', - 'block-size', - 'border', - 'border-block', - 'border-block-color', - 'border-block-end', - 'border-block-end-color', - 'border-block-end-style', - 'border-block-end-width', - 'border-block-start', - 'border-block-start-color', - 'border-block-start-style', - 'border-block-start-width', - 'border-block-style', - 'border-block-width', - 'border-bottom', - 'border-bottom-color', - 'border-bottom-left-radius', - 'border-bottom-right-radius', - 'border-bottom-style', - 'border-bottom-width', - 'border-collapse', - 'border-color', - 'border-image', - 'border-image-outset', - 'border-image-repeat', - 'border-image-slice', - 'border-image-source', - 'border-image-width', - 'border-inline', - 'border-inline-color', - 'border-inline-end', - 'border-inline-end-color', - 'border-inline-end-style', - 'border-inline-end-width', - 'border-inline-start', - 'border-inline-start-color', - 'border-inline-start-style', - 'border-inline-start-width', - 'border-inline-style', - 'border-inline-width', - 'border-left', - 'border-left-color', - 'border-left-style', - 'border-left-width', - 'border-radius', - 'border-right', - 'border-right-color', - 'border-right-style', - 'border-right-width', - 'border-spacing', - 'border-style', - 'border-top', - 'border-top-color', - 'border-top-left-radius', - 'border-top-right-radius', - 'border-top-style', - 'border-top-width', - 'border-width', - 'bottom', - 'box-decoration-break', - 'box-shadow', - 'box-sizing', - 'break-after', - 'break-before', - 'break-inside', - 'caption-side', - 'caret-color', - 'clear', - 'clip', - 'clip-path', - 'clip-rule', - 'color', - 'column-count', - 'column-fill', - 'column-gap', - 'column-rule', - 'column-rule-color', - 'column-rule-style', - 'column-rule-width', - 'column-span', - 'column-width', - 'columns', - 'contain', - 'content', - 'content-visibility', - 'counter-increment', - 'counter-reset', - 'cue', - 'cue-after', - 'cue-before', - 'cursor', - 'direction', - 'display', - 'empty-cells', - 'filter', - 'flex', - 'flex-basis', - 'flex-direction', - 'flex-flow', - 'flex-grow', - 'flex-shrink', - 'flex-wrap', - 'float', - 'flow', - 'font', - 'font-display', - 'font-family', - 'font-feature-settings', - 'font-kerning', - 'font-language-override', - 'font-size', - 'font-size-adjust', - 'font-smoothing', - 'font-stretch', - 'font-style', - 'font-synthesis', - 'font-variant', - 'font-variant-caps', - 'font-variant-east-asian', - 'font-variant-ligatures', - 'font-variant-numeric', - 'font-variant-position', - 'font-variation-settings', - 'font-weight', - 'gap', - 'glyph-orientation-vertical', - 'grid', - 'grid-area', - 'grid-auto-columns', - 'grid-auto-flow', - 'grid-auto-rows', - 'grid-column', - 'grid-column-end', - 'grid-column-start', - 'grid-gap', - 'grid-row', - 'grid-row-end', - 'grid-row-start', - 'grid-template', - 'grid-template-areas', - 'grid-template-columns', - 'grid-template-rows', - 'hanging-punctuation', - 'height', - 'hyphens', - 'icon', - 'image-orientation', - 'image-rendering', - 'image-resolution', - 'ime-mode', - 'inline-size', - 'isolation', - 'justify-content', - 'left', - 'letter-spacing', - 'line-break', - 'line-height', - 'list-style', - 'list-style-image', - 'list-style-position', - 'list-style-type', - 'margin', - 'margin-block', - 'margin-block-end', - 'margin-block-start', - 'margin-bottom', - 'margin-inline', - 'margin-inline-end', - 'margin-inline-start', - 'margin-left', - 'margin-right', - 'margin-top', - 'marks', - 'mask', - 'mask-border', - 'mask-border-mode', - 'mask-border-outset', - 'mask-border-repeat', - 'mask-border-slice', - 'mask-border-source', - 'mask-border-width', - 'mask-clip', - 'mask-composite', - 'mask-image', - 'mask-mode', - 'mask-origin', - 'mask-position', - 'mask-repeat', - 'mask-size', - 'mask-type', - 'max-block-size', - 'max-height', - 'max-inline-size', - 'max-width', - 'min-block-size', - 'min-height', - 'min-inline-size', - 'min-width', - 'mix-blend-mode', - 'nav-down', - 'nav-index', - 'nav-left', - 'nav-right', - 'nav-up', - 'none', - 'normal', - 'object-fit', - 'object-position', - 'opacity', - 'order', - 'orphans', - 'outline', - 'outline-color', - 'outline-offset', - 'outline-style', - 'outline-width', - 'overflow', - 'overflow-wrap', - 'overflow-x', - 'overflow-y', - 'padding', - 'padding-block', - 'padding-block-end', - 'padding-block-start', - 'padding-bottom', - 'padding-inline', - 'padding-inline-end', - 'padding-inline-start', - 'padding-left', - 'padding-right', - 'padding-top', - 'page-break-after', - 'page-break-before', - 'page-break-inside', - 'pause', - 'pause-after', - 'pause-before', - 'perspective', - 'perspective-origin', - 'pointer-events', - 'position', - 'quotes', - 'resize', - 'rest', - 'rest-after', - 'rest-before', - 'right', - 'row-gap', - 'scroll-margin', - 'scroll-margin-block', - 'scroll-margin-block-end', - 'scroll-margin-block-start', - 'scroll-margin-bottom', - 'scroll-margin-inline', - 'scroll-margin-inline-end', - 'scroll-margin-inline-start', - 'scroll-margin-left', - 'scroll-margin-right', - 'scroll-margin-top', - 'scroll-padding', - 'scroll-padding-block', - 'scroll-padding-block-end', - 'scroll-padding-block-start', - 'scroll-padding-bottom', - 'scroll-padding-inline', - 'scroll-padding-inline-end', - 'scroll-padding-inline-start', - 'scroll-padding-left', - 'scroll-padding-right', - 'scroll-padding-top', - 'scroll-snap-align', - 'scroll-snap-stop', - 'scroll-snap-type', - 'scrollbar-color', - 'scrollbar-gutter', - 'scrollbar-width', - 'shape-image-threshold', - 'shape-margin', - 'shape-outside', - 'speak', - 'speak-as', - 'src', // @font-face - 'tab-size', - 'table-layout', - 'text-align', - 'text-align-all', - 'text-align-last', - 'text-combine-upright', - 'text-decoration', - 'text-decoration-color', - 'text-decoration-line', - 'text-decoration-style', - 'text-emphasis', - 'text-emphasis-color', - 'text-emphasis-position', - 'text-emphasis-style', - 'text-indent', - 'text-justify', - 'text-orientation', - 'text-overflow', - 'text-rendering', - 'text-shadow', - 'text-transform', - 'text-underline-position', - 'top', - 'transform', - 'transform-box', - 'transform-origin', - 'transform-style', - 'transition', - 'transition-delay', - 'transition-duration', - 'transition-property', - 'transition-timing-function', - 'unicode-bidi', - 'vertical-align', - 'visibility', - 'voice-balance', - 'voice-duration', - 'voice-family', - 'voice-pitch', - 'voice-range', - 'voice-rate', - 'voice-stress', - 'voice-volume', - 'white-space', - 'widows', - 'width', - 'will-change', - 'word-break', - 'word-spacing', - 'word-wrap', - 'writing-mode', - 'z-index' - // reverse makes sure longer attributes `font-weight` are matched fully - // instead of getting false positives on say `font` - ].reverse(); - - /* - Language: CSS - Category: common, css, web - Website: https://developer.mozilla.org/en-US/docs/Web/CSS - */ - - /** @type LanguageFn */ - function css(hljs) { - const regex = hljs.regex; - const modes = MODES(hljs); - const VENDOR_PREFIX = { begin: /-(webkit|moz|ms|o)-(?=[a-z])/ }; - const AT_MODIFIERS = "and or not only"; - const AT_PROPERTY_RE = /@-?\w[\w]*(-\w+)*/; // @-webkit-keyframes - const IDENT_RE = '[a-zA-Z-][a-zA-Z0-9_-]*'; - const STRINGS = [ - hljs.APOS_STRING_MODE, - hljs.QUOTE_STRING_MODE - ]; - - return { - name: 'CSS', - case_insensitive: true, - illegal: /[=|'\$]/, - keywords: { keyframePosition: "from to" }, - classNameAliases: { - // for visual continuity with `tag {}` and because we - // don't have a great class for this? - keyframePosition: "selector-tag" }, - contains: [ - modes.BLOCK_COMMENT, - VENDOR_PREFIX, - // to recognize keyframe 40% etc which are outside the scope of our - // attribute value mode - modes.CSS_NUMBER_MODE, - { - className: 'selector-id', - begin: /#[A-Za-z0-9_-]+/, - relevance: 0 - }, - { - className: 'selector-class', - begin: '\\.' + IDENT_RE, - relevance: 0 - }, - modes.ATTRIBUTE_SELECTOR_MODE, - { - className: 'selector-pseudo', - variants: [ - { begin: ':(' + PSEUDO_CLASSES.join('|') + ')' }, - { begin: ':(:)?(' + PSEUDO_ELEMENTS.join('|') + ')' } - ] - }, - // we may actually need this (12/2020) - // { // pseudo-selector params - // begin: /\(/, - // end: /\)/, - // contains: [ hljs.CSS_NUMBER_MODE ] - // }, - modes.CSS_VARIABLE, - { - className: 'attribute', - begin: '\\b(' + ATTRIBUTES.join('|') + ')\\b' - }, - // attribute values - { - begin: /:/, - end: /[;}{]/, - contains: [ - modes.BLOCK_COMMENT, - modes.HEXCOLOR, - modes.IMPORTANT, - modes.CSS_NUMBER_MODE, - ...STRINGS, - // needed to highlight these as strings and to avoid issues with - // illegal characters that might be inside urls that would tigger the - // languages illegal stack - { - begin: /(url|data-uri)\(/, - end: /\)/, - relevance: 0, // from keywords - keywords: { built_in: "url data-uri" }, - contains: [ - ...STRINGS, - { - className: "string", - // any character other than `)` as in `url()` will be the start - // of a string, which ends with `)` (from the parent mode) - begin: /[^)]/, - endsWithParent: true, - excludeEnd: true - } - ] - }, - modes.FUNCTION_DISPATCH - ] - }, - { - begin: regex.lookahead(/@/), - end: '[{;]', - relevance: 0, - illegal: /:/, // break on Less variables @var: ... - contains: [ - { - className: 'keyword', - begin: AT_PROPERTY_RE - }, - { - begin: /\s/, - endsWithParent: true, - excludeEnd: true, - relevance: 0, - keywords: { - $pattern: /[a-z-]+/, - keyword: AT_MODIFIERS, - attribute: MEDIA_FEATURES.join(" ") - }, - contains: [ - { - begin: /[a-z-]+(?=:)/, - className: "attribute" - }, - ...STRINGS, - modes.CSS_NUMBER_MODE - ] - } - ] - }, - { - className: 'selector-tag', - begin: '\\b(' + TAGS.join('|') + ')\\b' - } - ] - }; - } - - const IDENT_RE = '[A-Za-z$_][0-9A-Za-z$_]*'; - const KEYWORDS = [ - "as", // for exports - "in", - "of", - "if", - "for", - "while", - "finally", - "var", - "new", - "function", - "do", - "return", - "void", - "else", - "break", - "catch", - "instanceof", - "with", - "throw", - "case", - "default", - "try", - "switch", - "continue", - "typeof", - "delete", - "let", - "yield", - "const", - "class", - // JS handles these with a special rule - // "get", - // "set", - "debugger", - "async", - "await", - "static", - "import", - "from", - "export", - "extends" - ]; - const LITERALS = [ - "true", - "false", - "null", - "undefined", - "NaN", - "Infinity" - ]; - - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects - const TYPES = [ - // Fundamental objects - "Object", - "Function", - "Boolean", - "Symbol", - // numbers and dates - "Math", - "Date", - "Number", - "BigInt", - // text - "String", - "RegExp", - // Indexed collections - "Array", - "Float32Array", - "Float64Array", - "Int8Array", - "Uint8Array", - "Uint8ClampedArray", - "Int16Array", - "Int32Array", - "Uint16Array", - "Uint32Array", - "BigInt64Array", - "BigUint64Array", - // Keyed collections - "Set", - "Map", - "WeakSet", - "WeakMap", - // Structured data - "ArrayBuffer", - "SharedArrayBuffer", - "Atomics", - "DataView", - "JSON", - // Control abstraction objects - "Promise", - "Generator", - "GeneratorFunction", - "AsyncFunction", - // Reflection - "Reflect", - "Proxy", - // Internationalization - "Intl", - // WebAssembly - "WebAssembly" - ]; - - const ERROR_TYPES = [ - "Error", - "EvalError", - "InternalError", - "RangeError", - "ReferenceError", - "SyntaxError", - "TypeError", - "URIError" - ]; - - const BUILT_IN_GLOBALS = [ - "setInterval", - "setTimeout", - "clearInterval", - "clearTimeout", - - "require", - "exports", - - "eval", - "isFinite", - "isNaN", - "parseFloat", - "parseInt", - "decodeURI", - "decodeURIComponent", - "encodeURI", - "encodeURIComponent", - "escape", - "unescape" - ]; - - const BUILT_IN_VARIABLES = [ - "arguments", - "this", - "super", - "console", - "window", - "document", - "localStorage", - "sessionStorage", - "module", - "global" // Node.js - ]; - - const BUILT_INS = [].concat( - BUILT_IN_GLOBALS, - TYPES, - ERROR_TYPES - ); - - /* - Language: JavaScript - Description: JavaScript (JS) is a lightweight, interpreted, or just-in-time compiled programming language with first-class functions. - Category: common, scripting, web - Website: https://developer.mozilla.org/en-US/docs/Web/JavaScript - */ - - /** @type LanguageFn */ - function javascript(hljs) { - const regex = hljs.regex; - /** - * Takes a string like "<Booger" and checks to see - * if we can find a matching "</Booger" later in the - * content. - * @param {RegExpMatchArray} match - * @param {{after:number}} param1 - */ - const hasClosingTag = (match, { after }) => { - const tag = "</" + match[0].slice(1); - const pos = match.input.indexOf(tag, after); - return pos !== -1; - }; - - const IDENT_RE$1 = IDENT_RE; - const FRAGMENT = { - begin: '<>', - end: '</>' - }; - // to avoid some special cases inside isTrulyOpeningTag - const XML_SELF_CLOSING = /<[A-Za-z0-9\\._:-]+\s*\/>/; - const XML_TAG = { - begin: /<[A-Za-z0-9\\._:-]+/, - end: /\/[A-Za-z0-9\\._:-]+>|\/>/, - /** - * @param {RegExpMatchArray} match - * @param {CallbackResponse} response - */ - isTrulyOpeningTag: (match, response) => { - const afterMatchIndex = match[0].length + match.index; - const nextChar = match.input[afterMatchIndex]; - if ( - // HTML should not include another raw `<` inside a tag - // nested type? - // `<Array<Array<number>>`, etc. - nextChar === "<" || - // the , gives away that this is not HTML - // `<T, A extends keyof T, V>` - nextChar === "," - ) { - response.ignoreMatch(); - return; - } - - // `<something>` - // Quite possibly a tag, lets look for a matching closing tag... - if (nextChar === ">") { - // if we cannot find a matching closing tag, then we - // will ignore it - if (!hasClosingTag(match, { after: afterMatchIndex })) { - response.ignoreMatch(); - } - } - - // `<blah />` (self-closing) - // handled by simpleSelfClosing rule - - let m; - const afterMatch = match.input.substring(afterMatchIndex); - - // some more template typing stuff - // <T = any>(key?: string) => Modify< - if ((m = afterMatch.match(/^\s*=/))) { - response.ignoreMatch(); - return; - } - - // `<From extends string>` - // technically this could be HTML, but it smells like a type - // NOTE: This is ugh, but added specifically for https://github.com/highlightjs/highlight.js/issues/3276 - if ((m = afterMatch.match(/^\s+extends\s+/))) { - if (m.index === 0) { - response.ignoreMatch(); - // eslint-disable-next-line no-useless-return - return; - } - } - } - }; - const KEYWORDS$1 = { - $pattern: IDENT_RE, - keyword: KEYWORDS, - literal: LITERALS, - built_in: BUILT_INS, - "variable.language": BUILT_IN_VARIABLES - }; - - // https://tc39.es/ecma262/#sec-literals-numeric-literals - const decimalDigits = '[0-9](_?[0-9])*'; - const frac = `\\.(${decimalDigits})`; - // DecimalIntegerLiteral, including Annex B NonOctalDecimalIntegerLiteral - // https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals - const decimalInteger = `0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*`; - const NUMBER = { - className: 'number', - variants: [ - // DecimalLiteral - { begin: `(\\b(${decimalInteger})((${frac})|\\.)?|(${frac}))` + - `[eE][+-]?(${decimalDigits})\\b` }, - { begin: `\\b(${decimalInteger})\\b((${frac})\\b|\\.)?|(${frac})\\b` }, - - // DecimalBigIntegerLiteral - { begin: `\\b(0|[1-9](_?[0-9])*)n\\b` }, - - // NonDecimalIntegerLiteral - { begin: "\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b" }, - { begin: "\\b0[bB][0-1](_?[0-1])*n?\\b" }, - { begin: "\\b0[oO][0-7](_?[0-7])*n?\\b" }, - - // LegacyOctalIntegerLiteral (does not include underscore separators) - // https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals - { begin: "\\b0[0-7]+n?\\b" }, - ], - relevance: 0 - }; - - const SUBST = { - className: 'subst', - begin: '\\$\\{', - end: '\\}', - keywords: KEYWORDS$1, - contains: [] // defined later - }; - const HTML_TEMPLATE = { - begin: 'html`', - end: '', - starts: { - end: '`', - returnEnd: false, - contains: [ - hljs.BACKSLASH_ESCAPE, - SUBST - ], - subLanguage: 'xml' - } - }; - const CSS_TEMPLATE = { - begin: 'css`', - end: '', - starts: { - end: '`', - returnEnd: false, - contains: [ - hljs.BACKSLASH_ESCAPE, - SUBST - ], - subLanguage: 'css' - } - }; - const TEMPLATE_STRING = { - className: 'string', - begin: '`', - end: '`', - contains: [ - hljs.BACKSLASH_ESCAPE, - SUBST - ] - }; - const JSDOC_COMMENT = hljs.COMMENT( - /\/\*\*(?!\/)/, - '\\*/', - { - relevance: 0, - contains: [ - { - begin: '(?=@[A-Za-z]+)', - relevance: 0, - contains: [ - { - className: 'doctag', - begin: '@[A-Za-z]+' - }, - { - className: 'type', - begin: '\\{', - end: '\\}', - excludeEnd: true, - excludeBegin: true, - relevance: 0 - }, - { - className: 'variable', - begin: IDENT_RE$1 + '(?=\\s*(-)|$)', - endsParent: true, - relevance: 0 - }, - // eat spaces (not newlines) so we can find - // types or variables - { - begin: /(?=[^\n])\s/, - relevance: 0 - } - ] - } - ] - } - ); - const COMMENT = { - className: "comment", - variants: [ - JSDOC_COMMENT, - hljs.C_BLOCK_COMMENT_MODE, - hljs.C_LINE_COMMENT_MODE - ] - }; - const SUBST_INTERNALS = [ - hljs.APOS_STRING_MODE, - hljs.QUOTE_STRING_MODE, - HTML_TEMPLATE, - CSS_TEMPLATE, - TEMPLATE_STRING, - // Skip numbers when they are part of a variable name - { match: /\$\d+/ }, - NUMBER, - // This is intentional: - // See https://github.com/highlightjs/highlight.js/issues/3288 - // hljs.REGEXP_MODE - ]; - SUBST.contains = SUBST_INTERNALS - .concat({ - // we need to pair up {} inside our subst to prevent - // it from ending too early by matching another } - begin: /\{/, - end: /\}/, - keywords: KEYWORDS$1, - contains: [ - "self" - ].concat(SUBST_INTERNALS) - }); - const SUBST_AND_COMMENTS = [].concat(COMMENT, SUBST.contains); - const PARAMS_CONTAINS = SUBST_AND_COMMENTS.concat([ - // eat recursive parens in sub expressions - { - begin: /\(/, - end: /\)/, - keywords: KEYWORDS$1, - contains: ["self"].concat(SUBST_AND_COMMENTS) - } - ]); - const PARAMS = { - className: 'params', - begin: /\(/, - end: /\)/, - excludeBegin: true, - excludeEnd: true, - keywords: KEYWORDS$1, - contains: PARAMS_CONTAINS - }; - - // ES6 classes - const CLASS_OR_EXTENDS = { - variants: [ - // class Car extends vehicle - { - match: [ - /class/, - /\s+/, - IDENT_RE$1, - /\s+/, - /extends/, - /\s+/, - regex.concat(IDENT_RE$1, "(", regex.concat(/\./, IDENT_RE$1), ")*") - ], - scope: { - 1: "keyword", - 3: "title.class", - 5: "keyword", - 7: "title.class.inherited" - } - }, - // class Car - { - match: [ - /class/, - /\s+/, - IDENT_RE$1 - ], - scope: { - 1: "keyword", - 3: "title.class" - } - }, - - ] - }; - - const CLASS_REFERENCE = { - relevance: 0, - match: - regex.either( - // Hard coded exceptions - /\bJSON/, - // Float32Array, OutT - /\b[A-Z][a-z]+([A-Z][a-z]*|\d)*/, - // CSSFactory, CSSFactoryT - /\b[A-Z]{2,}([A-Z][a-z]+|\d)+([A-Z][a-z]*)*/, - // FPs, FPsT - /\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\d)*([A-Z][a-z]*)*/, - // P - // single letters are not highlighted - // BLAH - // this will be flagged as a UPPER_CASE_CONSTANT instead - ), - className: "title.class", - keywords: { - _: [ - // se we still get relevance credit for JS library classes - ...TYPES, - ...ERROR_TYPES - ] - } - }; - - const USE_STRICT = { - label: "use_strict", - className: 'meta', - relevance: 10, - begin: /^\s*['"]use (strict|asm)['"]/ - }; - - const FUNCTION_DEFINITION = { - variants: [ - { - match: [ - /function/, - /\s+/, - IDENT_RE$1, - /(?=\s*\()/ - ] - }, - // anonymous function - { - match: [ - /function/, - /\s*(?=\()/ - ] - } - ], - className: { - 1: "keyword", - 3: "title.function" - }, - label: "func.def", - contains: [ PARAMS ], - illegal: /%/ - }; - - const UPPER_CASE_CONSTANT = { - relevance: 0, - match: /\b[A-Z][A-Z_0-9]+\b/, - className: "variable.constant" - }; - - function noneOf(list) { - return regex.concat("(?!", list.join("|"), ")"); - } - - const FUNCTION_CALL = { - match: regex.concat( - /\b/, - noneOf([ - ...BUILT_IN_GLOBALS, - "super", - "import" - ]), - IDENT_RE$1, regex.lookahead(/\(/)), - className: "title.function", - relevance: 0 - }; - - const PROPERTY_ACCESS = { - begin: regex.concat(/\./, regex.lookahead( - regex.concat(IDENT_RE$1, /(?![0-9A-Za-z$_(])/) - )), - end: IDENT_RE$1, - excludeBegin: true, - keywords: "prototype", - className: "property", - relevance: 0 - }; - - const GETTER_OR_SETTER = { - match: [ - /get|set/, - /\s+/, - IDENT_RE$1, - /(?=\()/ - ], - className: { - 1: "keyword", - 3: "title.function" - }, - contains: [ - { // eat to avoid empty params - begin: /\(\)/ - }, - PARAMS - ] - }; - - const FUNC_LEAD_IN_RE = '(\\(' + - '[^()]*(\\(' + - '[^()]*(\\(' + - '[^()]*' + - '\\)[^()]*)*' + - '\\)[^()]*)*' + - '\\)|' + hljs.UNDERSCORE_IDENT_RE + ')\\s*=>'; - - const FUNCTION_VARIABLE = { - match: [ - /const|var|let/, /\s+/, - IDENT_RE$1, /\s*/, - /=\s*/, - /(async\s*)?/, // async is optional - regex.lookahead(FUNC_LEAD_IN_RE) - ], - keywords: "async", - className: { - 1: "keyword", - 3: "title.function" - }, - contains: [ - PARAMS - ] - }; - - return { - name: 'Javascript', - aliases: ['js', 'jsx', 'mjs', 'cjs'], - keywords: KEYWORDS$1, - // this will be extended by TypeScript - exports: { PARAMS_CONTAINS, CLASS_REFERENCE }, - illegal: /#(?![$_A-z])/, - contains: [ - hljs.SHEBANG({ - label: "shebang", - binary: "node", - relevance: 5 - }), - USE_STRICT, - hljs.APOS_STRING_MODE, - hljs.QUOTE_STRING_MODE, - HTML_TEMPLATE, - CSS_TEMPLATE, - TEMPLATE_STRING, - COMMENT, - // Skip numbers when they are part of a variable name - { match: /\$\d+/ }, - NUMBER, - CLASS_REFERENCE, - { - className: 'attr', - begin: IDENT_RE$1 + regex.lookahead(':'), - relevance: 0 - }, - FUNCTION_VARIABLE, - { // "value" container - begin: '(' + hljs.RE_STARTERS_RE + '|\\b(case|return|throw)\\b)\\s*', - keywords: 'return throw case', - relevance: 0, - contains: [ - COMMENT, - hljs.REGEXP_MODE, - { - className: 'function', - // we have to count the parens to make sure we actually have the - // correct bounding ( ) before the =>. There could be any number of - // sub-expressions inside also surrounded by parens. - begin: FUNC_LEAD_IN_RE, - returnBegin: true, - end: '\\s*=>', - contains: [ - { - className: 'params', - variants: [ - { - begin: hljs.UNDERSCORE_IDENT_RE, - relevance: 0 - }, - { - className: null, - begin: /\(\s*\)/, - skip: true - }, - { - begin: /\(/, - end: /\)/, - excludeBegin: true, - excludeEnd: true, - keywords: KEYWORDS$1, - contains: PARAMS_CONTAINS - } - ] - } - ] - }, - { // could be a comma delimited list of params to a function call - begin: /,/, - relevance: 0 - }, - { - match: /\s+/, - relevance: 0 - }, - { // JSX - variants: [ - { begin: FRAGMENT.begin, end: FRAGMENT.end }, - { match: XML_SELF_CLOSING }, - { - begin: XML_TAG.begin, - // we carefully check the opening tag to see if it truly - // is a tag and not a false positive - 'on:begin': XML_TAG.isTrulyOpeningTag, - end: XML_TAG.end - } - ], - subLanguage: 'xml', - contains: [ - { - begin: XML_TAG.begin, - end: XML_TAG.end, - skip: true, - contains: ['self'] - } - ] - } - ], - }, - FUNCTION_DEFINITION, - { - // prevent this from getting swallowed up by function - // since they appear "function like" - beginKeywords: "while if switch catch for" - }, - { - // we have to count the parens to make sure we actually have the correct - // bounding ( ). There could be any number of sub-expressions inside - // also surrounded by parens. - begin: '\\b(?!function)' + hljs.UNDERSCORE_IDENT_RE + - '\\(' + // first parens - '[^()]*(\\(' + - '[^()]*(\\(' + - '[^()]*' + - '\\)[^()]*)*' + - '\\)[^()]*)*' + - '\\)\\s*\\{', // end parens - returnBegin:true, - label: "func.def", - contains: [ - PARAMS, - hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1, className: "title.function" }) - ] - }, - // catch ... so it won't trigger the property rule below - { - match: /\.\.\./, - relevance: 0 - }, - PROPERTY_ACCESS, - // hack: prevents detection of keywords in some circumstances - // .keyword() - // $keyword = x - { - match: '\\$' + IDENT_RE$1, - relevance: 0 - }, - { - match: [ /\bconstructor(?=\s*\()/ ], - className: { 1: "title.function" }, - contains: [ PARAMS ] - }, - FUNCTION_CALL, - UPPER_CASE_CONSTANT, - CLASS_OR_EXTENDS, - GETTER_OR_SETTER, - { - match: /\$[(.]/ // relevance booster for a pattern common to JS libs: `$(something)` and `$.something` - } - ] - }; - } - - /* - Language: Bash - Author: vah <vahtenberg@gmail.com> - Contributrors: Benjamin Pannell <contact@sierrasoftworks.com> - Website: https://www.gnu.org/software/bash/ - Category: common - */ - - /** @type LanguageFn */ - function bash(hljs) { - const regex = hljs.regex; - const VAR = {}; - const BRACED_VAR = { - begin: /\$\{/, - end: /\}/, - contains: [ - "self", - { - begin: /:-/, - contains: [ VAR ] - } // default values - ] - }; - Object.assign(VAR, { - className: 'variable', - variants: [ - { begin: regex.concat(/\$[\w\d#@][\w\d_]*/, - // negative look-ahead tries to avoid matching patterns that are not - // Perl at all like $ident$, @ident@, etc. - `(?![\\w\\d])(?![$])`) }, - BRACED_VAR - ] - }); - - const SUBST = { - className: 'subst', - begin: /\$\(/, - end: /\)/, - contains: [ hljs.BACKSLASH_ESCAPE ] - }; - const HERE_DOC = { - begin: /<<-?\s*(?=\w+)/, - starts: { contains: [ - hljs.END_SAME_AS_BEGIN({ - begin: /(\w+)/, - end: /(\w+)/, - className: 'string' - }) - ] } - }; - const QUOTE_STRING = { - className: 'string', - begin: /"/, - end: /"/, - contains: [ - hljs.BACKSLASH_ESCAPE, - VAR, - SUBST - ] - }; - SUBST.contains.push(QUOTE_STRING); - const ESCAPED_QUOTE = { - className: '', - begin: /\\"/ - - }; - const APOS_STRING = { - className: 'string', - begin: /'/, - end: /'/ - }; - const ARITHMETIC = { - begin: /\$?\(\(/, - end: /\)\)/, - contains: [ - { - begin: /\d+#[0-9a-f]+/, - className: "number" - }, - hljs.NUMBER_MODE, - VAR - ] - }; - const SH_LIKE_SHELLS = [ - "fish", - "bash", - "zsh", - "sh", - "csh", - "ksh", - "tcsh", - "dash", - "scsh", - ]; - const KNOWN_SHEBANG = hljs.SHEBANG({ - binary: `(${SH_LIKE_SHELLS.join("|")})`, - relevance: 10 - }); - const FUNCTION = { - className: 'function', - begin: /\w[\w\d_]*\s*\(\s*\)\s*\{/, - returnBegin: true, - contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: /\w[\w\d_]*/ }) ], - relevance: 0 - }; - - const KEYWORDS = [ - "if", - "then", - "else", - "elif", - "fi", - "for", - "while", - "in", - "do", - "done", - "case", - "esac", - "function" - ]; - - const LITERALS = [ - "true", - "false" - ]; - - // to consume paths to prevent keyword matches inside them - const PATH_MODE = { match: /(\/[a-z._-]+)+/ }; - - // http://www.gnu.org/software/bash/manual/html_node/Shell-Builtin-Commands.html - const SHELL_BUILT_INS = [ - "break", - "cd", - "continue", - "eval", - "exec", - "exit", - "export", - "getopts", - "hash", - "pwd", - "readonly", - "return", - "shift", - "test", - "times", - "trap", - "umask", - "unset" - ]; - - const BASH_BUILT_INS = [ - "alias", - "bind", - "builtin", - "caller", - "command", - "declare", - "echo", - "enable", - "help", - "let", - "local", - "logout", - "mapfile", - "printf", - "read", - "readarray", - "source", - "type", - "typeset", - "ulimit", - "unalias" - ]; - - const ZSH_BUILT_INS = [ - "autoload", - "bg", - "bindkey", - "bye", - "cap", - "chdir", - "clone", - "comparguments", - "compcall", - "compctl", - "compdescribe", - "compfiles", - "compgroups", - "compquote", - "comptags", - "comptry", - "compvalues", - "dirs", - "disable", - "disown", - "echotc", - "echoti", - "emulate", - "fc", - "fg", - "float", - "functions", - "getcap", - "getln", - "history", - "integer", - "jobs", - "kill", - "limit", - "log", - "noglob", - "popd", - "print", - "pushd", - "pushln", - "rehash", - "sched", - "setcap", - "setopt", - "stat", - "suspend", - "ttyctl", - "unfunction", - "unhash", - "unlimit", - "unsetopt", - "vared", - "wait", - "whence", - "where", - "which", - "zcompile", - "zformat", - "zftp", - "zle", - "zmodload", - "zparseopts", - "zprof", - "zpty", - "zregexparse", - "zsocket", - "zstyle", - "ztcp" - ]; - - const GNU_CORE_UTILS = [ - "chcon", - "chgrp", - "chown", - "chmod", - "cp", - "dd", - "df", - "dir", - "dircolors", - "ln", - "ls", - "mkdir", - "mkfifo", - "mknod", - "mktemp", - "mv", - "realpath", - "rm", - "rmdir", - "shred", - "sync", - "touch", - "truncate", - "vdir", - "b2sum", - "base32", - "base64", - "cat", - "cksum", - "comm", - "csplit", - "cut", - "expand", - "fmt", - "fold", - "head", - "join", - "md5sum", - "nl", - "numfmt", - "od", - "paste", - "ptx", - "pr", - "sha1sum", - "sha224sum", - "sha256sum", - "sha384sum", - "sha512sum", - "shuf", - "sort", - "split", - "sum", - "tac", - "tail", - "tr", - "tsort", - "unexpand", - "uniq", - "wc", - "arch", - "basename", - "chroot", - "date", - "dirname", - "du", - "echo", - "env", - "expr", - "factor", - // "false", // keyword literal already - "groups", - "hostid", - "id", - "link", - "logname", - "nice", - "nohup", - "nproc", - "pathchk", - "pinky", - "printenv", - "printf", - "pwd", - "readlink", - "runcon", - "seq", - "sleep", - "stat", - "stdbuf", - "stty", - "tee", - "test", - "timeout", - // "true", // keyword literal already - "tty", - "uname", - "unlink", - "uptime", - "users", - "who", - "whoami", - "yes" - ]; - - return { - name: 'Bash', - aliases: [ 'sh' ], - keywords: { - $pattern: /\b[a-z][a-z0-9._-]+\b/, - keyword: KEYWORDS, - literal: LITERALS, - built_in: [ - ...SHELL_BUILT_INS, - ...BASH_BUILT_INS, - // Shell modifiers - "set", - "shopt", - ...ZSH_BUILT_INS, - ...GNU_CORE_UTILS - ] - }, - contains: [ - KNOWN_SHEBANG, // to catch known shells and boost relevancy - hljs.SHEBANG(), // to catch unknown shells but still highlight the shebang - FUNCTION, - ARITHMETIC, - hljs.HASH_COMMENT_MODE, - HERE_DOC, - PATH_MODE, - QUOTE_STRING, - ESCAPED_QUOTE, - APOS_STRING, - VAR - ] - }; - } - - var builtIns = /*#__PURE__*/Object.freeze({ - __proto__: null, - grmr_gleam: gleam, - grmr_erlang: erlang, - grmr_xml: xml, - grmr_css: css, - grmr_javascript: javascript, - grmr_bash: bash - }); - - const hljs = HighlightJS; - - for (const key of Object.keys(builtIns)) { - // our builtInLanguages Rollup plugin has to use `_` to allow identifiers to be - // compatible with `export` naming conventions, so we need to convert the - // identifiers back into the more typical dash style that we use for language - // naming via the API - const languageName = key.replace("grmr_", "").replace("_", "-"); - hljs.registerLanguage(languageName, builtIns[key]); - } - - return hljs; - -})(); -if (typeof exports === 'object' && typeof module !== 'undefined') { module.exports = hljs; } diff --git a/compat/lustre_animation/index.html b/compat/lustre_animation/index.html deleted file mode 100644 index 5d15471..0000000 --- a/compat/lustre_animation/index.html +++ /dev/null @@ -1,95 +0,0 @@ -<html> - -<head> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Lustre Animation Example</title> - <style> - head, - body { - margin: 0; - padding: 1em; - background: linear-gradient(to bottom, #ffaff3, white); - } - </style> - <link rel="stylesheet" href="lustre_animation.css" /> -</head> - -<body> - <script type="text/javascript" src="highlight.js"></script> - <h1>Lustre Animation</h1> - You can use this package within Gleam with - <pre><code class="language-bash">gleam add lustre_animation</code></pre> - You can get the repository with - <pre><code class="no-highlight">git clone https://git.chmeee.org/lustre_animation</code></pre> - <h1>Usage</h1> - The basic usage is - <ol> - <li>keep 1 `Animations` value</li> - <li>add animations by some trigger</li> - <li>call `animation.cmd()` on your value and let your lustre `update()` return it. - This will cause a dispatch on the next JS animation frame, unless all animations have finished (and - auto-removed).</li> - <li>update the animations with the new time.</li> - <li>evaluate for each animation you are interested in.</li> - </ol> - e.g. like this: - <pre><code class="language-gleam">import lustre/animation as a - -pub type Msg { - Trigger - Tick(Float) -} - -pub fn update(model: Model, msg: Msg) { - let m = case msg of { - Trigger -> { - let new_anim = a.add(model.animations, "id", 1.0, 2.0, 0.250) - Model(1.0, animations: new_anim) - } - Tick(t) -> { - let new_anim = a.tick(model.animations, t) - let new_value = a.value(new_anim, "id", model.value) - Model(new_value, new_anim) - } - } - #(m, animation.cmd(m.animations, Tick)) -}</code></pre> - - In the above <code>type Model</code>, <code>init</code> and <code>render</code> have been omitted. - - <h1>Examples</h1> - Below are two working examples, of which the code is in the git repository. - <div style="display: flex; flex-wrap: wrap; overflow-y: auto; overflow-x: hidden;"> - <div style="width: 320; margin-right: 2em;"> - <h2>1. Two independent animations</h2> - <p>Click on the four buttons to manipulate the text horizontally, - and vertically.</p> - <p>See <code>./test/example_move_around.gleam</code></p> - <div id="root" - style="width: calc(320px - 2em); height: 480; border: 2px solid grey; padding: 0 1em 1em 1em;"> - <h1>Lustre Animation Example</h1> - Please wait for the JavaScript (compiled Gleam) to take over. - <noscript> - <p>This site needs JavaScript to run, please enable/allow it.</p> - </noscript> - </div> - </div> - - <div style="width: 320;"> - <h2>2. Many animations that are all 'the same'</h2> - <p>Each drop behaves in the same way. Click in the area to generate them.</p> - <p>See <code>./test/example_drops.gleam</code></p> - <div id="drops" style="width: calc(320px - 2em); height: 480; border: 2px solid grey; padding: 1em;"> - <h1>Lustre Animation Example</h1> - Please wait for the JavaScript (compiled Gleam) to take over. - <noscript> - <p>This site needs JavaScript to run, please enable/allow it.</p> - </noscript> - </div> - </div> - </div> - <script type="text/javascript" src="run_highlight.js"></script> - <script src="./test/start_examples.mjs" type="module"></script> -</body> - -</html> diff --git a/compat/lustre_animation/lustre_animation.css b/compat/lustre_animation/lustre_animation.css deleted file mode 100644 index 3e6bc47..0000000 --- a/compat/lustre_animation/lustre_animation.css +++ /dev/null @@ -1,90 +0,0 @@ -code { - color: #333; - border: 1px solid #847; - border-radius: 0.5ex; - padding: 0 0.5ex; -} - -pre code { - border: 0; - padding: 0; -} - -pre { - background: linear-gradient(to bottom, white, #ffaff3); - overflow-x: auto; - border: 1px solid #847; - border-radius: 1ex; - padding: 0.5ex 1.5ex; - margin: 1ex 0; -} - -.hljs-comment, -.hljs-quote { - color: #a0a1a7; - font-style: italic -} - -.hljs-doctag, -.hljs-formula, -.hljs-keyword { - color: #a626a4 -} - -.hljs-deletion, -.hljs-name, -.hljs-section, -.hljs-selector-tag, -.hljs-subst { - color: #e45649 -} - -.hljs-literal { - color: #0184bb -} - -.hljs-addition, -.hljs-attribute, -.hljs-meta .hljs-string, -.hljs-regexp, -.hljs-string { - color: #50a14f -} - -.hljs-attr, -.hljs-number, -.hljs-selector-attr, -.hljs-selector-class, -.hljs-selector-pseudo, -.hljs-template-variable, -.hljs-type, -.hljs-variable { - color: #986801 -} - -.hljs-bullet, -.hljs-link, -.hljs-meta, -.hljs-selector-id, -.hljs-symbol, -.hljs-title { - color: #4078f2 -} - -.hljs-built_in, -.hljs-class .hljs-title, -.hljs-title.class_ { - color: #c18401 -} - -.hljs-emphasis { - font-style: italic -} - -.hljs-strong { - font-weight: 700 -} - -.hljs-link { - text-decoration: underline -} diff --git a/compat/lustre_animation/manifest.toml b/compat/lustre_animation/manifest.toml deleted file mode 100644 index d420871..0000000 --- a/compat/lustre_animation/manifest.toml +++ /dev/null @@ -1,13 +0,0 @@ -# This file was generated by Gleam -# You typically do not need to edit this file - -packages = [ - { name = "gleam_stdlib", version = "0.30.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "704258528887F95075FFED7AAE1CCF836A9B88E3AADA2F69F9DA15815F94A4F9" }, - { name = "gleeunit", version = "0.10.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "ECEA2DE4BE6528D36AFE74F42A21CDF99966EC36D7F25DEB34D47DD0F7977BAF" }, - { name = "lustre", version = "3.0.0-rc.8", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "local", path = "/Users/hayleigh/dev/gleam/lustre/lib" }, -] - -[requirements] -gleam_stdlib = { version = "~> 0.30" } -gleeunit = { version = "~> 0.10" } -lustre = { path = "../../lib" } diff --git a/compat/lustre_animation/package-lock.json b/compat/lustre_animation/package-lock.json deleted file mode 100644 index 5c43ad9..0000000 --- a/compat/lustre_animation/package-lock.json +++ /dev/null @@ -1,790 +0,0 @@ -{ - "name": "lustre_animation", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "devDependencies": { - "vite": "^4.4.9" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/postcss": { - "version": "8.4.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz", - "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/rollup": { - "version": "3.28.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz", - "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==", - "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/vite": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", - "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", - "dev": true, - "dependencies": { - "esbuild": "^0.18.10", - "postcss": "^8.4.27", - "rollup": "^3.27.1" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "@types/node": ">= 14", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - } - }, - "dependencies": { - "@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "dev": true, - "optional": true - }, - "esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, - "requires": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" - } - }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true - }, - "nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "postcss": { - "version": "8.4.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz", - "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==", - "dev": true, - "requires": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, - "rollup": { - "version": "3.28.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz", - "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==", - "dev": true, - "requires": { - "fsevents": "~2.3.2" - } - }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true - }, - "vite": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", - "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", - "dev": true, - "requires": { - "esbuild": "^0.18.10", - "fsevents": "~2.3.2", - "postcss": "^8.4.27", - "rollup": "^3.27.1" - } - } - } -} diff --git a/compat/lustre_animation/package.json b/compat/lustre_animation/package.json deleted file mode 100644 index a0831d3..0000000 --- a/compat/lustre_animation/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "scripts": { - "dev": "gleam build && vite", - "build": "gleam build && vite build" - }, - "devDependencies": { - "vite": "^4.4.9" - } -} diff --git a/compat/lustre_animation/run_highlight.js b/compat/lustre_animation/run_highlight.js deleted file mode 100644 index d73c817..0000000 --- a/compat/lustre_animation/run_highlight.js +++ /dev/null @@ -1,3 +0,0 @@ -document.querySelectorAll('code').forEach((el) => { - hljs.highlightElement(el); -}); diff --git a/compat/lustre_animation/src/ffi.mjs b/compat/lustre_animation/src/ffi.mjs deleted file mode 100644 index fd5d9d3..0000000 --- a/compat/lustre_animation/src/ffi.mjs +++ /dev/null @@ -1,2 +0,0 @@ -export const request_animation_frame = f => requestAnimationFrame(f) -export const cancel_animation_frame = id => cancelAnimationFrame(id)
\ No newline at end of file diff --git a/compat/lustre_animation/src/lustre/animation.gleam b/compat/lustre_animation/src/lustre/animation.gleam deleted file mode 100644 index 8f3312e..0000000 --- a/compat/lustre_animation/src/lustre/animation.gleam +++ /dev/null @@ -1,170 +0,0 @@ -import lustre/effect.{Effect} -import gleam/list.{filter, find, map} - -/// A singleton holding all your animations, and a timestamp -/// -/// Hayleigh note: since the stdlib got some improvmements a while ago, I think -/// you could use a `Map(String, Animation)` now instead of `List.find`ing all -/// over the place. -pub opaque type Animations { - Animations(t: Float, List(Animation)) -} - -type Animation { - Animation(name: String, range: #(Float, Float), state: AnimationState) -} - -// InParallel(Animations) -// InSequence(Animations) -// Repeat(Animation) -// Cancelled(Animation) - -// The State Done will guarantee the `stop` value is returned, before the Animation is removed from the list -type AnimationState { - NotStarted(seconds: Float) - Running(seconds: Float, since: Float) - Done -} - -pub fn new() { - Animations(0.0, []) -} - -/// Add an animation (linear interpolation) from `start` to `stop`, for `seconds` duration. -/// The `name` should be unique, so the animation can be retrieved and evaluated by `value()` later. -/// The animation is started when a subsequent command from `effect()` is returned by your -/// lustre `update()` function; followed by a call to `tick()`. -pub fn add( - animations: Animations, - name, - start: Float, - stop: Float, - seconds: Float, -) -> Animations { - use list <- change_list(animations) - [Animation(name, #(start, stop), NotStarted(seconds)), ..list] -} - -/// Remove an animation if it should stop before it is finished. -/// If an animation runs to its end normally, you do not have to `remove()` it manually, -/// will be automatically removed by `tick()`. -pub fn remove(animations: Animations, name) -> Animations { - use list <- change_list(animations) - filter(list, does_not_have_name(_, name)) - // Would not filter, but replace w/ Cancelled and filtered in the next call, see comments at bottom of this file -} - -fn change_list( - animations: Animations, - f: fn(List(Animation)) -> List(Animation), -) -> Animations { - let assert Animations(t, list) = animations - Animations(t, f(list)) -} - -fn does_not_have_name(animation: Animation, name: String) { - let assert Animation(n, _, _) = animation - n != name -} - -/// When called for the first time on an animation, its start time is -/// set to time-Offset. Its stop time then is the start time plus the -/// duration. -/// -/// When called for the *first* time for animations that have finished, -/// they will be marked as such in the result. `value()` will return the `stop` value -/// that you passed with `add()` -/// -/// When called the *second* time for animations that have finished, they will be absent -/// from the returned value. -pub fn tick(animations: Animations, time_offset) -> Animations { - let assert Animations(_, list) = animations - let new_list = - list - |> filter(not_done) - |> map(tick_animation(_, time_offset)) - Animations(time_offset, new_list) -} - -fn not_done(animation: Animation) -> Bool { - case animation { - Animation(_, _, Done) -> False - _ -> True - } -} - -fn tick_animation(animation: Animation, time: Float) -> Animation { - let assert Animation(name, range, state) = animation - let new_state = case state { - NotStarted(seconds) -> Running(seconds, since: time) - Running(seconds, since) -> - case time -. since >=. seconds *. 1000.0 { - True -> Done - False -> state - } - Done -> Done - } - Animation(name, range, new_state) -} - -/// Returns `effect.none()` if none of the animations is running. -/// Otherwise returns an opaque `Effect` that will cause `msg(time)` to -/// be dispatched on a JavaScript `requestAnimationFrame` -/// -/// Hayleigh note: Maybe this could have a better name like `next_tick` or -/// `request_tick`? -pub fn effect(animations: Animations, msg: fn(Float) -> m) -> Effect(m) { - case animations { - Animations(_, []) -> effect.none() - _ -> request_animation_frame(msg) - } -} - -/// If the animation specified by `which` is not found, returns `default`. -/// Otherwise, the interpolated value for the `time` passed to `tick()` is returned. -pub fn value(animations: Animations, which: String, default: Float) -> Float { - let assert Animations(t, list) = animations - case find(list, has_name(_, which)) { - Ok(animation) -> evaluate(animation, t) - Error(Nil) -> default - } -} - -fn has_name(animation: Animation, name: String) { - let assert Animation(n, _, _) = animation - n == name -} - -fn evaluate(animation: Animation, time: Float) -> Float { - let assert Animation(_, #(start, stop), state) = animation - case state { - NotStarted(_) -> start - Running(seconds, since) -> { - let dt = time -. since - let delta = dt /. { seconds *. 1000.0 } - start +. { stop -. start } *. delta - } - Done -> stop - } -} - -pub fn request_animation_frame(msg: fn(Float) -> m) -> Effect(m) { - effect.from(fn(dispatch) { - js_request_animation_frame(fn(time_offset: Float) { - dispatch(msg(time_offset)) - }) - Nil - }) -} - -pub type RequestedFrame - -// The returned 'long' can be passed to 'cancelAnimationFrame' - except we do not have any means to -// TODO Push the 'long' down into JS land, with the Animation, so we can -// make a mapping from Animation to RequestFrame and a `cancelFrame(Animation, msg) -> Effect(m)` -// that (again in JS land) *can* cancel the appropriate request frame. -external fn js_request_animation_frame(f) -> RequestedFrame = - "../ffi.mjs" "request_animation_frame" - -pub external fn cancel_animation_frame(frame: RequestedFrame) -> Nil = - "../ffi.mjs" "cancel_animation_frame" diff --git a/compat/lustre_animation/test/example_drops.gleam b/compat/lustre_animation/test/example_drops.gleam deleted file mode 100644 index dd69f5c..0000000 --- a/compat/lustre_animation/test/example_drops.gleam +++ /dev/null @@ -1,120 +0,0 @@ -import lustre -import lustre/animation.{Animations} -import lustre/attribute.{id, style} -import lustre/effect.{Effect} -import lustre/element.{Element, text} -import lustre/element/html.{div, h3} -import lustre/event.{on} -import gleam/float -import gleam/int -import gleam/list.{filter, map} -import gleam/dynamic.{Dynamic} as d -import gleam/option.{Some} - -pub type Msg { - Click(x: Float, y: Float) - Tick(time_offset: Float) -} - -pub type Drop { - Drop(id: String, x: Float, y: Float, r: Float) -} - -pub type Model { - Model(counter: Int, drops: List(Drop), animations: Animations) -} - -pub fn main() { - lustre.application(init, update, render) - |> lustre.start("#drops", Nil) -} - -fn init(_) { - #(Model(0, [], animation.new()), effect.none()) -} - -const to_s = int.to_string - -pub fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { - let m = case msg { - Click(x, y) -> { - let id = "drop" <> to_s(model.counter) - let new_animations = animation.add(model.animations, id, 0.0, 1.0, 1.5) - let new_drop = Drop(id, x, y, 0.0) - Model(model.counter + 1, [new_drop, ..model.drops], new_animations) - } - Tick(time_offset) -> { - let new_animations = animation.tick(model.animations, time_offset) - let new_drops = - model.drops - |> filter(fn(drop) { drop.r != 1.0 }) - |> map(fn(drop) { - let r = animation.value(new_animations, drop.id, drop.r) - Drop(..drop, r: r) - }) - Model(..model, drops: new_drops, animations: new_animations) - } - } - #(m, animation.effect(m.animations, Tick)) -} - -pub fn render(model: Model) -> Element(Msg) { - div( - [ - style([ - #("width", "100%"), - #("height", "100%"), - #("display", "grid"), - #("grid-template-rows", "auto 1fr"), - ]), - ], - [ - h3([style([#("text-align", "center")])], [text("Click to make drops")]), - div( - [ - id("pond"), - style([#("position", "relative")]), - on( - "mouseDown", - fn(event) { - let assert Ok(x) = d.field("clientX", d.float)(event) - let assert Ok(y) = d.field("clientY", d.float)(event) - let rect = bounding_client_rect("pond") - let assert Ok(top) = d.field("top", d.float)(rect) - let assert Ok(left) = d.field("left", d.float)(rect) - - Some(Click(x -. left, y -. top)) - }, - ), - ], - map(model.drops, render_drop), - ), - ], - ) -} - -fn render_drop(drop: Drop) { - let r = drop.r *. 50.0 - let rad = float.to_string(r *. 2.0) - let rw = float.to_string(drop.r *. 2.5) - let alpha = - 1.0 -. drop.r - |> float.to_string - div( - [ - style([ - #("position", "absolute"), - #("left", float.to_string(drop.x -. r) <> "px"), - #("top", float.to_string(drop.y -. r) <> "px"), - #("width", rad <> "px"), - #("height", rad <> "px"), - #("border", rw <> "px solid rgba(0, 0, 0, " <> alpha <> ")"), - #("border-radius", "50%"), - ]), - ], - [], - ) -} - -external fn bounding_client_rect(String) -> Dynamic = - "./info.mjs" "bounding_client_rect" diff --git a/compat/lustre_animation/test/example_two_independent.gleam b/compat/lustre_animation/test/example_two_independent.gleam deleted file mode 100644 index 61e687d..0000000 --- a/compat/lustre_animation/test/example_two_independent.gleam +++ /dev/null @@ -1,103 +0,0 @@ -import lustre -import lustre/animation.{Animations} -import lustre/attribute.{style} -import lustre/effect.{Effect} -import lustre/element.{text} -import lustre/element/html.{button, div, h3, span} -import lustre/event.{on_click} -import gleam/float - -pub type Msg { - Left - Right - Top - Bottom - Tick(time_offset: Float) -} - -pub type Model { - Model(x: Float, y: Float, animations: Animations) -} - -pub fn main() { - lustre.application(init, update, render) - |> lustre.start("#root", Nil) -} - -fn init(_) { - #(Model(0.5, 0.5, animation.new()), effect.none()) -} - -pub fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { - let m = case msg { - Left -> { - let new_animations = - animation.add(model.animations, "x", model.x, 0.0, 2.5) - Model(..model, animations: new_animations) - } - Right -> { - let new_animations = - animation.add(model.animations, "x", model.x, 1.0, 2.5) - Model(..model, animations: new_animations) - } - Top -> { - let new_animations = - animation.add(model.animations, "y", model.y, 0.0, 2.5) - Model(..model, animations: new_animations) - } - Bottom -> { - let new_animations = - animation.add(model.animations, "y", model.y, 1.0, 2.5) - Model(..model, animations: new_animations) - } - Tick(time_offset) -> { - let new_animations = animation.tick(model.animations, time_offset) - let x = animation.value(new_animations, "x", model.x) - let y = animation.value(new_animations, "y", model.y) - Model(x: x, y: y, animations: new_animations) - } - } - #(m, animation.effect(m.animations, Tick)) -} - -pub fn render(model: Model) { - let sp = span([], []) - let to_s = float.to_string - div( - [ - style([ - #("display", "grid"), - #("grid-template-rows", "1fr auto"), - #("width", "100%"), - #("height", "100%"), - ]), - ], - [ - div( - [ - style([ - #("display", "grid"), - #( - "grid-template-rows", - to_s(model.y) <> "fr auto " <> to_s(1.0 -. model.y) <> "fr", - ), - #( - "grid-template-columns", - to_s(model.x) <> "fr auto " <> to_s(1.0 -. model.x) <> "fr", - ), - ]), - ], - [sp, sp, sp, sp, h3([], [text("Move me around")]), sp, sp, sp, sp], - ), - div( - [], - [ - button([on_click(Left)], [text("Move to the Left")]), - button([on_click(Right)], [text("Move to the Right")]), - button([on_click(Top)], [text("Move to the Top")]), - button([on_click(Bottom)], [text("Move to the Bottom")]), - ], - ), - ], - ) -} diff --git a/compat/lustre_animation/test/info.mjs b/compat/lustre_animation/test/info.mjs deleted file mode 100644 index d58e440..0000000 --- a/compat/lustre_animation/test/info.mjs +++ /dev/null @@ -1,2 +0,0 @@ -export const bounding_client_rect = id => document.getElementById(id).getBoundingClientRect() - diff --git a/compat/lustre_animation/test/lustre/animation_test.gleam b/compat/lustre_animation/test/lustre/animation_test.gleam deleted file mode 100644 index 98ded63..0000000 --- a/compat/lustre_animation/test/lustre/animation_test.gleam +++ /dev/null @@ -1,39 +0,0 @@ -import gleeunit/should -import lustre/animation.{add, new, tick, value} - -pub fn evaluate_test() { - new() - |> add("a", 7.0, 42.0, 2.0) - |> value("a", 0.0) - |> should.equal(7.0) -} - -pub fn evaluate_start_test() { - new() - |> add("a", 7.0, 42.0, 2.0) - |> tick(1000.0) - |> value("a", 0.0) - |> should.equal(7.0) -} - -pub fn evaluate_done_test() { - new() - |> add("a", 7.0, 42.0, 2.0) - |> tick(1000.0) - |> tick(3000.0) - |> value("a", 0.0) - |> should.equal(42.0) -} - -pub fn evaluate_gone_test() { - new() - |> add("a", 7.0, 42.0, 2.0) - // start - |> tick(1000.0) - // Done - |> tick(3000.0001) - // remove - |> tick(3000.1) - |> value("a", 0.0) - |> should.equal(0.0) -} diff --git a/compat/lustre_animation/test/lustre_animation_test.gleam b/compat/lustre_animation/test/lustre_animation_test.gleam deleted file mode 100644 index ecd12ad..0000000 --- a/compat/lustre_animation/test/lustre_animation_test.gleam +++ /dev/null @@ -1,5 +0,0 @@ -import gleeunit - -pub fn main() { - gleeunit.main() -} diff --git a/compat/lustre_animation/test/start_examples.mjs b/compat/lustre_animation/test/start_examples.mjs deleted file mode 100644 index 6d792c7..0000000 --- a/compat/lustre_animation/test/start_examples.mjs +++ /dev/null @@ -1,5 +0,0 @@ -import * as two from "../build/dev/javascript/lustre_animation/example_two_independent.mjs"; -import * as drops from "../build/dev/javascript/lustre_animation/example_drops.mjs"; - -two.main(); -drops.main(); diff --git a/compat/lustre_http/.gitignore b/compat/lustre_http/.gitignore deleted file mode 100644 index e03d74b..0000000 --- a/compat/lustre_http/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -build -*tar diff --git a/compat/lustre_http/README.md b/compat/lustre_http/README.md deleted file mode 100644 index 1117b93..0000000 --- a/compat/lustre_http/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# lustre_http - -[](https://hex.pm/packages/lustre_http) -[](https://hexdocs.pm/lustre_http/) - -Make HTTP requests from `lustre` via its `cmd` interface. -Makes life easy when such requests are initiated by the user. - -## Quick start - -Add to your Gleam project: - -```sh -gleam add lustre_http -``` - -View the documentation at <https://hexdocs.pm/lustre_http>, although only `get_as_text` is documented a.t.m. diff --git a/compat/lustre_http/gleam.toml b/compat/lustre_http/gleam.toml deleted file mode 100644 index 19bf4bc..0000000 --- a/compat/lustre_http/gleam.toml +++ /dev/null @@ -1,16 +0,0 @@ -name = "lustre_http" -version = "0.3.0" -target = "javascript" - -licences = ["MIT"] -description = "HTTP requests from lustre" -repository = { type = "custom", url = "https://git.chmeee.org/lustre_http" } -links = [{ title = "Package", href = "https://hex.pm/packages/lustre_http" }] - -[dependencies] -gleam_stdlib = "~> 0.30" -gleam_json = "~> 0.6" -lustre = { path = "../../lib" } - -[dev-dependencies] -gleeunit = "~> 0.10"
\ No newline at end of file diff --git a/compat/lustre_http/manifest.toml b/compat/lustre_http/manifest.toml deleted file mode 100644 index 7b77a95..0000000 --- a/compat/lustre_http/manifest.toml +++ /dev/null @@ -1,16 +0,0 @@ -# This file was generated by Gleam -# You typically do not need to edit this file - -packages = [ - { name = "gleam_json", version = "0.6.0", build_tools = ["gleam"], requirements = ["thoas", "gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "C6CC5BEECA525117E97D0905013AB3F8836537455645DDDD10FE31A511B195EF" }, - { name = "gleam_stdlib", version = "0.30.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "704258528887F95075FFED7AAE1CCF836A9B88E3AADA2F69F9DA15815F94A4F9" }, - { name = "gleeunit", version = "0.11.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "1397E5C4AC4108769EE979939AC39BF7870659C5AFB714630DEEEE16B8272AD5" }, - { name = "lustre", version = "3.0.0-rc.8", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "local", path = "/Users/hayleigh/dev/gleam/lustre/lib" }, - { name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" }, -] - -[requirements] -gleam_json = { version = "~> 0.6" } -gleam_stdlib = { version = "~> 0.30" } -gleeunit = { version = "~> 0.10" } -lustre = { path = "../../lib" } diff --git a/compat/lustre_http/src/ffi.mjs b/compat/lustre_http/src/ffi.mjs deleted file mode 100644 index bb01b09..0000000 --- a/compat/lustre_http/src/ffi.mjs +++ /dev/null @@ -1,16 +0,0 @@ -export const do_request = (method, url, body, on_success, on_error) => { - let xhr = new XMLHttpRequest() - xhr.onerror = _evt => { - console.log("error for", method, url, ":", xhr) - on_error(888, "oops!") - } - xhr.onloadend = _evt => { - switch (xhr.status) { - case 200: - case 204: on_success(xhr.responseText); break; - default: on_error(xhr.status, xhr.responseText) - } - } - xhr.open(method, url) - xhr.send(body); -} diff --git a/compat/lustre_http/src/lustre_http.gleam b/compat/lustre_http/src/lustre_http.gleam deleted file mode 100644 index a066faa..0000000 --- a/compat/lustre_http/src/lustre_http.gleam +++ /dev/null @@ -1,126 +0,0 @@ -import gleam/json -import lustre/effect.{Effect} - -pub type HttpError { - Unauthorized - NotFound - InternalServerError(String) - OtherError(Int, String) -} - -pub type StringResult = - Result(String, HttpError) - -pub type HttpOrJsonError { - H(HttpError) - J(json.DecodeError) -} - -pub type JsonResult(t) = - Result(t, HttpOrJsonError) - -external fn do_request( - method: String, - url: String, - body: String, - on_success: fn(String) -> Nil, - on_error: fn(Int, String) -> Nil, -) -> Nil = - "./ffi.mjs" "do_request" - -fn do_get(url, on_success, on_error) { - do_request("GET", url, "", on_success, on_error) -} - -fn do_post(url, body, on_success, on_error) { - do_request("POST", url, body, on_success, on_error) -} - -/// ### Usage -/// ``` -/// import lustre_http as http -/// -/// type Msg { -/// SomeInteraction -/// TextReceived(StringResult) -/// } -/// -/// pub fn update(model, msg) { -/// case msg { -/// SomeInteraction -> #(model, http.get_as_text("the_text", TextReceived)) -/// TextReceived(Ok(text)) -> #(apply(text, model), effect.none()) -/// TextReceived(Error(e)) -> #(indicate_problem(e, model), effect.none()) -/// } -/// } -/// ``` -pub fn get_as_text(url: String, to_msg: fn(StringResult) -> msg) -> Effect(msg) { - use dispatch <- effect.from - - do_get( - url, - fn(body) { dispatch(to_msg(Ok(body))) }, - fn(code, msg) { - decode_error(code, msg) - |> Error - |> to_msg - |> dispatch - }, - ) -} - -fn decode_error(code, msg) { - case code { - 401 -> Unauthorized - 404 -> NotFound - 500 -> InternalServerError(msg) - _ -> OtherError(code, msg) - } -} - -/// Will automatically try to decode the JSON for you. -/// The error-type is a bit complicated. In a future version will probably -/// (a) force "text/json" in the headers (b) not decode for you. -pub fn get_as_json( - url: String, - to_msg: fn(JsonResult(String)) -> msg, - decoder, -) -> Effect(msg) { - use dispatch <- effect.from - - do_get( - url, - fn(body) { - case json.decode(from: body, using: decoder) { - Ok(json) -> dispatch(to_msg(Ok(json))) - Error(json_error) -> dispatch(to_msg(Error(J(json_error)))) - } - }, - fn(code, msg) { - let http_error = case code { - 401 -> Unauthorized - 404 -> NotFound - 500 -> InternalServerError(msg) - _ -> OtherError(code, msg) - } - dispatch(to_msg(Error(H(http_error)))) - }, - ) -} - -/// Future versions will force headers in both the request and the response -/// so you can post json and receive text, post text and receive json, etc. -pub fn post_text(url: String, body: String, to_msg: fn(StringResult) -> msg) { - use dispatch <- effect.from - - do_post( - url, - body, - fn(body) { dispatch(to_msg(Ok(body))) }, - fn(code, msg) { - decode_error(code, msg) - |> Error - |> to_msg - |> dispatch - }, - ) -} diff --git a/compat/lustre_http/test/lustre_http_test.gleam b/compat/lustre_http/test/lustre_http_test.gleam deleted file mode 100644 index e426a70..0000000 --- a/compat/lustre_http/test/lustre_http_test.gleam +++ /dev/null @@ -1,15 +0,0 @@ -import lustre_http as http -import gleeunit - -pub fn main() { - gleeunit.main() -} - -pub type ToMsgProvider { - Wrapper(http.StringResult) -} - -// We cannot run the resulting lustre.Cmd, but we can at least ensure it can be used/compiled this way -pub fn compilation_test() { - http.get_as_text("http://localhost:8080/not_here", Wrapper) -} diff --git a/compat/lustre_websocket/.gitignore b/compat/lustre_websocket/.gitignore deleted file mode 100644 index 378eac2..0000000 --- a/compat/lustre_websocket/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build diff --git a/compat/lustre_websocket/README.md b/compat/lustre_websocket/README.md deleted file mode 100644 index 5e30c8e..0000000 --- a/compat/lustre_websocket/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# lustre_websocket - -[](https://hex.pm/packages/lustre_websocket) -[](https://hexdocs.pm/lustre_websocket/) - -Use websockets from your `lustre` application! - -## Quick start - -Add to your Gleam project: - -```sh -gleam add lustre_websocket -``` - -Typical usage looks like -``` -import lustre_websocket as ws - -pub type Msg { - WsWrapper(ws.WebSocketEvent) -} - -let init = #(Model(None), ws.init("/path", WsWrapper)) -``` -and then you pass `init` as first argument to `lustre.application`. -But you can create a socket at any time, esp. re-create it after it is closed by the server. - -The events can be handled like this: -``` -update(model, msg) { - case msg { - WsWrapper(OnOpen(socket)) -> #(Model(..model, ws: Some(socket)), ws.send(socket, "client-init")) - WsWrapper(OnMessage(msg)) -> todo - WsWrapper(OnClose(reason)) -> #(Model(..model, ws: None), cmd.none()) - } -} -``` -which also demonstrates how you send a text message over the socket. - -### Caveat - -*This package cannot handle more than 1 socket on a server endpoint* - -### TODO: - * support protocol choice, including one websocket per protocol per endpoint - * support binary data - * allow client to close the socket - * provide errors to the application, when I have a clue on what those might actually be - * prevent sending over closed sockets - * maybe auto-reopen sockets that were closed because of Normal diff --git a/compat/lustre_websocket/gleam.toml b/compat/lustre_websocket/gleam.toml deleted file mode 100644 index ba11fd3..0000000 --- a/compat/lustre_websocket/gleam.toml +++ /dev/null @@ -1,18 +0,0 @@ -name = "lustre_websocket" -version = "0.6.0" -target = "javascript" - -licences = ["MIT"] -description = "Web Socket requests from lustre" -repository = { type = "custom", url = "https://git.chmeee.org/lustre_websocket" } -links = [ - { title = "Package", href = "https://hex.pm/packages/lustre_websocket" }, -] - -[dependencies] -gleam_stdlib = "~> 0.30" -lustre = { path = "../../lib" } - - -[dev-dependencies] -gleeunit = "~> 0.10"
\ No newline at end of file diff --git a/compat/lustre_websocket/manifest.toml b/compat/lustre_websocket/manifest.toml deleted file mode 100644 index 7cf6fc0..0000000 --- a/compat/lustre_websocket/manifest.toml +++ /dev/null @@ -1,13 +0,0 @@ -# This file was generated by Gleam -# You typically do not need to edit this file - -packages = [ - { name = "gleam_stdlib", version = "0.30.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "704258528887F95075FFED7AAE1CCF836A9B88E3AADA2F69F9DA15815F94A4F9" }, - { name = "gleeunit", version = "0.11.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "1397E5C4AC4108769EE979939AC39BF7870659C5AFB714630DEEEE16B8272AD5" }, - { name = "lustre", version = "3.0.0-rc.8", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "local", path = "/Users/hayleigh/dev/gleam/lustre/lib" }, -] - -[requirements] -gleam_stdlib = { version = "~> 0.30" } -gleeunit = { version = "~> 0.10" } -lustre = { path = "../../lib" } diff --git a/compat/lustre_websocket/src/ffi.mjs b/compat/lustre_websocket/src/ffi.mjs deleted file mode 100644 index 56f11fd..0000000 --- a/compat/lustre_websocket/src/ffi.mjs +++ /dev/null @@ -1,39 +0,0 @@ -let ws_handler_registry = {} - -export const init_websocket = path => { - let ws - if (typeof WebSocket === "function") { - // we're in the browser - let url = new URL(document.URL) - let protocol = url.protocol === "http:" ? "ws" : "wss" - let ws_url = protocol + "://" + url.host + url.pathname + path - ws = new WebSocket(ws_url) - } else { - // we're NOT in the browser, prolly running tests - ws = {} - } - ws_handler_registry[ws.url] = { ws: ws } - - ws.onopen = evt => { - ws_handler_registry[ws.url]?.on_open?.() - } - ws.onclose = evt => { - ws_handler_registry[ws.url]?.on_close?.(evt.code) - delete ws_handler_registry[ws.url] - } - ws.onmessage = event => ws_handler_registry[ws.url]?.on_message?.(event.data) - ws.onerror = error => console.log("ws", ws.url, "error", error, "no handler, since I have no clue what errors we might be talking about") - return ws -} - -export const register_websocket_handler = (ws, on_open, on_message, on_close) => { - const reg_entry = ws_handler_registry[ws.url] - reg_entry.on_open = on_open - reg_entry.on_message = on_message - reg_entry.on_close = on_close - console.log("ws reg", ws_handler_registry) -} - -export const send_over_websocket = (ws, msg) => { - ws.send(msg) -} diff --git a/compat/lustre_websocket/src/lustre_websocket.gleam b/compat/lustre_websocket/src/lustre_websocket.gleam deleted file mode 100644 index ce46a4a..0000000 --- a/compat/lustre_websocket/src/lustre_websocket.gleam +++ /dev/null @@ -1,85 +0,0 @@ -import lustre/effect.{Effect} - -pub type WebSocket - -pub type WebSocketCloseReason { - // 1000 - Normal - // 1001 - GoingAway - // 1002 - ProtocolError - // 1003 - UnexpectedTypeOfData - // 1004 Reserved - // 1005 - NoCodeFromServer - // 1006, no close frame - AbnormalClose - // 1007 - IncomprehensibleFrame - // 1008 - PolicyViolated - // 1009 - MessageTooBig - // 1010 - FailedExtensionNegotation - // 1011 - UnexpectedFailure - // 1015 - FailedTLSHandshake -} - -pub type WebSocketEvent { - OnOpen(WebSocket) - OnMessage(String) - OnClose(WebSocketCloseReason) -} - -/// Initialize a websocket. These constructs are fully asynchronous, so you must provide a wrapper -/// that takes a `WebSocketEvent` and turns it into a lustre message of your application. -pub fn init(path: String, wrapper: fn(WebSocketEvent) -> a) -> Effect(a) { - let ws = do_init(path) - use dispatch <- effect.from - let on_open = fn() { dispatch(wrapper(OnOpen(ws))) } - let on_message = fn(in_msg) { dispatch(wrapper(OnMessage(in_msg))) } - let on_close = fn(code) { - case code { - 1000 -> dispatch(wrapper(OnClose(Normal))) - 1001 -> dispatch(wrapper(OnClose(GoingAway))) - 1002 -> dispatch(wrapper(OnClose(ProtocolError))) - 1003 -> dispatch(wrapper(OnClose(UnexpectedTypeOfData))) - 1005 -> dispatch(wrapper(OnClose(NoCodeFromServer))) - 1006 -> dispatch(wrapper(OnClose(AbnormalClose))) - 1007 -> dispatch(wrapper(OnClose(IncomprehensibleFrame))) - 1008 -> dispatch(wrapper(OnClose(PolicyViolated))) - 1009 -> dispatch(wrapper(OnClose(MessageTooBig))) - 1010 -> dispatch(wrapper(OnClose(FailedExtensionNegotation))) - 1011 -> dispatch(wrapper(OnClose(UnexpectedFailure))) - 1015 -> dispatch(wrapper(OnClose(FailedTLSHandshake))) - } - } - - do_register(ws, on_open, on_message, on_close) -} - -external fn do_init(path) -> WebSocket = - "./ffi.mjs" "init_websocket" - -external fn do_register( - ws: WebSocket, - on_open: fn() -> Nil, - on_message: fn(String) -> Nil, - on_close: fn(Int) -> Nil, -) -> Nil = - "./ffi.mjs" "register_websocket_handler" - -/// Send a text message over the web socket. This is asynchronous. There is no -/// expectation of a reply. See `init`. Only works on an Non-Closed socket. -/// Returns a `Effect(a)` that you must pass as second entry in the lustre `update` return. -pub fn send(ws: WebSocket, msg: String) -> Effect(a) { - effect.from(fn(_) { do_send(ws, msg) }) -} - -external fn do_send(ws: WebSocket, msg: String) -> Nil = - "./ffi.mjs" "send_over_websocket" diff --git a/compat/lustre_websocket/test/lustre_websocket_test.gleam b/compat/lustre_websocket/test/lustre_websocket_test.gleam deleted file mode 100644 index 2ce0f90..0000000 --- a/compat/lustre_websocket/test/lustre_websocket_test.gleam +++ /dev/null @@ -1,15 +0,0 @@ -import gleeunit -import lustre_websocket as ws - -pub fn main() { - gleeunit.main() -} - -pub type Wrapper { - Wrapper(ws.WebSocketEvent) -} - -pub fn rather_thin_compilation_test() { - let _cmd = ws.init("/blah", Wrapper) - // We cannot run the resulting lustre.Cmd, but we can at least ensure it can be used/compiled this way -} |