diff --git a/INTEGRATION_NOTES.md b/INTEGRATION_NOTES.md new file mode 100644 index 0000000..df0d29a --- /dev/null +++ b/INTEGRATION_NOTES.md @@ -0,0 +1,149 @@ +# Discourse integration review — September 2026 + +## Direction + +Keep the legacy BBCode language for existing posts and authors who use it. A +separate HTML/CSS layout block could be useful for new designs, but changing the +authoring language does not remove the rendering, sanitization, editor, and +maintenance work. This plugin already exposes substantial HTML/CSS through +`[div]`, `[class]`, and the layout tags. + +The local compatibility work targets Discourse core revision `c9d27d5d2e`. +Selected public examples from the saved XenForo snapshot are local fixtures, +not part of this repository: the BBCode guide, BBCode+ guide, and Sock Pile. +Their original credits remain visible. The full XenForo database was not +restored and the migration importer was not run. + +## Integration improvements + +- Register BBCode processing through the native Markdown plugin pipeline rather + than intercepting assignment of Discourse's Markdown engine. +- Keep ordinary Markdown paragraphs and code handling when no legacy tags are + present. Respect feature overrides used by chat and the plugin enable setting. +- Use the same registered parser bundle for saved posts and editor previews, + with Discourse's normal sanitizer after rendering. +- Use native `details` disclosure behavior for spoilers and accordion slides; + make inline spoiler decoration repeatable and keyboard accessible. +- Restore the table/list syntax used by existing XenForo posts. +- Recover unclosed/misnested legacy layout tags without dropping their content, + and keep generated, indented layout HTML out of Markdown code blocks. +- Preserve literal hash labels inside authored DIV and NOBR layouts rather than + turning them into Markdown headings. Native Markdown headings outside those + layouts and explicit BBCode headings still work. Existing affected posts need + a rebake after installing the updated parser. +- Read complete default BBCode options with either single or double outer + quotes, preserving interior CSS quotes and equals signs in URLs. This repairs + CSS variables/backgrounds in layouts without modifying the author's source. +- Match XenForo tab title spacing so authored visual controls align with the + radio labels underneath. Ordinary named tabs and keyboard focus remain. +- Keep positioned artwork inside the author's own horizontal scroll container + on small screens, without shrinking fixed-width designs or adding a second + scroll area around the post. +- Replace the obsolete admin Handlebars template with a current `.gjs` template. + Parser refresh uses an authenticated POST request. +- Keep optional BBScript disabled by default. Cleaning up decoration and event + handlers is not a complete security review of that interpreter. + +## HTML/CSS alternatives + +### Constrained layouts inside normal posts + +This fits Discourse's normal selection, quoting, links, image handling, and +document sizing most closely. It needs a deliberately restricted language: + +1. Parse HTML and allow selected layout/formatting elements and attributes. + Remove executable markup, event handlers, forms, and unapproved embeds. + Validate URL schemes as well as element names. +2. Parse CSS, validate class/ID tokens, and rewrite every selector and animation + reference into a generated wrapper the author cannot alter. Random suffixes + or regular-expression stripping alone are not sufficient. +3. Limit properties/values and resource URLs. Prevent viewport positioning, + global selectors, imports, and rules that target Discourse controls. +4. Protect layout and paint containment on the wrapper. Containment is useful + for positioning/clipping, but does not replace HTML/CSS sanitization. +5. Preserve readable text for search, email, quoting, and moderation, and test + desktop/mobile preview parity. + +These restrictions need a compatibility inventory: authors' existing free-form +designs may depend on CSS that the constrained renderer would reject. A shared +rendering layer could eventually support both BBCode and constrained HTML input. + +### Sandboxed HTML/CSS frames + +For substantially freer CSS, an iframe provides a separate document boundary. +Start with no user scripts, forms, top-level navigation, or same-origin access; +sanitize the content and apply a restrictive content security policy as well. +If frames are served from a URL, use a dedicated origin so opening that URL +directly does not bypass the intended isolation. + +This requires more Discourse integration: frame sizing, search and email +fallbacks, selection/quoting, lightboxes, theme colors, responsive behavior, +accessibility, and performance with many posts. Any resizing bridge must be +controlled by the plugin and must not grant arbitrary user scripts access to +the forum. Avoid treating unrestricted iframes as a drop-in post renderer. + +## Current boundaries + +The legacy plugin's free-form inline styles and custom class styles are not a +security boundary. CSS can affect layout outside the intended content area; +optional `@scope` and random class suffixes do not establish complete isolation. +The BBScript engines also contain global selector and HTML-construction paths +that need a dedicated review before expanding their use. + +This compatibility update does not introduce unrestricted user HTML or claim +that the existing BBCode+ language is sandboxed. Legacy optional integrations, +such as Font Awesome kits and remote image/font hosts, still need their normal +configuration and available resources. BBCode+ features missing from the parser +need separate implementation and regression coverage. + +The copied BBCode+ guide demonstrates those limits: its layout is available, +but script-driven navigation/examples remain inactive while BBScript is disabled, +and its legacy `[input]` control is not implemented. Use the composer's Markdown +mode to author and edit BBCode; this update does not add a rich-text BBCode editor. + +## References + +- [HTML iframe sandbox rules](https://html.spec.whatwg.org/multipage/iframe-embed-object.html#attr-iframe-sandbox) +- [Iframe behavior and tradeoffs](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe) +- [CSS paint containment](https://drafts.csswg.org/css-contain/#paint-containment) +- [Shadow DOM encapsulation](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) + +## Focused tests + +Inside a Discourse checkout with this plugin installed and frontend assets built: + +```sh +RAILS_ENV=test LOAD_PLUGINS=1 bundle exec rspec plugins/bbcode/spec/lib/pretty_text_spec.rb +bundle exec ruby bin/qunit --target bbcode --rails-port 3000 --filter BBCode +``` + +The server tests use synthetic inputs. Real-source fixture content is kept +outside the plugin checkout and should not be committed as test data. + +Local validation on the core revision above: 55 server examples and 13 browser +unit tests passed, along with JavaScript/Ruby/SCSS lint checks. An authenticated +Chromium check covered desktop and an emulated iPhone viewport, the guide and +five Sock Pile samples, spoiler/tab/accordion interactions, Markdown composer +preview, and the admin parser refresh. It checked both console errors and +uncaught errors. This is mobile viewport coverage, not a Safari/iOS device test. + +The later hash-label fix adds 10 server boundary regressions. A fresh browser +check verified the original Midnight card and three quoted copies at desktop +and mobile sizes: labels remain 7px and fit inside their card. Browser preview +checks retain native Markdown headings before/after the same legacy layout. +The local fixture refresh corrected 136 accidental headings across 65 posts +without changing any raw post text. Other audit gaps (media, icon dependencies, +some URL syntax, and image sizing) remain separate compatibility work. + +The Cyberkill follow-up adds six generic attribute regressions, covering single, +double and unquoted default options, interior CSS quotes, keyed attributes and +URLs containing equals signs. Browser checks verify original and quoted light +and dark variants, including 32 clicks/taps at the visible numbered button +centers across desktop/mobile. A newly authored synthetic layout verifies the +same path with different colors and ordinary named tabs in browser preview. +This fixes shared tag/CSS behavior; production code contains no fixture IDs, +design names, or source-content substitutions. + +Pre-PR checks also cover revealed inline-spoiler links reaching Discourse's +delegated handlers and long named tab labels wrapping within narrow layouts. +Short author-overlaid tab controls retain their XenForo-compatible geometry. diff --git a/README.md b/README.md index 7db9050..19d044c 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,8 @@ The goal of this repo and plugin is to provide users with the BBCode suite that - [x] Sides - [x] Tabs - [x] Accordions -- [x] ~~Tables~~ now using markdown tables +- [x] Legacy tables and native Markdown tables +- [x] Legacy numbered and bulleted lists - [x] Center Block - [x] Background - [x] Border @@ -105,6 +106,18 @@ For more, see the [Discourse Docker Guide](https://meta.discourse.org/docs?topic ## Architecture -The architecture of this project is for all BBCode Parser related code to be contained in `/bbcode-src`, which would then be minified into a module and added to the appropriate location in `/assets/javascripts` to be used by the discourse plugin proper. This is to work around the weird way discourse requires libraries to be loaded in. There will be a Rollup config and github action setup to automate minifying and moving the module. - -Honestly, if anyone has a better solution, please send help. +Parser and tag implementations live in `bbcode-src`. Run +`yarn install --frozen-lockfile` and `yarn build` to regenerate the checked-in parser bundles +and source maps before deploying source changes. Install the full plugin in +Discourse's `plugins/bbcode` directory and restart Discourse and its frontend +build when adding or removing plugin modules. + +The parser is registered for both server cooking and browser previews. A native +Markdown plugin invokes it for legacy BBCode and leaves ordinary Markdown on +Discourse's normal path. Rendered content still passes through the native +sanitizer. Interactive features use cooked-post decoration and native HTML +controls. + +See [integration notes](INTEGRATION_NOTES.md) for the current compatibility work, +tests, remaining boundaries, and the tradeoffs of a future contained HTML/CSS +editor. BBScript remains optional and disabled by default. diff --git a/assets/bundled/bbcode-parser.min.js b/assets/bundled/bbcode-parser.min.js index 6b3dff1..17995d8 100644 --- a/assets/bundled/bbcode-parser.min.js +++ b/assets/bundled/bbcode-parser.min.js @@ -1,3 +1,3 @@ /* Source code in bbcode-src */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).bbcodeParser={})}(this,(function(t){"use strict";const e="\n",n="\t",s="=",r='"',o=" ",a="[",i="]",c="/",l="\\";function u(t){return"object"==typeof t&&null!==t&&"tag"in t}function d(t){return"string"==typeof t}function g(t){return t===e}function h(t,e,n){return Object.keys(t).reduce(((n,s)=>e(n,s,t)),n)}function p(t){return u(t)&&Array.isArray(t.content)?t.content.reduce(((t,e)=>t+p(e)),0):d(t)?String(t).length:0}function f(t){return t.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'").replace(/(javascript|data|vbscript):/gi,"$1%3A")}function b(t,e){switch(typeof e){case"boolean":return e?`${t}`:"";case"number":return`${t}="${e}"`;case"string":return`${t}="${f(e)}"`;case"object":return`${t}="${f(JSON.stringify(e))}"`;default:return""}}function m(t){return null==t?"":h(t,((t,e,n)=>[...t,b(e,n[e])]),[""]).join(" ")}const w=(t,e)=>{const n=h(e||{},((t,e,n)=>n[e]===e?n[e]:null),null);if(n){const s=b(t,n),r={...e};delete r[String(n)];return`${s}${m(r)}`}return`${t}${m(e)}`};class y{attr(t,e){return void 0!==e&&(this.attrs[t]=e),this.attrs[t]}append(t){return function(t,e){Array.isArray(t.content)&&t.content.push(e)}(this,t)}setStart(t){this.start=t}setEnd(t){this.end=t}get length(){return p(this)}toTagStart({openTag:t=a,closeTag:e=i}={}){return`${t}${w(String(this.tag),this.attrs)}${e}`}toTagEnd({openTag:t=a,closeTag:e=i}={}){return`${t}${c}${this.tag}${e}`}toTagNode(){const t=new y(String(this.tag).toLowerCase(),this.attrs,this.content);return this.start&&t.setStart(this.start),this.end&&t.setEnd(this.end),t}toString({openTag:t=a,closeTag:e=i}={}){const n=this.content?((t,e,n)=>{const s=t=>u(t)?t.toString({openTag:e,closeTag:n}):String(t);return Array.isArray(t)?t.reduce(((t,e)=>null!==e?t+s(e):t),""):t?s(t):null})(this.content,t,e):"",s=this.toTagStart({openTag:t,closeTag:e});return null===this.content||Array.isArray(this.content)&&0===this.content.length?s:`${s}${n}${this.toTagEnd({openTag:t,closeTag:e})}`}static create(t,e={},n=null,s){const r=new y(t,e,n);return s&&r.setStart(s),r}static isOf(t,e){return t.tag===e}constructor(t,e,n){this.tag=t,this.attrs=e,this.content=n}}const v="t",x=t=>t&&void 0!==t.v?t.v:"",$=t=>x(t).charCodeAt(0)===c.charCodeAt(0);class k{get type(){return this[v]}isEmpty(){return 0===this[v]||isNaN(this[v])}isText(){return!(!(t=this)||void 0===t[v]||5!==t[v]&&6!==t[v]&&1!==t[v]);var t}isTag(){return!(!(t=this)||void 0===t[v])&&2===t[v];var t}isAttrName(){return!(!(t=this)||void 0===t[v])&&3===t[v];var t}isAttrValue(){return!(!(t=this)||void 0===t[v])&&4===t[v];var t}isStart(){return!$(this)}isEnd(){return $(this)}getName(){return(t=>{const e=x(t);return $(t)?e.slice(1):e})(this)}getValue(){return x(this)}getLine(){return(t=this)&&t.l||0;var t}getColumn(){return(t=this)&&t.r||0;var t}getStart(){return(t=this)&&t.s||0;var t}getEnd(){return(t=this)&&t.e||0;var t}toString({openTag:t=a,closeTag:e=i}={}){return((t,e=a,n=i)=>{let s=e;return s+=x(t),s+=n,s})(this,t,e)}constructor(t,e,n=0,s=0,r=0,o=0){this.l=n,this.r=s,this[v]=t||0,this.v=String(e),this.s=r,this.e=o}}class T{skip(t=1,e){this.c.pos+=t,this.o&&this.o.onSkip&&!e&&this.o.onSkip()}hasNext(){return this.c.len>this.c.pos}getCurr(){return void 0===this.s[this.c.pos]?"":this.s[this.c.pos]}getPos(){return this.c.pos}getLength(){return this.c.len}getRest(){return this.s.substring(this.c.pos)}getNext(){const t=this.c.pos+1;return t<=this.s.length-1?this.s[t]:null}getPrev(){const t=this.c.pos-1;return void 0===this.s[t]?null:this.s[t]}isLast(){return this.c.pos===this.c.len}includes(t){return this.s.indexOf(t,this.c.pos)>=0}grabWhile(t,e){let n=0;if(this.hasNext())for(n=this.c.pos;this.hasNext()&&t(this.getCurr());)this.skip(1,e);return this.s.substring(n,this.c.pos)}grabN(t=0){return this.s.substring(this.c.pos,this.c.pos+t)}substrUntilChar(t){const{pos:e}=this.c,n=this.s.indexOf(t,e);return n>=0?this.s.substring(e,n):""}constructor(t,e={}){this.s=t,this.c={pos:0,len:t.length},this.o=e}}const A=(t,e)=>new T(t,e);const C=[o,n],S=[s,o,n],_=t=>C.indexOf(t)>=0,j=t=>t===l,L=t=>((t,e)=>{for(;t.charAt(0)===e;)t=t.substring(1);for(;t.charAt(t.length-1)===e;)t=t.substring(0,t.length-1);return t})(t,r).replace(l+r,r);function O(t,u={}){let d=0,g=0,h=0,p=-1,f=0,b=0,m="";const w=new Array(Math.floor(t.length)),y=u.openTag||a,v=u.closeTag||i,x=!!u.enableEscapeTags,$=(u.contextFreeTags||[]).filter(Boolean).map((t=>t.toLowerCase())),T=u.caseFreeTags||!1,C=new Map,O=u.onToken||(()=>{}),E=[v,y,r,l,o,n,s,e,"!"],N=[y,o,n,e],W=t=>-1===N.indexOf(t),I=()=>{h++},V=(t,e)=>{""!==m&&e&&(m=""),""===m&&$.includes(t.toLowerCase())&&(m=t)},U=A(t,{onSkip:I});function q(t,e,n,s){const r=function(t,e,n=0,s=0,r=0,o=0){return new k(t,e,n,s,r,o)}(t,e,d,g,n,s);O(r),g=h,p+=1,w[p]=r}function D(t,e,n){if(1===b){const e=t=>!(t===s||_(t)),n=t.grabWhile(e),r=t.isLast(),o=t.getCurr()!==s;return t.skip(),r||o?q(4,L(n)):q(3,n),r?0:o?1:2}if(2===b){let n=!1;const o=o=>{const a=o===r,i=t.getPrev(),c=t.getNext(),u=i===l,d=c===s,g=_(o),h=c&&_(c);return!(!n||!(t=>S.indexOf(t)>=0)(o))||!!(!a||u||(n=!n,n||d||h))&&(!!e||!g)},a=t.grabWhile(o);return t.skip(),q(4,L(a)),t.getPrev()===r&&g++,t.isLast()?0:1}const o=n+t.getPos()-1,a=t.grabWhile((e=>!(e===s||_(e)||t.isLast())));if(q(2,a,o,n+t.getLength()+1),V(a),t.skip(),g++,e)return 2;return t.includes(s)?1:2}function G(){const t=U.getCurr(),e=U.getNext();U.skip();const n=U.substrUntilChar(v),r=0===n.length||n.indexOf(y)>=0;if(e&&(o=e,E.indexOf(o)>=0)||r||U.isLast())return q(1,t),0;var o;const a=-1===n.indexOf(s),i=n[0]===c;if(a||i){const t=U.getPos()-1,e=U.grabWhile((t=>t!==v)),n=t+e.length+2;return U.skip(),q(2,e,t,n),V(e,i),0}return 2}function z(){const t=U.getPos(),e=U.grabWhile((t=>t!==v),!0),n=A(e,{onSkip:I}),s=n.includes(o);for(b=0;n.hasNext();)b=D(n,!s,t);return U.skip(),0}function P(){if(U.getCurr()===e)return q(6,U.getCurr()),U.skip(),h=0,g=0,d++,0;if(_(U.getCurr())){return q(5,U.grabWhile(_)),0}if(U.getCurr()===y){if(m){const t=y.length+1+m.length,e=`${y}${c}${m}`;if(U.grabN(t)===e)return 1}else if(U.includes(v))return 1;return q(1,U.getCurr()),U.skip(),g++,0}if(x){if(j(U.getCurr())){const t=U.getCurr(),e=U.getNext();return U.skip(),e&&(t=>t===y||t===v||t===l)(e)?(U.skip(),q(1,e),0):(q(1,t),0)}const t=t=>W(t)&&!j(t);return q(1,U.grabWhile(t)),0}return q(1,U.grabWhile(W)),0}return{tokenize:function(){for(f=0;U.hasNext();)switch(f){case 1:f=G();break;case 2:f=z();break;default:f=P()}return w.length=p+1,w},isTokenNested:function(e){const n=y+c+e;if(C.has(n))return!!C.get(n);{const e=T?t.toLowerCase().indexOf(n.toLowerCase())>-1:t.indexOf(n)>-1;return C.set(n,e),e}}}}class E{last(){return Array.isArray(this.n)&&this.n.length>0&&void 0!==this.n[this.n.length-1]?this.n[this.n.length-1]:null}flush(){return!!this.n.length&&this.n.pop()}push(t){this.n.push(t)}toArray(){return this.n}constructor(){this.n=[]}}const N=()=>new E;function W(t,e={}){const n=e,s=n.openTag||a,r=n.closeTag||i,o=(n.onlyAllowTags||[]).filter(Boolean).map((t=>t.toLowerCase())),c=n.caseFreeTags||!1;let l=null;const d=N(),g=N(),h=N(),p=N(),f=new Set;function b(t){return Boolean(f.has(c?t.toLowerCase():t))}function m(){h.flush()&&p.flush()}function w(){const t=g.last();return t&&u(t)?t.content:d.toArray()}function v(t,e,n=!0){Array.isArray(t)&&void 0!==e&&(t.push(e.toTagStart({openTag:s,closeTag:r})),Array.isArray(e.content)&&e.content.length&&(e.content.forEach((e=>{t.push(e)})),n&&t.push(e.toTagEnd({openTag:s,closeTag:r}))))}function x(t,e){var n;Array.isArray(t)&&void 0!==e&&(u(e)?(n=e.tag,!o.length||o.indexOf(n.toLowerCase())>=0?t.push(e.toTagNode()):v(t,e)):t.push(e))}function $(t){m();const e=y.create(t.getValue(),{},[],{from:t.getStart(),to:t.getEnd()}),n=function(t){const e=t.getValue(),n=c?e.toLowerCase():e,{isTokenNested:s}=l||{};return!f.has(n)&&s&&s(n)?(f.add(n),!0):f.has(n)}(t);if(h.push(e),n)g.push(e);else{x(w(),e)}}function k(t){t.isStart()&&$(t),t.isEnd()&&function(t){const e=t.getValue().slice(1),o=g.flush();if(m(),o){const e=w();u(o)&&o.setEnd({from:t.getStart(),to:t.getEnd()}),x(e,o)}else if(b(e)){if("function"==typeof n.onError){const e=t.getValue(),s=t.getLine(),r=t.getColumn();n.onError({tagName:e,lineNumber:s,columnNumber:r})}}else x(w(),t.toString({openTag:s,closeTag:r}))}(t)}const T=e.createTokenizer?e.createTokenizer:O;l=T(t,{onToken:function(t){t.isTag()?k(t):function(t){const e=h.last(),n=t.getValue(),o=b(t.toString()),a=w();if(null!==e)if(t.isAttrName()){p.push(n);const t=p.last();t&&e.attr(t,"")}else if(t.isAttrValue()){const t=p.last();t?(e.attr(t,n),p.flush()):e.attr(n,n)}else t.isText()?o?e.append(n):x(a,n):t.isTag()&&x(a,t.toString({openTag:s,closeTag:r}));else t.isText()?x(a,n):t.isTag()&&x(a,t.toString({openTag:s,closeTag:r}))}(t)},openTag:s,closeTag:r,onlyAllowTags:n.onlyAllowTags,contextFreeTags:n.contextFreeTags,caseFreeTags:n.caseFreeTags,enableEscapeTags:n.enableEscapeTags}),l.tokenize();const A=g.flush();return null!==A&&A&&u(A)&&b(A.tag)&&v(w(),A,!1),d.toArray()}const I=t=>"object"==typeof t&&null!==t;function V(t,e){const n=t;if(Array.isArray(n))for(let t=0;t[].some.call(e,(e=>U(t,e))))):!(!I(t)||!I(e))&&Object.keys(t).every((n=>{const s=e[n],r=t[n];return I(r)&&I(s)?U(r,s):"boolean"==typeof r?r!==(null===s):s===r})):t===e)}function q(t,e){const n=t;return n.messages=[...n.messages||[]],n.options={...e,...n.options},n.walk=function(t){return V(this,t)},n.match=function(t,e){return function(t,e,n){return Array.isArray(e)?V(t,(t=>{for(let s=0;sU(e,t)?n(t):t))}(this,t,e)},n}function D(t,e){const{stripTags:n=!1}=e||{};if(null==t)return"";if("string"==typeof t||"number"==typeof t)return String(t);if(Array.isArray(t))return G(t,e);if(u(t)){if(n)return G(t.content,e);const s=m(t.attrs);return null===t.content?"<"+t.tag+s+"/>":"<"+t.tag+s+">"+G(t.content,e)+""}return""}function G(t,e){return t&&Array.isArray(t)?t.reduce(((t,n)=>t+D(n,e)),""):t?D(t,e):""}const z=(t,e,n=[])=>({tag:t,attrs:e,content:n,gen:!0}),P=(t,e)=>{const n=Object.keys(t.attrs).join(" "),s=Object.values(t.attrs).join(" ");if(n!==s)return t.attrs;if(!e||!t.start)return{_default:s};const r=e.substring(t.start.from,t.start.to);if(!r.includes("="))return t.attrs;const o=r.split("=");if(2!==o.length)return t.attrs;let a=o[1].slice(0,-1).trim();return a.startsWith('"')&&a.endsWith('"')&&(a=a.slice(1,-1)),{_default:a}},M=(t,e)=>{if(t.start)return e.substring(t.start.from,t.start.to);if(!t.attrs)return`[${t.tag}]`;const n=P(t,e);return n._default?`[${t.tag}=${n._default}]`:t.toTagStart()},F=(t,e)=>t.end?e.substring(t.end.from,t.end.to):t.toTagEnd(),B=(t,e)=>t.end?[M(t,e),...t.content,F(t,e)]:M(t,e),R=(t,e,n)=>{const s=t.substring(n||0).search(e);return s>=0?s+(n||0):s},J="\x3c!-- bbcode injected newlines --\x3e\n\n",Z="\n\n\x3c!-- bbcode pre injected newlines --\x3e",H="\x3c!-- bbcode injected newlines --\x3e",K=new RegExp(`^${/(http|ftp|https|upload):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])/.source}|${/\!?\[.*\]\((http|ftp|https|upload):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])\)/.source}$`),Q=/((\n|^)(?```+|~~~+)(?.*\n))|(?\[(?i?code|plain)(=.*)?\])|(?(?`{1,2})(.*)(?\k))/im,X=/^(\|[^\n]+\|\r?\n)((?:\| ?:?[-]+:? ?)+\|)(\n(?:\|[^\n]+\|\r?\n?)*)?$/m;function Y(){let t=(new Date).getTime();return window.performance&&"function"==typeof window.performance.now&&(t+=performance.now()),"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,(function(e){const n=(t+16*Math.random())%16|0;return t=Math.floor(t/16),("x"===e?n:3&n|8).toString(16)}))}const tt=t=>"object"==typeof t,et=t=>"string"==typeof t,nt=(t,e=!1)=>{const n=t;if(Array.isArray(n)){st(n),n.some(et)&&(n.unshift(J),n.push(J));for(let t=0;t{let e=t.findLastIndex((t=>et(t)&&!g(t)))+1;for(let n=e-1;n>=0;n--)et(t[n])&&!g(t[n])||(g(t[n])||tt(t[n]))&&(n!==e-1&&t.splice(n+1,e-n-1,t.slice(n+1,e).join("")),e=n);0!==e&&t.splice(0,e,t.slice(0,e).join(""))},rt=t=>{const e=t;if(Array.isArray(e))for(let t=0;t1&&" "===e[0]){let t=e.length;return[String.fromCharCode(160).repeat(t)]}return e},ot=/\n{2,}/gm,at=t=>t.walk((t=>(u(t)&&t.attrs&&Object.keys(t.attrs).forEach((e=>{"string"==typeof t.attrs[e]&&(t.attrs[e]=t.attrs[e].replace(ot,"\n"))})),t)));function it(t,e,n,s){return e.walk((e=>{if(u(e)){const r=e.tag,o=t[r];if("function"==typeof o)return o(e,n,s)}return e}))}const ct=Symbol("slide-title-open"),lt=Symbol("slide-title-close"),ut=Symbol("slide-close"),dt=/(?\{slide=)|(?\})|(?\{\/slide\})/i;function gt(t){switch(t){case ct:return"{slide=";case lt:return"}";case ut:return"{/slide}";default:return t}}const ht={accordion:(t,e)=>{const n=Y(),s=function(t){t=[...t];const e=[];for(;t.length>0;){const n=t[0];if(u(n)){e.push(t.shift());continue}const s=R(n,dt);if(-1===s){e.push(t.shift());continue}const r=n.match(dt),o=n.slice(0,s),a=n.slice(s+r[0].length);o.length&&e.push(o),r.groups.slideTitleOpen&&e.push(ct),r.groups.slideTitleClose&&e.push(lt),r.groups.slideClose&&e.push(ut),a.length?t[0]=a:t.shift()}return e}(t.content),r=function(t){const e=[];let n=null,s=null;for(const r of t)if(r===ct&&null===s)n=y.create("slide"),n.content=[],n.customTitle=[],s=ct;else{if(r===lt&&s===ct){s=lt;continue}r===ut&&n&&s===lt?(e.push(n),n=null,s=null):n?s===ct?n.customTitle.push(gt(r)):n.content.push(gt(r)):e.push(gt(r))}return e}(s),o=r.filter((t=>u(t)&&"slide"===t.tag)).map((t=>(t.isValid=!0,t.groupId=n,t)));if(!o.length)return[M(t,e.data.raw),...t.content,F(t,e.data.raw)];const a=P(t,e.data.raw);if(a._default){const t=a._default.split("|").map((t=>t.trim())),e=t.filter((t=>["bright","bcenter","bleft","fleft","fright"].includes(t))).pop();e&&(a.align??=e),(t.some((t=>t.endsWith("px")))||t.some((t=>t.endsWith("%"))))&&(a.width??=t.find((t=>t.endsWith("px")||t.endsWith("%"))))}let i=a.align?.toLowerCase()||"",c="";return(a.width?.endsWith("px")||a.width?.endsWith("%"))&&(c=`width: ${a.width};`),z("div",{class:"bb-accordion "+i,"data-group-id":n,style:c},o)},slide:(t,e)=>{if(!t.isValid)return[M(t,e.data.raw),...t.content,F(t,e.data.raw)];const n=P(t,e.data.raw);let s=[n.title||n._default||"Slide"],r=!!n.open||!1,o=n.left?"left":n.right?"right":n.center?"center":"left";if(t.customTitle?.length){s=t.customTitle;const e=s.filter((t=>"string"==typeof t)).join("").toLowerCase().split("|").map((t=>t.trim()));e.includes("open")&&(r=!0),e.includes("right")&&(o="right"),e.includes("center")&&(o="center"),e.includes("left")&&(o="left"),s=s.map((t=>(d(t)&&(t=t.replace(/\|(open|right|center|left)/gi,"")),t)))}return[z("details",{class:"bb-slide",open:r},[z("summary",{class:"bb-slide-title",style:`text-align: ${o}; ${n.style||""}`},s),z("div",{class:"bb-slide-content"},t.content)])]}},pt={left:t=>z("div",{class:"bb-left"},t.content),center:t=>z("div",{class:"bb-center"},t.content),right:t=>z("div",{class:"bb-right"},t.content)},ft={a:(t,e)=>{const n=P(t,e.data.raw)._default||"";return z("a",{id:`user-anchor-${n.trim()}`,name:`user-anchor-${n.trim()}`},t.content)},goto:(t,e)=>{const n=P(t,e.data.raw)._default||"";return z("a",{href:`#user-anchor-${n.trim()}`},t.content)}},bt=["arial","book antiqua","courier new","georgia","tahoma","times new roman","trebuchet ms","verdana"],mt={thin:"100",extralight:"200",light:"300",regular:"400",medium:"500",semibold:"600",bold:"700",extrabold:"800",black:"900"},wt=["ital","opsz","slnt","wdth","wght"],yt=/(?[a-zA-Z]*)?\s?(?[0-9]*)?\s?(?italic)?/;const vt=z("div",{class:"bb-email-header"},""),xt=z("div",{class:"bb-email-footer"},z("div",{class:"bb-email-button"},"")),$t={row:t=>z("div",{class:"bb-row"},t.content),column:(t,e)=>{const n=P(t,e.data.raw)._default||"8",s=n.startsWith("span")?`column-width-${n}`:`column-width-span${n}`;return z("div",{class:"bb-column","data-span":s},t.content)}},kt=["init","click","change","input","dblclick","mouseenter","mouseleave","scroll"];const Tt=["me","them","right","left"],At={textmessage:(t,e)=>{const n=t.content.filter((t=>u(t)&&"message"===t.tag));if(n.forEach((t=>{t.isValid=!0})),!n.length)return B(t,e.data.raw);const s=P(t,e.data.raw)._default||"Recipient",r=""!==s.trim()?s:"Recipient";return z("div",{class:"bb-textmessage"},[z("div",{class:"bb-textmessage-name"},r),z("div",{class:"bb-textmessage-overflow"},[z("div",{class:"bb-textmessage-content"},t.content)])])},message:(t,e)=>{if(!t.isValid)return B(t,e.data.raw);let n=P(t,e?.data?.raw)?._default?.toLowerCase();Tt.includes(n)&&"right"!==n||(n="me"),"left"===n&&(n="them");return z("div",{class:"me"===n?"bb-message-me":"bb-message-them"},[z("div",{class:"bb-message-content"},t.content)])}},Ct={...ht,...pt,...ft,animation:(t,e)=>{e.data.previewing||e.data.commonGUID||(e.data.commonGUID="post-"+Math.random().toString(36).substring(2,7));const n=e.data.previewing?"preview":e.data.commonGUID,s=P(t,e.data.raw)?._default||"",r=t.content.filter((t=>u(t)&&"keyframe"===t.tag)).map((t=>{t.isValid=!0;const n=P(t,e.data.raw)._default||"";t.ident=n+(n.match(/^\d+$/)?"%":"");const s=t.content.filter(d).join("").replaceAll(/[\[\]\{\}]/g,"");return t.formatted=`${t.ident}{ ${s} }`,t})),o=`@keyframes ${n}${s} { ${r.map((t=>t.formatted)).join("\n")} }`;return e.data.styles.push(o),[]},bg:(t,e)=>{const n=P(t,e.data.raw)._default;return z("div",{style:`background-color: ${n};`,class:"bb-background"},t.content)},block:(t,e)=>{const n="block",s=(P(t,e.data.raw)._default||n).toLowerCase(),r=["block","dice","dice10","setting","warning","storyteller","announcement","important","question","encounter","information","character","treasure"].includes(s)?s:n;return z("table",{class:"bb-block","data-bb-block":r},[z("tbody",[z("tr",[z("td",{class:"bb-block-icon"}),z("td",{class:"bb-block-content"},t.content)])])])},blockquote:(t,e)=>{const n=P(t,e.data.raw)._default||"";return z("div",{class:"bb-blockquote"},[z("div",{class:"bb-blockquote-left"}),z("div",{class:"bb-blockquote-content"},[t.content,z("div",{class:"bb-blockquote-speaker"},""!==n?`- ${n}`:"")]),z("div",{class:"bb-blockquote-right"})])},border:(t,e)=>{const n=P(t,e.data.raw)._default;return z("div",{style:`border: ${n};`,class:"bb-border"},t.content)},br:()=>z("br",{},null),centerblock:(t,e)=>{const n=P(t,e.data.raw)._default||"50";return z("div",{style:`margin: 0 auto; width: ${n}%`},t.content)},check:(t,e)=>{const n=P(t,e.data.raw)._default||"dot";return z("div",{class:"bb-check","data-type":n},t.content)},class:(t,e)=>{const n=P(t),s=n.name||n._default;e.data.previewing||e.data.commonGUID||(e.data.commonGUID="post-"+Math.random().toString(36).substring(2,7));const r=e.data.previewing?"preview":e.data.commonGUID,o=s+"__"+r,a=t.content.filter(d).map((t=>t.replaceAll("{post_id}",r).replaceAll(/[\[\]\{\}]/g,"")));let i="";const c=[];return["hover","focus","active","focus-within","focus-visible"].includes(n.state?.toLowerCase())&&(i=":"+n.state.toLowerCase()),n.selector&&(i=n.selector.replace(/[,{}\\\n]/g,"")),n.minWidth?.match(/^[0-9]+[a-z]+$/)&&c.push(`(min-width: ${n.minWidth})`),n.maxWidth?.match(/^[0-9]+[a-z]+$/)&&c.push(`(max-width: ${n.maxWidth})`),a.unshift(`.${o}${i} {`),a.push("}"),c.length&&(a.unshift(`@media ${c.join(" and ")} {`),a.push("}")),e.data.styles.push(a.join("")),[]},code:t=>({isWhitespaceSensitive:!0,content:["```"+(P(t)._default||"bbcode")+"\n",t.content,"\n```\n"]}),color:t=>{const e=P(t)._default||"";return""===e.trim()?t.content:z("span",{style:`color: ${e}`},t.content)},comment:t=>z("span",{class:"hidden"},t.content),div:(t,e)=>{if(t.gen)return t;const n=P(t,e.data.raw),s=n.style||n._default,r=n.class;if(!r?.trim())return z("div",{style:s},t.content);e.data.previewing||e.data.commonGUID||(e.data.commonGUID="post-"+Math.random().toString(36).substring(2,7));const o=e.data.previewing?"preview":e.data.commonGUID,a=r.split(" ").map((t=>t+"__"+o)).join(" ");return z("div",{class:a,style:s},t.content)},divide:t=>{const e=(P(t)._default||"").toLowerCase();return z("span",{class:"bb-divide","data-type":e},t.content)},fieldset:(t,e)=>{const n=P(t,e.data.raw)._default||"";return z("fieldset",{class:"bb-fieldset"},[z("legend",{class:"bb-fieldset-legend"},n),z("div",{class:"bb-fieldset"},t.content)])},fa:t=>{const e=t.attrs;let n=e.style||"";return n+=e["primary-color"]?`--fa-primary-color: ${e["primary-color"]};`:"",n+=e["secondary-color"]?`--fa-secondary-color: ${e["secondary-color"]};`:"",n+=e["primary-opacity"]?`--fa-primary-opacity: ${e["primary-opacity"]};`:"",n+=e["secondary-opacity"]?`--fa-secondary-opacity: ${e["secondary-opacity"]};`:"",n+=e["rotate-angle"]?`--fa-rotate-angle: ${e["rotate-angle"]};`:"",z("i",{"data-bbcode-fa":null},[z("i",{class:(t.content||[]).join(""),style:n,"data-fa-transform":e["fa-transform"]||""},[])])},font:(t,e)=>{const n=P(t,e.data.raw),s=n?._default||n?.family||n?.name;if(!s||""===s.trim())return t.content;if(bt.includes(s.trim().toLowerCase()))return z("span",{style:`font-family: '${s}'`},t.content);const r=(t=>{let e={ital:0,wght:400};if(t?.style){const n=t.style.trim().toLowerCase(),s=yt.exec(n).groups||{};s?.italic&&(e.ital=1);const r=s.weight;r&&r>=0&&r<=900?e.wght=r:Object.keys(mt).includes(s.named_weight||"")&&(e.wght=mt[s.named_weight]),e={...e,...Object.fromEntries(Object.entries(t).filter((([t])=>wt.includes(t))))}}return e})(n),o=((t,e)=>(t=t.replaceAll(" ","+"),e=Object.keys(e).sort().reduce(((t,n)=>(t[n]=e[n],t)),{}),"https://fonts.googleapis.com/css2?family="+t+":"+Object.keys(e).join(",")+"@"+Object.values(e).join(",")))(s,r);e.data.fonts.add(o);const a=1===r.ital?"italic":"normal",i=Object.entries(r).filter((([t])=>"wght"!==t&&"ital"!==t));let c="";return i.length&&(c="font-variation-settings: "+i.map((([t,e])=>`'${t}' ${e}`)).join(", ")+";"),z("span",{style:`font-family: '${s}'; font-weight: ${r.wght}; font-style: ${a}; ${c}`,"data-font":o},t.content)},h:t=>z("h1",{},t.content),h1:t=>z("h1",{},t.content),h2:t=>z("h2",{},t.content),h3:t=>z("h3",{},t.content),h4:t=>z("h4",{},t.content),h5:t=>z("h5",{},t.content),h6:t=>z("h6",{},t.content),heightrestrict:t=>{const e=function(t){const e=t&&""!==t.trim()?t.replace(/[^\d.]/g,""):0;return e&&e>=0&&e<=700?e:0===e?0:700}(P(t)._default).toString();return z("div","0"===e?{class:"bb-height-restrict"}:{class:"bb-height-restrict",style:`height: ${e}px;`},t.content)},highlight:t=>z("span",{class:"bb-highlight"},t.content),icode:t=>({isWhitespaceSensitive:!0,content:["`",t.content,"`"]}),imagefloat:t=>{const e=P(t)._default||"";return z("div",{class:`bb-float-${e}`},t.content)},inlinespoiler:t=>z("span",{class:"bb-inline-spoiler"},t.content),justify:t=>z("div",{class:"bb-justify"},t.content),keyframe:(t,e)=>t.isValid?[]:[M(t,e.data.raw),...t.content,F(t,e.data.raw)],mail:t=>{const e=t.attrs;let n={mailOption:(e.type||"send").toLowerCase(),person:e.person||"Unknown",subject:e.subject||"Empty"};return z("div",{class:"bb-email","data-bb-email":n.mailOption},[vt,(o=n.person,z("div",{class:"bb-email-address"},o)),(r=n.subject,z("div",{class:"bb-email-subject"},r)),(s=t.content,z("div",{class:"bb-email-content"},s)),xt]);var s,r,o},newspaper:t=>z("div",{class:"bb-newspaper"},t.content),nobr:t=>({disableLineBreakConversion:!0,content:t.content}),note:t=>z("div",{class:"bb-note"},[z("div",{class:"bb-note-tape"},""),z("div",{class:"bb-note-content"},[t.content,z("div",{class:"bb-note-footer"},"")])]),ooc:t=>z("div",{class:"bb-ooc"},t.content),pindent:t=>z("span",{class:"bb-pindent"},t.content),plain:t=>t.content,print:t=>{const e="print",n=(P(t)._default||e).toLowerCase(),s=["print","line","graph","parchment"].includes(n)?n:e;return z("div",{class:s===e?"bb-print":`bb-print-${s}`},t.content)},progress:t=>{const e=P(t)._default;return z("div",{class:"bb-progress"},[z("div",{class:"bb-progress-text"},t.content),z("div",{class:"bb-progress-bar",style:`width: calc(${e}% - 6px)`},""),z("div",{class:"bb-progress-bar-other"},"")])},quote:(t,e)=>{const n=P(t,e.data.raw);return"\n"===t.content[0]&&t.content.shift(),[`\n[${t.tag}="${n._default}"]\n\n`,...t.content,"\n\n[/quote]\n"]},...$t,thinprogress:(t,e)=>{const n=P(t,e.data.raw)._default;return z("div",{class:"bb-progress-thin"},[z("div",{class:"bb-progress-text"},t.content),z("div",{class:"bb-progress-bar",style:`width: calc(${n}% - 6px)`},""),z("div",{class:"bb-progress-bar-other"},"")])},savenl:t=>({isWhitespaceSensitive:!0,content:t.content}),sh:t=>z("h2",{},t.content),script:(t,e)=>{const n=P(t,e.data.raw);e.data.previewing||e.data.commonGUID||(e.data.commonGUID="post-"+Math.random().toString(36).substring(2,7));const s=e.data.previewing?"preview":e.data.commonGUID,r=kt.includes(n.on?.toLowerCase()||"init")&&n.on?.toLowerCase()||"init",o={id:s,class:n.class||"",on:r,version:n.version||"",content:t.content.join("")};return e.data.bbscripts.push(o),[]},scroll:(t,e)=>{const n=function(t){const e=t&&""!==t.trim()?t.replace(/[^\d.]/g,""):0;return e&&e>=0&&e<=700?e:0===e?0:700}(P(t,e.data.raw)._default);return z("div",{class:"bb-scroll",style:`height: ${n}px`},t.content)},side:t=>{const e=P(t)._default||"left";return z("div",{class:"bb-side","data-side":e},t.content)},size:t=>{const e=function(t){let e,n={valid:!0};const s=/(\d+\.?\d?)(px|rem)?/i.exec(t),r=36,o=8,a=3,i=.2,c=7,l=1;if(s&&(e=s[1])){switch(n.unit=(s[2]||"").toLowerCase(),n.unit){case"px":e>r?e=r:ea?e=a:ec?e=c:e{const n=P(t,e.data.raw)._default;return z("details",{class:"bb-spoiler"},[z("summary",{},"Spoiler"+(n?`: ${n}`:"")),z("div",{class:"bb-spoiler-content"},t.content)])},sub:t=>z("sub",{},t.content),sup:t=>z("sup",{},t.content),tab:(t,e)=>{if(!t.isValid)return B(t,e.data.raw);const n=P(t,e.data.raw),s=n?._default||n?.name||"Tab",r=`tab-${s.replace(/\W/g,"_")}-${Y()}`;return[z("input",{type:"radio",id:r,name:"tab-group-"+t.groupId,class:"bb-tab",checked:t.open}),z("label",{class:"bb-tab-label",for:r,style:n.style},s),z("div",{class:"bb-tab-content"},t.content)]},tabs:(t,e)=>{const n=t.content.filter((t=>u(t)&&"tab"===t.tag)),s=Y();return n.forEach((t=>{t.isValid=!0,t.groupId=s})),n.length?(n[0].open=!0,z("div",{class:"bb-tabs"},n)):B(t,e.data.raw)},...At,b:t=>z("span",{class:"bbcode-b"},t.content),i:t=>t.gen?t:z("span",{class:"bbcode-i"},t.content),u:t=>z("span",{class:"bbcode-u"},t.content),s:t=>z("span",{class:"bbcode-s"},t.content)},St=Object.keys(Ct),_t=function t(e,n=it){const s=t=>{function r(t,r){return n(e,t,r,s.options||{})}return s.options=Object.assign(s.options||{},t),r.options=s.options,r};return s.extend=function(r){return t(r(e,s.options),n)},s}(Ct);function jt(t){return t.replaceAll(J,"").replaceAll(Z,"").replaceAll("\n"+H,"").replaceAll(H+"\n","").replaceAll(H,"")}function Lt(t){return t.replaceAll("\n
    ","").replaceAll("\n
      ","").replaceAll("\n
      ","")}function Ot(t,e){const n=e.hoistMap;for(const[e,s]of Object.entries(n))t=t.replaceAll(e,s);return t}function Et(t,e){if(0===e.styles.length)return t;return'"+t}function Nt(t,e){if(0===e.bbscripts.length)return t;return e.bbscripts.map((t=>``)).join("")+t}function Wt(t,e){const n={};let s=0;const r=(e,s,r,o=!1)=>{const a=Y();return-1!==s?(n[a]=t.substring(e,s),t=t.substring(0,e)+a+t.substring(s)):(n[a]=t.substring(e),t=t.substring(0,e)+a+r),o&&(n[a].startsWith("\n")&&(n[a]=n[a].substring(1)),n[a].endsWith("\n")&&(n[a]=n[a].substring(0,n[a].length-1))),e+a.length+r.length};for(;-1!==(s=R(t,Q,s));){const e=Q.exec(t.substring(s));if(e.groups?.fence){const r=e.groups.fence,o=e.groups.fenceInfo;"\n"===t[s]&&(s+=1);const a=new RegExp("\n"+r+"(\n|$)"),i=R(t,a,s+r.length),c=Y();n[c]=-1!==i?t.substring(s+r.length+o.length,i):t.substring(s+r.length+o.length);const l=`[saveNL]\n${r}${o}${c}\n${r}\n[/saveNL]`;t=t.substring(0,s)+l+(-1!==i?t.substring(i+1+r.length):""),s+=l.length}else if(e.groups?.bbcode){const n=e.groups.bbcode,o=`[/${e.groups.bbcodeTag.toLowerCase()}]`,a=t.toLowerCase().indexOf(o,s+1);s=r(s+n.length,a,o,!0)}else if(e.groups.backtick){const t=e.groups.backtick,n=e.groups.tickStart,o=e.groups.tickEnd;s=r(s+n.length,s+t.length-o.length,o)}}return e.hoistMap=n,[t,e]}function It(t,e){let n=0;for(;-1!==(n=R(t,X,n));){const e=X.exec(t.substring(n))[0],s=`[saveNL]\n${e}\n[/saveNL]`;t=t.substring(0,n)+s+t.substring(n+e.length),n+=s.length}return[t,e]}const Vt={onlyAllowTags:[...St],caseFreeTags:!0,contextFreeTags:["plain","code","icode","class","fa"],enableEscapeTags:!0,onError:t=>{Vt.previewing&&console.warn(t.message,t.lineNumber,t.columnNumber)}},Ut=_t();t.RpNBBCode=(t,e)=>{const n=[Ut];e.preserveWhitespace&&n.push((t=>rt(t))),n.push((t=>nt(t)),at);const[s,r]=function(t){let e={};const n=[Wt,It];for(const s of n)[t,e]=s(t,e);return[t,e]}(t);return function(t){const e="function"==typeof t?[t]:t||[],n=()=>"";return{process(t,s){const r=s||{skipParse:!1,parser:W,render:n,data:null},o=r.parser||W,a=r.render,i=r.data||null;if("function"!=typeof o)throw new Error("C1");const c=r.skipParse&&Array.isArray(t)?t:o(t,r);let l=r.skipParse&&Array.isArray(t)?q(t||[],r):q(c,r);for(let t=0;te(n,s,t)),n)}function f(t){return u(t)&&Array.isArray(t.content)?t.content.reduce(((t,e)=>t+f(e)),0):d(t)?String(t).length:0}function p(t){return t.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'").replace(/(javascript|data|vbscript):/gi,"$1%3A")}function b(t,e){switch(typeof e){case"boolean":return e?`${t}`:"";case"number":return`${t}="${e}"`;case"string":return`${t}="${p(e)}"`;case"object":return`${t}="${p(JSON.stringify(e))}"`;default:return""}}function m(t){return null==t?"":h(t,((t,e,n)=>[...t,b(e,n[e])]),[""]).join(" ")}const w=(t,e)=>{const n=h(e||{},((t,e,n)=>n[e]===e?n[e]:null),null);if(n){const s=b(t,n),r={...e};delete r[String(n)];return`${s}${m(r)}`}return`${t}${m(e)}`};class y{attr(t,e){return void 0!==e&&(this.attrs[t]=e),this.attrs[t]}append(t){return function(t,e){Array.isArray(t.content)&&t.content.push(e)}(this,t)}setStart(t){this.start=t}setEnd(t){this.end=t}get length(){return f(this)}toTagStart({openTag:t=a,closeTag:e=i}={}){return`${t}${w(String(this.tag),this.attrs)}${e}`}toTagEnd({openTag:t=a,closeTag:e=i}={}){return`${t}${c}${this.tag}${e}`}toTagNode(){const t=new y(String(this.tag).toLowerCase(),this.attrs,this.content);return this.start&&t.setStart(this.start),this.end&&t.setEnd(this.end),t}toString({openTag:t=a,closeTag:e=i}={}){const n=this.content?((t,e,n)=>{const s=t=>u(t)?t.toString({openTag:e,closeTag:n}):String(t);return Array.isArray(t)?t.reduce(((t,e)=>null!==e?t+s(e):t),""):t?s(t):null})(this.content,t,e):"",s=this.toTagStart({openTag:t,closeTag:e});return null===this.content||Array.isArray(this.content)&&0===this.content.length?s:`${s}${n}${this.toTagEnd({openTag:t,closeTag:e})}`}static create(t,e={},n=null,s){const r=new y(t,e,n);return s&&r.setStart(s),r}static isOf(t,e){return t.tag===e}constructor(t,e,n){this.tag=t,this.attrs=e,this.content=n}}const v="t",x=t=>t&&void 0!==t.v?t.v:"",$=t=>x(t).charCodeAt(0)===c.charCodeAt(0);class T{get type(){return this[v]}isEmpty(){return 0===this[v]||isNaN(this[v])}isText(){return!(!(t=this)||void 0===t[v]||5!==t[v]&&6!==t[v]&&1!==t[v]);var t}isTag(){return!(!(t=this)||void 0===t[v])&&2===t[v];var t}isAttrName(){return!(!(t=this)||void 0===t[v])&&3===t[v];var t}isAttrValue(){return!(!(t=this)||void 0===t[v])&&4===t[v];var t}isStart(){return!$(this)}isEnd(){return $(this)}getName(){return(t=>{const e=x(t);return $(t)?e.slice(1):e})(this)}getValue(){return x(this)}getLine(){return(t=this)&&t.l||0;var t}getColumn(){return(t=this)&&t.r||0;var t}getStart(){return(t=this)&&t.s||0;var t}getEnd(){return(t=this)&&t.e||0;var t}toString({openTag:t=a,closeTag:e=i}={}){return((t,e=a,n=i)=>{let s=e;return s+=x(t),s+=n,s})(this,t,e)}constructor(t,e,n=0,s=0,r=0,o=0){this.l=n,this.r=s,this[v]=t||0,this.v=String(e),this.s=r,this.e=o}}class k{skip(t=1,e){this.c.pos+=t,this.o&&this.o.onSkip&&!e&&this.o.onSkip()}hasNext(){return this.c.len>this.c.pos}getCurr(){return void 0===this.s[this.c.pos]?"":this.s[this.c.pos]}getPos(){return this.c.pos}getLength(){return this.c.len}getRest(){return this.s.substring(this.c.pos)}getNext(){const t=this.c.pos+1;return t<=this.s.length-1?this.s[t]:null}getPrev(){const t=this.c.pos-1;return void 0===this.s[t]?null:this.s[t]}isLast(){return this.c.pos===this.c.len}includes(t){return this.s.indexOf(t,this.c.pos)>=0}grabWhile(t,e){let n=0;if(this.hasNext())for(n=this.c.pos;this.hasNext()&&t(this.getCurr());)this.skip(1,e);return this.s.substring(n,this.c.pos)}grabN(t=0){return this.s.substring(this.c.pos,this.c.pos+t)}substrUntilChar(t){const{pos:e}=this.c,n=this.s.indexOf(t,e);return n>=0?this.s.substring(e,n):""}constructor(t,e={}){this.s=t,this.c={pos:0,len:t.length},this.o=e}}const A=(t,e)=>new k(t,e);function C(t,e,n=0,s=0,r=0,o=0){return new T(t,e,n,s,r,o)}const S=[o,n],_=[s,o,n],j=t=>S.indexOf(t)>=0,L=t=>t===l,E=t=>((t,e)=>{for(;t.charAt(0)===e;)t=t.substring(1);for(;t.charAt(t.length-1)===e;)t=t.substring(0,t.length-1);return t})(t,r).replace(l+r,r);function W(t,u={}){let d=0,g=0,h=0,f=-1,p=0,b=0,m="";const w=new Array(Math.floor(t.length)),y=u.openTag||a,v=u.closeTag||i,x=!!u.enableEscapeTags,$=(u.contextFreeTags||[]).filter(Boolean).map((t=>t.toLowerCase())),T=u.caseFreeTags||!1,k=new Map,S=u.onToken||(()=>{}),W=[v,y,r,l,o,n,s,e,"!"],N=[y,o,n,e],O=t=>-1===N.indexOf(t),I=()=>{h++},V=(t,e)=>{""!==m&&e&&(m=""),""===m&&$.includes(t.toLowerCase())&&(m=t)},B=A(t,{onSkip:I});function z(t,e,n,s){const r=C(t,e,d,g,n,s);S(r),g=h,f+=1,w[f]=r}function U(t,e,n){if(1===b){const e=t=>!(t===s||j(t)),n=t.grabWhile(e),r=t.isLast(),o=t.getCurr()!==s;return t.skip(),r||o?z(4,E(n)):z(3,n),r?0:o?1:2}if(2===b){let n=!1;const o=o=>{const a=o===r,i=t.getPrev(),c=t.getNext(),u=i===l,d=c===s,g=j(o),h=c&&j(c);return!(!n||!(t=>_.indexOf(t)>=0)(o))||!!(!a||u||(n=!n,n||d||h))&&(!!e||!g)},a=t.grabWhile(o);return t.skip(),z(4,E(a)),t.getPrev()===r&&g++,t.isLast()?0:1}const o=n+t.getPos()-1,a=t.grabWhile((e=>!(e===s||j(e)||t.isLast())));if(z(2,a,o,n+t.getLength()+1),V(a),t.skip(),g++,e)return 2;return t.includes(s)?1:2}function q(){const t=B.getCurr(),e=B.getNext();B.skip();const n=B.substrUntilChar(v),r=0===n.length||n.indexOf(y)>=0;if(e&&(o=e,W.indexOf(o)>=0)||r||B.isLast())return z(1,t),0;var o;const a=-1===n.indexOf(s),i=n[0]===c;if(a||i){const t=B.getPos()-1,e=B.grabWhile((t=>t!==v)),n=t+e.length+2;return B.skip(),z(2,e,t,n),V(e,i),0}return 2}function D(){const t=B.getPos(),e=B.grabWhile((t=>t!==v),!0),n=A(e,{onSkip:I}),s=n.includes(o);for(b=0;n.hasNext();)b=U(n,!s,t);return B.skip(),0}function G(){if(B.getCurr()===e)return z(6,B.getCurr()),B.skip(),h=0,g=0,d++,0;if(j(B.getCurr())){return z(5,B.grabWhile(j)),0}if(B.getCurr()===y){if(m){const t=y.length+1+m.length,e=`${y}${c}${m}`;if(B.grabN(t)===e)return 1}else if(B.includes(v))return 1;return z(1,B.getCurr()),B.skip(),g++,0}if(x){if(L(B.getCurr())){const t=B.getCurr(),e=B.getNext();return B.skip(),e&&(t=>t===y||t===v||t===l)(e)?(B.skip(),z(1,e),0):(z(1,t),0)}const t=t=>O(t)&&!L(t);return z(1,B.grabWhile(t)),0}return z(1,B.grabWhile(O)),0}return{tokenize:function(){for(p=0;B.hasNext();)switch(p){case 1:p=q();break;case 2:p=D();break;default:p=G()}return w.length=f+1,w},isTokenNested:function(e){const n=y+c+e;if(k.has(n))return!!k.get(n);{const e=T?t.toLowerCase().indexOf(n.toLowerCase())>-1:t.indexOf(n)>-1;return k.set(n,e),e}}}}class N{last(){return Array.isArray(this.n)&&this.n.length>0&&void 0!==this.n[this.n.length-1]?this.n[this.n.length-1]:null}flush(){return!!this.n.length&&this.n.pop()}push(t){this.n.push(t)}toArray(){return this.n}constructor(){this.n=[]}}const O=()=>new N;function I(t,e={}){const n=e,s=n.openTag||a,r=n.closeTag||i,o=(n.onlyAllowTags||[]).filter(Boolean).map((t=>t.toLowerCase())),c=n.caseFreeTags||!1;let l=null;const d=O(),g=O(),h=O(),f=O(),p=new Set;function b(t){return Boolean(p.has(c?t.toLowerCase():t))}function m(){h.flush()&&f.flush()}function w(){const t=g.last();return t&&u(t)?t.content:d.toArray()}function v(t,e,n=!0){Array.isArray(t)&&void 0!==e&&(t.push(e.toTagStart({openTag:s,closeTag:r})),Array.isArray(e.content)&&e.content.length&&(e.content.forEach((e=>{t.push(e)})),n&&t.push(e.toTagEnd({openTag:s,closeTag:r}))))}function x(t,e){var n;Array.isArray(t)&&void 0!==e&&(u(e)?(n=e.tag,!o.length||o.indexOf(n.toLowerCase())>=0?t.push(e.toTagNode()):v(t,e)):t.push(e))}function $(t){m();const e=y.create(t.getValue(),{},[],{from:t.getStart(),to:t.getEnd()}),n=function(t){const e=t.getValue(),n=c?e.toLowerCase():e,{isTokenNested:s}=l||{};return!p.has(n)&&s&&s(n)?(p.add(n),!0):p.has(n)}(t);if(h.push(e),n)g.push(e);else{x(w(),e)}}function T(t){t.isStart()&&$(t),t.isEnd()&&function(t){const e=t.getValue().slice(1),o=g.flush();if(m(),o){const e=w();u(o)&&o.setEnd({from:t.getStart(),to:t.getEnd()}),x(e,o)}else if(b(e)){if("function"==typeof n.onError){const e=t.getValue(),s=t.getLine(),r=t.getColumn();n.onError({tagName:e,lineNumber:s,columnNumber:r})}}else x(w(),t.toString({openTag:s,closeTag:r}))}(t)}const k=e.createTokenizer?e.createTokenizer:W;l=k(t,{onToken:function(t){t.isTag()?T(t):function(t){const e=h.last(),n=t.getValue(),o=b(t.toString()),a=w();if(null!==e)if(t.isAttrName()){f.push(n);const t=f.last();t&&e.attr(t,"")}else if(t.isAttrValue()){const t=f.last();t?(e.attr(t,n),f.flush()):e.attr(n,n)}else t.isText()?o?e.append(n):x(a,n):t.isTag()&&x(a,t.toString({openTag:s,closeTag:r}));else t.isText()?x(a,n):t.isTag()&&x(a,t.toString({openTag:s,closeTag:r}))}(t)},openTag:s,closeTag:r,onlyAllowTags:n.onlyAllowTags,contextFreeTags:n.contextFreeTags,caseFreeTags:n.caseFreeTags,enableEscapeTags:n.enableEscapeTags}),l.tokenize();const A=g.flush();return null!==A&&A&&u(A)&&b(A.tag)&&v(w(),A,!1),d.toArray()}const V=t=>"object"==typeof t&&null!==t;function B(t,e){const n=t;if(Array.isArray(n))for(let t=0;t[].some.call(e,(e=>z(t,e))))):!(!V(t)||!V(e))&&Object.keys(t).every((n=>{const s=e[n],r=t[n];return V(r)&&V(s)?z(r,s):"boolean"==typeof r?r!==(null===s):s===r})):t===e)}function U(t,e){const n=t;return n.messages=[...n.messages||[]],n.options={...e,...n.options},n.walk=function(t){return B(this,t)},n.match=function(t,e){return function(t,e,n){return Array.isArray(e)?B(t,(t=>{for(let s=0;sz(e,t)?n(t):t))}(this,t,e)},n}function q(t,e){const{stripTags:n=!1}=e||{};if(null==t)return"";if("string"==typeof t||"number"==typeof t)return String(t);if(Array.isArray(t))return D(t,e);if(u(t)){if(n)return D(t.content,e);const s=m(t.attrs);return null===t.content?"<"+t.tag+s+"/>":"<"+t.tag+s+">"+D(t.content,e)+""}return""}function D(t,e){return t&&Array.isArray(t)?t.reduce(((t,n)=>t+q(n,e)),""):t?q(t,e):""}const G=(t,e,n=[])=>({tag:t,attrs:e,content:n,gen:!0}),P=(t,e)=>{if(e&&t.start){const n=e.substring(t.start.from,t.start.to),s=/^\[[^\s=\]]+\s*=/.exec(n);if(s){let t=n.slice(s[0].length,-1).trim();return(t.startsWith('"')&&t.endsWith('"')||t.startsWith("'")&&t.endsWith("'"))&&(t=t.slice(1,-1)),{_default:t}}}const n=Object.keys(t.attrs).join(" "),s=Object.values(t.attrs).join(" ");return n!==s||e&&t.start?t.attrs:{_default:s}},M=(t,e)=>{if(t.start)return e.substring(t.start.from,t.start.to);if(!t.attrs)return`[${t.tag}]`;const n=P(t,e);return n._default?`[${t.tag}=${n._default}]`:t.toTagStart()},F=(t,e)=>t.end?e.substring(t.end.from,t.end.to):t.toTagEnd(),R=(t,e)=>t.end?[M(t,e),...t.content,F(t,e)]:M(t,e),H=(t,e,n)=>{const s=t.substring(n||0).search(e);return s>=0?s+(n||0):s},J="\x3c!-- bbcode injected newlines --\x3e\n\n",Z="\n\n\x3c!-- bbcode pre injected newlines --\x3e",K="\x3c!-- bbcode injected newlines --\x3e",Q=new RegExp(`^${/(http|ftp|https|upload):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])/.source}|${/\!?\[.*\]\((http|ftp|https|upload):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])\)/.source}$`),X=/((\n|^)(?```+|~~~+)(?.*\n))|(?\[(?i?code|plain)(=[^\]\r\n]*)?\])|(?(?`+)(?!`)([\s\S]*?)(?(?)(?!`))/im,Y=/^(\|[^\n]+\|\r?\n)((?:\| ?:?[-]+:? ?)+\|)(\n(?:\|[^\n]+\|\r?\n?)*)?$/m;function tt(){let t=(new Date).getTime();return window.performance&&"function"==typeof window.performance.now&&(t+=performance.now()),"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,(function(e){const n=(t+16*Math.random())%16|0;return t=Math.floor(t/16),("x"===e?n:3&n|8).toString(16)}))}const et=t=>"object"==typeof t,nt=t=>"string"==typeof t,st=(t,e=!1,n=!1)=>{let s=t;if(nt(s)&&n&&(s=s.replace(/^([ \t]*)(#{1,6})(?=[ \t\r\n]|$)/gm,((t,e,n)=>`${e}#${n.slice(1)}`))),Array.isArray(s)){rt(s),s.some(nt)&&(s.unshift(J),s.push(J));for(let t=0;t{let e=t.findLastIndex((t=>nt(t)&&!g(t)))+1;for(let n=e-1;n>=0;n--)nt(t[n])&&!g(t[n])||(g(t[n])||et(t[n]))&&(n!==e-1&&t.splice(n+1,e-n-1,t.slice(n+1,e).join("")),e=n);0!==e&&t.splice(0,e,t.slice(0,e).join(""))},ot=t=>{const e=t;if(Array.isArray(e))for(let t=0;t1&&" "===e[0]){let t=e.length;return[String.fromCharCode(160).repeat(t)]}return e},at=/\n{2,}/gm,it=t=>t.walk((t=>(u(t)&&t.attrs&&Object.keys(t.attrs).forEach((e=>{"string"==typeof t.attrs[e]&&(t.attrs[e]=t.attrs[e].replace(at,"\n"))})),t)));function ct(t,e,n,s){return e.walk((e=>{if(u(e)){const r=e.tag,o=t[r];if("function"==typeof o)return o(e,n,s)}return e}))}const lt=Symbol("slide-title-open"),ut=Symbol("slide-title-close"),dt=Symbol("slide-close"),gt=/(?\{slide=)|(?\})|(?\{\/slide\})/i;function ht(t){switch(t){case lt:return"{slide=";case ut:return"}";case dt:return"{/slide}";default:return t}}const ft={accordion:(t,e)=>{const n=tt(),s=function(t){t=[...t];const e=[];for(;t.length>0;){const n=t[0];if(u(n)){e.push(t.shift());continue}const s=H(n,gt);if(-1===s){e.push(t.shift());continue}const r=n.match(gt),o=n.slice(0,s),a=n.slice(s+r[0].length);o.length&&e.push(o),r.groups.slideTitleOpen&&e.push(lt),r.groups.slideTitleClose&&e.push(ut),r.groups.slideClose&&e.push(dt),a.length?t[0]=a:t.shift()}return e}(t.content),r=function(t){const e=[];let n=null,s=null;for(const r of t)if(r===lt&&null===s)n=y.create("slide"),n.content=[],n.customTitle=[],s=lt;else{if(r===ut&&s===lt){s=ut;continue}r===dt&&n&&s===ut?(e.push(n),n=null,s=null):n?s===lt?n.customTitle.push(ht(r)):n.content.push(ht(r)):e.push(ht(r))}return e}(s),o=r.filter((t=>u(t)&&"slide"===t.tag)).map((t=>(t.isValid=!0,t.groupId=n,t)));if(!o.length)return[M(t,e.data.raw),...t.content,F(t,e.data.raw)];const a=P(t,e.data.raw);if(a._default){const t=a._default.split("|").map((t=>t.trim())),e=t.filter((t=>["bright","bcenter","bleft","fleft","fright"].includes(t))).pop();e&&(a.align??=e),(t.some((t=>t.endsWith("px")))||t.some((t=>t.endsWith("%"))))&&(a.width??=t.find((t=>t.endsWith("px")||t.endsWith("%"))))}let i=a.align?.toLowerCase()||"",c="";return(a.width?.endsWith("px")||a.width?.endsWith("%"))&&(c=`width: ${a.width};`),G("div",{class:"bb-accordion "+i,"data-group-id":n,style:c},o)},slide:(t,e)=>{if(!t.isValid)return[M(t,e.data.raw),...t.content,F(t,e.data.raw)];const n=P(t,e.data.raw);let s=[n.title||n._default||"Slide"],r=!!n.open||!1,o=n.left?"left":n.right?"right":n.center?"center":"left";if(t.customTitle?.length){s=t.customTitle;const e=s.filter((t=>"string"==typeof t)).join("").toLowerCase().split("|").map((t=>t.trim()));e.includes("open")&&(r=!0),e.includes("right")&&(o="right"),e.includes("center")&&(o="center"),e.includes("left")&&(o="left"),s=s.map((t=>(d(t)&&(t=t.replace(/\|(open|right|center|left)/gi,"")),t)))}return[G("details",{class:"bb-slide",open:r},[G("summary",{class:"bb-slide-title",style:`text-align: ${o}; ${n.style||""}`},s),G("div",{class:"bb-slide-content"},t.content)])]}},pt={left:t=>G("div",{class:"bb-left"},t.content),center:t=>G("div",{class:"bb-center"},t.content),right:t=>G("div",{class:"bb-right"},t.content)},bt={a:(t,e)=>{const n=P(t,e.data.raw)._default||"";return G("a",{id:`user-anchor-${n.trim()}`,name:`user-anchor-${n.trim()}`},t.content)},goto:(t,e)=>{const n=P(t,e.data.raw)._default||"";return G("a",{href:`#user-anchor-${n.trim()}`},t.content)}},mt=["arial","book antiqua","courier new","georgia","tahoma","times new roman","trebuchet ms","verdana"],wt={thin:"100",extralight:"200",light:"300",regular:"400",medium:"500",semibold:"600",bold:"700",extrabold:"800",black:"900"},yt=["ital","opsz","slnt","wdth","wght"],vt=/(?[a-zA-Z]*)?\s?(?[0-9]*)?\s?(?italic)?/;const xt=G("div",{class:"bb-email-header"},""),$t=G("div",{class:"bb-email-footer"},G("div",{class:"bb-email-button"},"")),Tt={row:t=>G("div",{class:"bb-row"},t.content),column:(t,e)=>{const n=P(t,e.data.raw)._default||"8",s=n.startsWith("span")?`column-width-${n}`:`column-width-span${n}`;return G("div",{class:"bb-column","data-span":s},t.content)}},kt=["init","click","change","input","dblclick","mouseenter","mouseleave","scroll"];const At=/^(?:(?:none|dotted|dark)(?:-zebra2?)?|zebra2?)$/,Ct=new Set(["blue","gray","grey"]),St=new Set(["tr"]),_t=new Set(["td","th","tf"]);function jt(t,e){return t.some((t=>"string"==typeof t?t.trim().length>0:!u(t)||!e.has(t.tag.toLowerCase())))}function Lt(t,e){return String(P(t,e.data.raw)._default||"").trim().toLowerCase()}function Et(t,e){if(t.gen)return t;if(!t.isBBCodeTableCell)return R(t,e.data.raw);const n=t.tag.toLowerCase(),s="tf"===n?{class:"bb-table-footer"}:{},r=Lt(t,e);return/^\d{1,4}$/.test(r)&&Number(r)>0&&Number(r)<=1e3&&(s.colspan=String(Number(r))),G("th"===n?"th":"td",s,t.content)}const Wt={table:function(t,e){if(t.gen)return t;const n=t.content.filter((t=>u(t)&&"tr"===t.tag.toLowerCase()));if(!n.length||jt(t.content,St)||n.some((t=>jt(t.content,_t))))return R(t,e.data.raw);n.forEach((t=>t.isBBCodeTableRow=!0));const s=Lt(t,e),r={class:"bb-table"};return At.test(s)&&(r["data-bb-table-style"]=s),G("table",r,n)},tr:function(t,e){if(t.gen)return t;if(!t.isBBCodeTableRow)return R(t,e.data.raw);const n=t.content.filter((t=>u(t)&&_t.has(t.tag.toLowerCase())));n.forEach((t=>t.isBBCodeTableCell=!0));const s=Lt(t,e),r={};return Ct.has(s)&&(r["data-bb-table-row"]="grey"===s?"gray":s),G("tr",r,n)},td:Et,th:Et,tf:Et},Nt=["me","them","right","left"],Ot={textmessage:(t,e)=>{const n=t.content.filter((t=>u(t)&&"message"===t.tag));if(n.forEach((t=>{t.isValid=!0})),!n.length)return R(t,e.data.raw);const s=P(t,e.data.raw)._default||"Recipient",r=""!==s.trim()?s:"Recipient";return G("div",{class:"bb-textmessage"},[G("div",{class:"bb-textmessage-name"},r),G("div",{class:"bb-textmessage-overflow"},[G("div",{class:"bb-textmessage-content"},t.content)])])},message:(t,e)=>{if(!t.isValid)return R(t,e.data.raw);let n=P(t,e?.data?.raw)?._default?.toLowerCase();Nt.includes(n)&&"right"!==n||(n="me"),"left"===n&&(n="them");return G("div",{class:"me"===n?"bb-message-me":"bb-message-them"},[G("div",{class:"bb-message-content"},t.content)])}},It={...ft,...pt,...Wt,...bt,animation:(t,e)=>{e.data.previewing||e.data.commonGUID||(e.data.commonGUID="post-"+Math.random().toString(36).substring(2,7));const n=e.data.previewing?"preview":e.data.commonGUID,s=P(t,e.data.raw)?._default||"",r=t.content.filter((t=>u(t)&&"keyframe"===t.tag)).map((t=>{t.isValid=!0;const n=P(t,e.data.raw)._default||"";t.ident=n+(n.match(/^\d+$/)?"%":"");const s=t.content.filter(d).join("").replaceAll(/[\[\]\{\}]/g,"");return t.formatted=`${t.ident}{ ${s} }`,t})),o=`@keyframes ${n}${s} { ${r.map((t=>t.formatted)).join("\n")} }`;return e.data.styles.push(o),[]},bg:(t,e)=>{const n=P(t,e.data.raw)._default;return G("div",{style:`background-color: ${n};`,class:"bb-background"},t.content)},block:(t,e)=>{const n="block",s=(P(t,e.data.raw)._default||n).toLowerCase(),r=["block","dice","dice10","setting","warning","storyteller","announcement","important","question","encounter","information","character","treasure"].includes(s)?s:n;return G("table",{class:"bb-block","data-bb-block":r},[G("tbody",[G("tr",[G("td",{class:"bb-block-icon"}),G("td",{class:"bb-block-content"},t.content)])])])},blockquote:(t,e)=>{const n=P(t,e.data.raw)._default||"";return G("div",{class:"bb-blockquote"},[G("div",{class:"bb-blockquote-left"}),G("div",{class:"bb-blockquote-content"},[t.content,G("div",{class:"bb-blockquote-speaker"},""!==n?`- ${n}`:"")]),G("div",{class:"bb-blockquote-right"})])},border:(t,e)=>{const n=P(t,e.data.raw)._default;return G("div",{style:`border: ${n};`,class:"bb-border"},t.content)},br:()=>G("br",{},null),centerblock:(t,e)=>{const n=P(t,e.data.raw)._default||"50";return G("div",{style:`margin: 0 auto; width: ${n}%`},t.content)},check:(t,e)=>{const n=P(t,e.data.raw)._default||"dot";return G("div",{class:"bb-check","data-type":n},t.content)},class:(t,e)=>{const n=P(t),s=n.name||n._default;e.data.previewing||e.data.commonGUID||(e.data.commonGUID="post-"+Math.random().toString(36).substring(2,7));const r=e.data.previewing?"preview":e.data.commonGUID,o=s+"__"+r,a=t.content.filter(d).map((t=>t.replaceAll("{post_id}",r).replaceAll(/[\[\]\{\}]/g,"")));let i="";const c=[];return["hover","focus","active","focus-within","focus-visible"].includes(n.state?.toLowerCase())&&(i=":"+n.state.toLowerCase()),n.selector&&(i=n.selector.replace(/[,{}\\\n]/g,"")),n.minWidth?.match(/^[0-9]+[a-z]+$/)&&c.push(`(min-width: ${n.minWidth})`),n.maxWidth?.match(/^[0-9]+[a-z]+$/)&&c.push(`(max-width: ${n.maxWidth})`),a.unshift(`.${o}${i} {`),a.push("}"),c.length&&(a.unshift(`@media ${c.join(" and ")} {`),a.push("}")),e.data.styles.push(a.join("")),[]},code:t=>({isWhitespaceSensitive:!0,content:["```"+(P(t)._default||"bbcode")+"\n",t.content,"\n```\n"]}),color:t=>{const e=P(t)._default||"";return""===e.trim()?t.content:G("span",{style:`color: ${e}`},t.content)},comment:t=>G("span",{class:"hidden"},t.content),div:(t,e)=>{if(t.gen)return t;const n=P(t,e.data.raw),s=n.style||n._default,r=n.class;if(!r?.trim())return G("div",{"data-bbcode-div":"true",style:s},t.content);e.data.previewing||e.data.commonGUID||(e.data.commonGUID="post-"+Math.random().toString(36).substring(2,7));const o=e.data.previewing?"preview":e.data.commonGUID,a=r.split(" ").map((t=>t+"__"+o)).join(" ");return G("div",{"data-bbcode-div":"true",class:a,style:s},t.content)},divide:t=>{const e=(P(t)._default||"").toLowerCase();return G("span",{class:"bb-divide","data-type":e},t.content)},fieldset:(t,e)=>{const n=P(t,e.data.raw)._default||"";return G("fieldset",{class:"bb-fieldset"},[G("legend",{class:"bb-fieldset-legend"},n),G("div",{class:"bb-fieldset"},t.content)])},fa:t=>{const e=t.attrs;let n=e.style||"";return n+=e["primary-color"]?`--fa-primary-color: ${e["primary-color"]};`:"",n+=e["secondary-color"]?`--fa-secondary-color: ${e["secondary-color"]};`:"",n+=e["primary-opacity"]?`--fa-primary-opacity: ${e["primary-opacity"]};`:"",n+=e["secondary-opacity"]?`--fa-secondary-opacity: ${e["secondary-opacity"]};`:"",n+=e["rotate-angle"]?`--fa-rotate-angle: ${e["rotate-angle"]};`:"",G("i",{"data-bbcode-fa":null},[G("i",{class:(t.content||[]).join(""),style:n,"data-fa-transform":e["fa-transform"]||""},[])])},font:(t,e)=>{const n=P(t,e.data.raw),s=n?._default||n?.family||n?.name;if(!s||""===s.trim())return t.content;if(mt.includes(s.trim().toLowerCase()))return G("span",{style:`font-family: '${s}'`},t.content);const r=(t=>{let e={ital:0,wght:400};if(t?.style){const n=t.style.trim().toLowerCase(),s=vt.exec(n).groups||{};s?.italic&&(e.ital=1);const r=s.weight;r&&r>=0&&r<=900?e.wght=r:Object.keys(wt).includes(s.named_weight||"")&&(e.wght=wt[s.named_weight]),e={...e,...Object.fromEntries(Object.entries(t).filter((([t])=>yt.includes(t))))}}return e})(n),o=((t,e)=>(t=t.replaceAll(" ","+"),e=Object.keys(e).sort().reduce(((t,n)=>(t[n]=e[n],t)),{}),"https://fonts.googleapis.com/css2?family="+t+":"+Object.keys(e).join(",")+"@"+Object.values(e).join(",")))(s,r);e.data.fonts.add(o);const a=1===r.ital?"italic":"normal",i=Object.entries(r).filter((([t])=>"wght"!==t&&"ital"!==t));let c="";return i.length&&(c="font-variation-settings: "+i.map((([t,e])=>`'${t}' ${e}`)).join(", ")+";"),G("span",{style:`font-family: '${s}'; font-weight: ${r.wght}; font-style: ${a}; ${c}`,"data-font":o},t.content)},h:t=>G("h1",{},t.content),h1:t=>G("h1",{},t.content),h2:t=>G("h2",{},t.content),h3:t=>G("h3",{},t.content),h4:t=>G("h4",{},t.content),h5:t=>G("h5",{},t.content),h6:t=>G("h6",{},t.content),heightrestrict:t=>{const e=function(t){const e=t&&""!==t.trim()?t.replace(/[^\d.]/g,""):0;return e&&e>=0&&e<=700?e:0===e?0:700}(P(t)._default).toString();return G("div","0"===e?{class:"bb-height-restrict"}:{class:"bb-height-restrict",style:`height: ${e}px;`},t.content)},highlight:t=>G("span",{class:"bb-highlight"},t.content),icode:t=>({isWhitespaceSensitive:!0,content:["`",t.content,"`"]}),imagefloat:t=>{const e=P(t)._default||"";return G("div",{class:`bb-float-${e}`},t.content)},inlinespoiler:t=>G("span",{class:"bb-inline-spoiler"},t.content),justify:t=>G("div",{class:"bb-justify"},t.content),keyframe:(t,e)=>t.isValid?[]:[M(t,e.data.raw),...t.content,F(t,e.data.raw)],list:(t,e)=>{const n=[];let s=[];function r(){for(;s.length&&d(s[0])&&!s[0].trim();)s.shift();for(;s.length&&d(s[s.length-1])&&!s[s.length-1].trim();)s.pop();if(s.length){d(s[0])&&(s[0]=s[0].trimStart());const t=s.length-1;d(s[t])&&(s[t]=s[t].trimEnd()),n.push(G("li",{},s))}s=[]}for(const e of t.content){if(!d(e)){s.push(e);continue}const t=e.split("[*]");s.push(t[0]);for(const e of t.slice(1))r(),s.push(e)}r();let o=String(P(t,e.data.raw)._default||"").trim();return(o.startsWith("'")&&o.endsWith("'")||o.startsWith('"')&&o.endsWith('"'))&&(o=o.slice(1,-1)),G("1"===o?"ol":"ul",{},n)},mail:t=>{const e=t.attrs;let n={mailOption:(e.type||"send").toLowerCase(),person:e.person||"Unknown",subject:e.subject||"Empty"};return G("div",{class:"bb-email","data-bb-email":n.mailOption},[xt,(o=n.person,G("div",{class:"bb-email-address"},o)),(r=n.subject,G("div",{class:"bb-email-subject"},r)),(s=t.content,G("div",{class:"bb-email-content"},s)),$t]);var s,r,o},newspaper:t=>G("div",{class:"bb-newspaper"},t.content),nobr:t=>({disableLineBreakConversion:!0,preserveHeadingText:!0,content:t.content}),note:t=>G("div",{class:"bb-note"},[G("div",{class:"bb-note-tape"},""),G("div",{class:"bb-note-content"},[t.content,G("div",{class:"bb-note-footer"},"")])]),ooc:t=>G("div",{class:"bb-ooc"},t.content),pindent:t=>G("span",{class:"bb-pindent"},t.content),plain:t=>t.content,print:t=>{const e="print",n=(P(t)._default||e).toLowerCase(),s=["print","line","graph","parchment"].includes(n)?n:e;return G("div",{class:s===e?"bb-print":`bb-print-${s}`},t.content)},progress:t=>{const e=P(t)._default;return G("div",{class:"bb-progress"},[G("div",{class:"bb-progress-text"},t.content),G("div",{class:"bb-progress-bar",style:`width: calc(${e}% - 6px)`},""),G("div",{class:"bb-progress-bar-other"},"")])},quote:(t,e)=>{const n=P(t,e.data.raw);return"\n"===t.content[0]&&t.content.shift(),[`\n[${t.tag}="${n._default}"]\n\n`,...t.content,"\n\n[/quote]\n"]},...Tt,thinprogress:(t,e)=>{const n=P(t,e.data.raw)._default;return G("div",{class:"bb-progress-thin"},[G("div",{class:"bb-progress-text"},t.content),G("div",{class:"bb-progress-bar",style:`width: calc(${n}% - 6px)`},""),G("div",{class:"bb-progress-bar-other"},"")])},savenl:t=>({isWhitespaceSensitive:!0,content:t.content}),sh:t=>G("h2",{},t.content),script:(t,e)=>{const n=P(t,e.data.raw);e.data.previewing||e.data.commonGUID||(e.data.commonGUID="post-"+Math.random().toString(36).substring(2,7));const s=e.data.previewing?"preview":e.data.commonGUID,r=kt.includes(n.on?.toLowerCase()||"init")&&n.on?.toLowerCase()||"init",o={id:s,class:n.class||"",on:r,version:n.version||"",content:t.content.join("")};return e.data.bbscripts.push(o),[]},scroll:(t,e)=>{const n=function(t){const e=t&&""!==t.trim()?t.replace(/[^\d.]/g,""):0;return e&&e>=0&&e<=700?e:0===e?0:700}(P(t,e.data.raw)._default);return G("div",{class:"bb-scroll",style:`height: ${n}px`},t.content)},side:t=>{const e=P(t)._default||"left";return G("div",{class:"bb-side","data-side":e},t.content)},size:t=>{const e=function(t){let e,n={valid:!0};const s=/(\d+\.?\d?)(px|rem)?/i.exec(t),r=36,o=8,a=3,i=.2,c=7,l=1;if(s&&(e=s[1])){switch(n.unit=(s[2]||"").toLowerCase(),n.unit){case"px":e>r?e=r:ea?e=a:ec?e=c:e{const n=P(t,e.data.raw)._default;return G("details",{class:"bb-spoiler"},[G("summary",{},"Spoiler"+(n?`: ${n}`:"")),G("div",{class:"bb-spoiler-content"},t.content)])},sub:t=>G("sub",{},t.content),sup:t=>G("sup",{},t.content),tab:(t,e)=>{if(!t.isValid)return R(t,e.data.raw);const n=P(t,e.data.raw),s=n?._default||n?.name||"Tab",r=`tab-${s.replace(/\W/g,"_")}-${tt()}`;return[G("input",{type:"radio",id:r,name:"tab-group-"+t.groupId,class:"bb-tab",checked:t.open}),G("label",{class:"bb-tab-label",for:r,style:n.style},s),G("div",{class:"bb-tab-content"},t.content)]},tabs:(t,e)=>{const n=t.content.filter((t=>u(t)&&"tab"===t.tag)),s=tt();return n.forEach((t=>{t.isValid=!0,t.groupId=s})),n.length?(n[0].open=!0,G("div",{class:"bb-tabs"},n)):R(t,e.data.raw)},...Ot,b:t=>G("span",{class:"bbcode-b"},t.content),i:t=>t.gen?t:G("span",{class:"bbcode-i"},t.content),u:t=>G("span",{class:"bbcode-u"},t.content),s:t=>G("span",{class:"bbcode-s"},t.content)},Vt=Object.keys(It),Bt=function t(e,n=ct){const s=t=>{function r(t,r){return n(e,t,r,s.options||{})}return s.options=Object.assign(s.options||{},t),r.options=s.options,r};return s.extend=function(r){return t(r(e,s.options),n)},s}(It);function zt(t){return t.replaceAll(J,"").replaceAll(Z,"").replaceAll("\n"+K,"").replaceAll(K+"\n","").replaceAll(K,"")}function Ut(t){return t.replaceAll("
\n
    ","").replaceAll("\n
      ","").replaceAll("\n
      ","")}function qt(t,e){const n=e.hoistMap;for(const[e,s]of Object.entries(n)){const n=s.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">");t=t.replaceAll(e,n)}return t}function Dt(t,e){if(0===e.styles.length)return t;return'"+t}function Gt(t,e){if(0===e.bbscripts.length)return t;return e.bbscripts.map((t=>``)).join("")+t}function Pt(t,e){const n={};let s=0;const r=(e,s,r,o=!1)=>{const a=tt();return-1!==s?(n[a]=t.substring(e,s),t=t.substring(0,e)+a+t.substring(s)):(n[a]=t.substring(e),t=t.substring(0,e)+a+r),o&&(n[a].startsWith("\n")&&(n[a]=n[a].substring(1)),n[a].endsWith("\n")&&(n[a]=n[a].substring(0,n[a].length-1))),e+a.length+r.length};for(;-1!==(s=H(t,X,s));){const e=X.exec(t.substring(s));if(e.groups?.fence){const r=e.groups.fence,o=e.groups.fenceInfo;"\n"===t[s]&&(s+=1);const a=new RegExp("\n"+r+"(\n|$)"),i=H(t,a,s+r.length),c=tt();n[c]=-1!==i?t.substring(s+r.length+o.length,i):t.substring(s+r.length+o.length);const l=`[saveNL]\n${r}${o}${c}\n${r}\n[/saveNL]`;t=t.substring(0,s)+l+(-1!==i?t.substring(i+1+r.length):""),s+=l.length}else if(e.groups?.bbcode){const n=e.groups.bbcode,o=`[/${e.groups.bbcodeTag.toLowerCase()}]`,a=t.toLowerCase().indexOf(o,s+1);s=r(s+n.length,a,o,!0)}else if(e.groups.backtick){const t=e.groups.backtick,n=e.groups.tickStart,o=e.groups.tickEnd;s=r(s+n.length,s+t.length-o.length,o)}}return e.hoistMap=n,[t,e]}function Mt(t,e){let n=0;for(;-1!==(n=H(t,Y,n));){const e=Y.exec(t.substring(n))[0],s=`[saveNL]\n${e}\n[/saveNL]`;t=t.substring(0,n)+s+t.substring(n+e.length),n+=s.length}return[t,e]}function Ft(t){let e={};const n=[Pt,Mt];for(const s of n)[t,e]=s(t,e);return[t,e]}function Rt(t,e){const n=[],s=e.onToken,r=(t,e)=>{s(C(2,`/${t}`,0,0,e,e))},o=W(t,{...e,onToken(t){if(t.isTag()&&t.isStart()){const e=t.getValue().toLowerCase();o.isTokenNested(e)&&n.push(e)}else if(t.isTag()&&t.isEnd()){const e=t.getValue().slice(1).toLowerCase(),o=n.lastIndexOf(e);if(-1===o)return void s(C(1,t.toString()));for(;n.length-1>o;)r(n.pop(),t.getStart());n.pop()}s(t)}}),a=o.tokenize;return o.tokenize=()=>{const e=a();for(;n.length;)r(n.pop(),t.length);return e},o}const Ht={onlyAllowTags:[...Vt],caseFreeTags:!0,contextFreeTags:["plain","code","icode","class","fa"],enableEscapeTags:!0,onError:t=>{Ht.previewing&&console.warn(t.message,t.lineNumber,t.columnNumber)}},Jt=Bt(),Zt=new RegExp(`\\[(?:${Vt.filter((t=>"savenl"!==t)).join("|")})(?=[\\s=\\]])`,"i");t.RpNBBCode=(t,e)=>{const n=[Jt];e.preserveWhitespace&&n.push((t=>ot(t))),n.push((t=>st(t)),it);const[s,r]=Ft(t);return function(t){const e="function"==typeof t?[t]:t||[],n=()=>"";return{process(t,s){const r=s||{skipParse:!1,parser:I,render:n,data:null},o=r.parser||I,a=r.render,i=r.data||null;if("function"!=typeof o)throw new Error("C1");const c=r.skipParse&&Array.isArray(t)?t:o(t,r);let l=r.skipParse&&Array.isArray(t)?U(t||[],r):U(c,r);for(let t=0;treduce(acc, key, obj), def);\n}\nfunction getNodeLength(node) {\n if (isTagNode(node) && Array.isArray(node.content)) {\n return node.content.reduce((count, contentNode)=>{\n return count + getNodeLength(contentNode);\n }, 0);\n }\n if (isStringNode(node)) {\n return String(node).length;\n }\n return 0;\n}\nfunction appendToNode(node, value) {\n if (Array.isArray(node.content)) {\n node.content.push(value);\n }\n}\n/**\n * Replaces \" to &qquot;\n * @param {string} value\n */ function escapeAttrValue(value) {\n return value.replace(/&/g, '&').replace(//g, '>').replace(/\"/g, '"').replace(/'/g, ''')// eslint-disable-next-line no-script-url\n .replace(/(javascript|data|vbscript):/gi, '$1%3A');\n}\n/**\n * @deprecated use escapeAttrValue\n */ const escapeHTML = escapeAttrValue;\n/**\n * Accept name and value and return valid html5 attribute string\n */ function attrValue(name, value) {\n // in case of performance\n switch(typeof value){\n case 'boolean':\n return value ? `${name}` : '';\n case 'number':\n return `${name}=\"${value}\"`;\n case 'string':\n return `${name}=\"${escapeAttrValue(value)}\"`;\n case 'object':\n return `${name}=\"${escapeAttrValue(JSON.stringify(value))}\"`;\n default:\n return '';\n }\n}\n/**\n * Transforms attrs to html params string\n * @example\n * attrsToString({ 'foo': true, 'bar': bar' }) => 'foo=\"true\" bar=\"bar\"'\n */ function attrsToString(values) {\n // To avoid some malformed attributes\n if (values == null) {\n return '';\n }\n return keysReduce(values, (arr, key, obj)=>[\n ...arr,\n attrValue(key, obj[key])\n ], [\n ''\n ]).join(' ');\n}\n/**\n * Gets value from\n * @example\n * getUniqAttr({ 'foo': true, 'bar': bar' }) => 'bar'\n */ function getUniqAttr(attrs) {\n return keysReduce(attrs || {}, (res, key, obj)=>obj[key] === key ? obj[key] : null, null);\n}\nexport { attrsToString, attrValue, appendToNode, escapeHTML, escapeAttrValue, getNodeLength, getUniqAttr, isTagNode, isStringNode, isEOL };\n","import { OPEN_BRAKET, CLOSE_BRAKET, SLASH } from './char';\nimport { getUniqAttr, getNodeLength, appendToNode, attrsToString, attrValue, isTagNode } from './helpers';\nconst getTagAttrs = (tag, params)=>{\n const uniqAttr = getUniqAttr(params);\n if (uniqAttr) {\n const tagAttr = attrValue(tag, uniqAttr);\n const attrs = {\n ...params\n };\n delete attrs[String(uniqAttr)];\n const attrsStr = attrsToString(attrs);\n return `${tagAttr}${attrsStr}`;\n }\n return `${tag}${attrsToString(params)}`;\n};\nconst renderContent = (content, openTag, closeTag)=>{\n const toString = (node)=>{\n if (isTagNode(node)) {\n return node.toString({\n openTag,\n closeTag\n });\n }\n return String(node);\n };\n if (Array.isArray(content)) {\n return content.reduce((r, node)=>{\n if (node !== null) {\n return r + toString(node);\n }\n return r;\n }, '');\n }\n if (content) {\n return toString(content);\n }\n return null;\n};\nexport class TagNode {\n attr(name, value) {\n if (typeof value !== 'undefined') {\n this.attrs[name] = value;\n }\n return this.attrs[name];\n }\n append(value) {\n return appendToNode(this, value);\n }\n setStart(value) {\n this.start = value;\n }\n setEnd(value) {\n this.end = value;\n }\n get length() {\n return getNodeLength(this);\n }\n toTagStart({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}) {\n const tagAttrs = getTagAttrs(String(this.tag), this.attrs);\n return `${openTag}${tagAttrs}${closeTag}`;\n }\n toTagEnd({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}) {\n return `${openTag}${SLASH}${this.tag}${closeTag}`;\n }\n toTagNode() {\n const newNode = new TagNode(String(this.tag).toLowerCase(), this.attrs, this.content);\n if (this.start) {\n newNode.setStart(this.start);\n }\n if (this.end) {\n newNode.setEnd(this.end);\n }\n return newNode;\n }\n toString({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}) {\n const content = this.content ? renderContent(this.content, openTag, closeTag) : '';\n const tagStart = this.toTagStart({\n openTag,\n closeTag\n });\n if (this.content === null || Array.isArray(this.content) && this.content.length === 0) {\n return tagStart;\n }\n return `${tagStart}${content}${this.toTagEnd({\n openTag,\n closeTag\n })}`;\n }\n static create(tag, attrs = {}, content = null, start) {\n const node = new TagNode(tag, attrs, content);\n if (start) {\n node.setStart(start);\n }\n return node;\n }\n static isOf(node, type) {\n return node.tag === type;\n }\n constructor(tag, attrs, content){\n this.tag = tag;\n this.attrs = attrs;\n this.content = content;\n }\n}\n","import { OPEN_BRAKET, CLOSE_BRAKET, SLASH } from '@bbob/plugin-helper';\n// type, value, line, row, start pos, end pos\nconst TOKEN_TYPE_ID = 't'; // 0;\nconst TOKEN_VALUE_ID = 'v'; // 1;\nconst TOKEN_COLUMN_ID = 'r'; // 2;\nconst TOKEN_LINE_ID = 'l'; // 3;\nconst TOKEN_START_POS_ID = 's'; // 4;\nconst TOKEN_END_POS_ID = 'e'; // 5;\nconst TOKEN_TYPE_WORD = 1; // 'word';\nconst TOKEN_TYPE_TAG = 2; // 'tag';\nconst TOKEN_TYPE_ATTR_NAME = 3; // 'attr-name';\nconst TOKEN_TYPE_ATTR_VALUE = 4; // 'attr-value';\nconst TOKEN_TYPE_SPACE = 5; // 'space';\nconst TOKEN_TYPE_NEW_LINE = 6; // 'new-line';\nconst getTokenValue = (token)=>{\n if (token && typeof token[TOKEN_VALUE_ID] !== 'undefined') {\n return token[TOKEN_VALUE_ID];\n }\n return '';\n};\nconst getTokenLine = (token)=>token && token[TOKEN_LINE_ID] || 0;\nconst getTokenColumn = (token)=>token && token[TOKEN_COLUMN_ID] || 0;\nconst getStartPosition = (token)=>token && token[TOKEN_START_POS_ID] || 0;\nconst getEndPosition = (token)=>token && token[TOKEN_END_POS_ID] || 0;\nconst isTextToken = (token)=>{\n if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') {\n return token[TOKEN_TYPE_ID] === TOKEN_TYPE_SPACE || token[TOKEN_TYPE_ID] === TOKEN_TYPE_NEW_LINE || token[TOKEN_TYPE_ID] === TOKEN_TYPE_WORD;\n }\n return false;\n};\nconst isTagToken = (token)=>{\n if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') {\n return token[TOKEN_TYPE_ID] === TOKEN_TYPE_TAG;\n }\n return false;\n};\nconst isTagEnd = (token)=>getTokenValue(token).charCodeAt(0) === SLASH.charCodeAt(0);\nconst isTagStart = (token)=>!isTagEnd(token);\nconst isAttrNameToken = (token)=>{\n if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') {\n return token[TOKEN_TYPE_ID] === TOKEN_TYPE_ATTR_NAME;\n }\n return false;\n};\nconst isAttrValueToken = (token)=>{\n if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') {\n return token[TOKEN_TYPE_ID] === TOKEN_TYPE_ATTR_VALUE;\n }\n return false;\n};\nconst getTagName = (token)=>{\n const value = getTokenValue(token);\n return isTagEnd(token) ? value.slice(1) : value;\n};\nconst tokenToText = (token, openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET)=>{\n let text = openTag;\n text += getTokenValue(token);\n text += closeTag;\n return text;\n};\n/**\n * @export\n * @class Token\n */ class Token {\n get type() {\n return this[TOKEN_TYPE_ID];\n }\n isEmpty() {\n return this[TOKEN_TYPE_ID] === 0 || isNaN(this[TOKEN_TYPE_ID]);\n }\n isText() {\n return isTextToken(this);\n }\n isTag() {\n return isTagToken(this);\n }\n isAttrName() {\n return isAttrNameToken(this);\n }\n isAttrValue() {\n return isAttrValueToken(this);\n }\n isStart() {\n return isTagStart(this);\n }\n isEnd() {\n return isTagEnd(this);\n }\n getName() {\n return getTagName(this);\n }\n getValue() {\n return getTokenValue(this);\n }\n getLine() {\n return getTokenLine(this);\n }\n getColumn() {\n return getTokenColumn(this);\n }\n getStart() {\n return getStartPosition(this);\n }\n getEnd() {\n return getEndPosition(this);\n }\n toString({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}) {\n return tokenToText(this, openTag, closeTag);\n }\n constructor(type, value, row = 0, col = 0, start = 0, end = 0){\n this[TOKEN_LINE_ID] = row;\n this[TOKEN_COLUMN_ID] = col;\n this[TOKEN_TYPE_ID] = type || 0;\n this[TOKEN_VALUE_ID] = String(value);\n this[TOKEN_START_POS_ID] = start;\n this[TOKEN_END_POS_ID] = end;\n }\n}\nexport const TYPE_ID = TOKEN_TYPE_ID;\nexport const VALUE_ID = TOKEN_VALUE_ID;\nexport const LINE_ID = TOKEN_LINE_ID;\nexport const COLUMN_ID = TOKEN_COLUMN_ID;\nexport const START_POS_ID = TOKEN_START_POS_ID;\nexport const END_POS_ID = TOKEN_END_POS_ID;\nexport const TYPE_WORD = TOKEN_TYPE_WORD;\nexport const TYPE_TAG = TOKEN_TYPE_TAG;\nexport const TYPE_ATTR_NAME = TOKEN_TYPE_ATTR_NAME;\nexport const TYPE_ATTR_VALUE = TOKEN_TYPE_ATTR_VALUE;\nexport const TYPE_SPACE = TOKEN_TYPE_SPACE;\nexport const TYPE_NEW_LINE = TOKEN_TYPE_NEW_LINE;\nexport { Token };\nexport default Token;\n","import { QUOTEMARK, BACKSLASH } from '@bbob/plugin-helper';\nexport class CharGrabber {\n skip(num = 1, silent) {\n this.c.pos += num;\n if (this.o && this.o.onSkip && !silent) {\n this.o.onSkip();\n }\n }\n hasNext() {\n return this.c.len > this.c.pos;\n }\n getCurr() {\n if (typeof this.s[this.c.pos] === 'undefined') {\n return '';\n }\n return this.s[this.c.pos];\n }\n getPos() {\n return this.c.pos;\n }\n getLength() {\n return this.c.len;\n }\n getRest() {\n return this.s.substring(this.c.pos);\n }\n getNext() {\n const nextPos = this.c.pos + 1;\n return nextPos <= this.s.length - 1 ? this.s[nextPos] : null;\n }\n getPrev() {\n const prevPos = this.c.pos - 1;\n if (typeof this.s[prevPos] === 'undefined') {\n return null;\n }\n return this.s[prevPos];\n }\n isLast() {\n return this.c.pos === this.c.len;\n }\n includes(val) {\n return this.s.indexOf(val, this.c.pos) >= 0;\n }\n grabWhile(condition, silent) {\n let start = 0;\n if (this.hasNext()) {\n start = this.c.pos;\n while(this.hasNext() && condition(this.getCurr())){\n this.skip(1, silent);\n }\n }\n return this.s.substring(start, this.c.pos);\n }\n grabN(num = 0) {\n return this.s.substring(this.c.pos, this.c.pos + num);\n }\n /**\n * Grabs rest of string until it find a char\n */ substrUntilChar(char) {\n const { pos } = this.c;\n const idx = this.s.indexOf(char, pos);\n return idx >= 0 ? this.s.substring(pos, idx) : '';\n }\n constructor(source, options = {}){\n this.s = source;\n this.c = {\n pos: 0,\n len: source.length\n };\n this.o = options;\n }\n}\n/**\n * Creates a grabber wrapper for source string, that helps to iterate over string char by char\n */ export const createCharGrabber = (source, options)=>new CharGrabber(source, options);\n/**\n * Trims string from start and end by char\n * @example\n * trimChar('*hello*', '*') ==> 'hello'\n */ export const trimChar = (str, charToRemove)=>{\n while(str.charAt(0) === charToRemove){\n // eslint-disable-next-line no-param-reassign\n str = str.substring(1);\n }\n while(str.charAt(str.length - 1) === charToRemove){\n // eslint-disable-next-line no-param-reassign\n str = str.substring(0, str.length - 1);\n }\n return str;\n};\n/**\n * Unquotes \\\" to \"\n */ export const unquote = (str)=>str.replace(BACKSLASH + QUOTEMARK, QUOTEMARK);\n","/* eslint-disable no-plusplus,no-param-reassign */ import { OPEN_BRAKET, CLOSE_BRAKET, QUOTEMARK, BACKSLASH, SLASH, SPACE, TAB, EQ, N } from '@bbob/plugin-helper';\nimport { Token, TYPE_ATTR_NAME, TYPE_ATTR_VALUE, TYPE_NEW_LINE, TYPE_SPACE, TYPE_TAG, TYPE_WORD } from './Token';\nimport { createCharGrabber, trimChar, unquote } from './utils';\n// for cases \nconst EM = '!';\nexport function createTokenOfType(type, value, r = 0, cl = 0, p = 0, e = 0) {\n return new Token(type, value, r, cl, p, e);\n}\nconst STATE_WORD = 0;\nconst STATE_TAG = 1;\nconst STATE_TAG_ATTRS = 2;\nconst TAG_STATE_NAME = 0;\nconst TAG_STATE_ATTR = 1;\nconst TAG_STATE_VALUE = 2;\nconst WHITESPACES = [\n SPACE,\n TAB\n];\nconst SPECIAL_CHARS = [\n EQ,\n SPACE,\n TAB\n];\nconst END_POS_OFFSET = 2; // length + start position offset\nconst isWhiteSpace = (char)=>WHITESPACES.indexOf(char) >= 0;\nconst isEscapeChar = (char)=>char === BACKSLASH;\nconst isSpecialChar = (char)=>SPECIAL_CHARS.indexOf(char) >= 0;\nconst isNewLine = (char)=>char === N;\nconst unq = (val)=>unquote(trimChar(val, QUOTEMARK));\nexport function createLexer(buffer, options = {}) {\n let row = 0;\n let prevCol = 0;\n let col = 0;\n let tokenIndex = -1;\n let stateMode = STATE_WORD;\n let tagMode = TAG_STATE_NAME;\n let contextFreeTag = '';\n const tokens = new Array(Math.floor(buffer.length));\n const openTag = options.openTag || OPEN_BRAKET;\n const closeTag = options.closeTag || CLOSE_BRAKET;\n const escapeTags = !!options.enableEscapeTags;\n const contextFreeTags = (options.contextFreeTags || []).filter(Boolean).map((tag)=>tag.toLowerCase());\n const caseFreeTags = options.caseFreeTags || false;\n const nestedMap = new Map();\n const onToken = options.onToken || (()=>{});\n const RESERVED_CHARS = [\n closeTag,\n openTag,\n QUOTEMARK,\n BACKSLASH,\n SPACE,\n TAB,\n EQ,\n N,\n EM\n ];\n const NOT_CHAR_TOKENS = [\n openTag,\n SPACE,\n TAB,\n N\n ];\n const isCharReserved = (char)=>RESERVED_CHARS.indexOf(char) >= 0;\n const isCharToken = (char)=>NOT_CHAR_TOKENS.indexOf(char) === -1;\n const isEscapableChar = (char)=>char === openTag || char === closeTag || char === BACKSLASH;\n const onSkip = ()=>{\n col++;\n };\n const checkContextFreeMode = (name, isClosingTag)=>{\n if (contextFreeTag !== '' && isClosingTag) {\n contextFreeTag = '';\n }\n if (contextFreeTag === '' && contextFreeTags.includes(name.toLowerCase())) {\n contextFreeTag = name;\n }\n };\n const chars = createCharGrabber(buffer, {\n onSkip\n });\n /**\n * Emits newly created token to subscriber\n */ function emitToken(type, value, startPos, endPos) {\n const token = createTokenOfType(type, value, row, prevCol, startPos, endPos);\n onToken(token);\n prevCol = col;\n tokenIndex += 1;\n tokens[tokenIndex] = token;\n }\n function nextTagState(tagChars, isSingleValueTag, masterStartPos) {\n if (tagMode === TAG_STATE_ATTR) {\n const validAttrName = (char)=>!(char === EQ || isWhiteSpace(char));\n const name = tagChars.grabWhile(validAttrName);\n const isEnd = tagChars.isLast();\n const isValue = tagChars.getCurr() !== EQ;\n tagChars.skip();\n if (isEnd || isValue) {\n emitToken(TYPE_ATTR_VALUE, unq(name));\n } else {\n emitToken(TYPE_ATTR_NAME, name);\n }\n if (isEnd) {\n return TAG_STATE_NAME;\n }\n if (isValue) {\n return TAG_STATE_ATTR;\n }\n return TAG_STATE_VALUE;\n }\n if (tagMode === TAG_STATE_VALUE) {\n let stateSpecial = false;\n const validAttrValue = (char)=>{\n // const isEQ = char === EQ;\n const isQM = char === QUOTEMARK;\n const prevChar = tagChars.getPrev();\n const nextChar = tagChars.getNext();\n const isPrevSLASH = prevChar === BACKSLASH;\n const isNextEQ = nextChar === EQ;\n const isWS = isWhiteSpace(char);\n // const isPrevWS = isWhiteSpace(prevChar);\n const isNextWS = nextChar && isWhiteSpace(nextChar);\n if (stateSpecial && isSpecialChar(char)) {\n return true;\n }\n if (isQM && !isPrevSLASH) {\n stateSpecial = !stateSpecial;\n if (!stateSpecial && !(isNextEQ || isNextWS)) {\n return false;\n }\n }\n if (!isSingleValueTag) {\n return !isWS;\n // return (isEQ || isWS) === false;\n }\n return true;\n };\n const name = tagChars.grabWhile(validAttrValue);\n tagChars.skip();\n emitToken(TYPE_ATTR_VALUE, unq(name));\n if (tagChars.getPrev() === QUOTEMARK) {\n prevCol++;\n }\n if (tagChars.isLast()) {\n return TAG_STATE_NAME;\n }\n return TAG_STATE_ATTR;\n }\n const start = masterStartPos + tagChars.getPos() - 1;\n const validName = (char)=>!(char === EQ || isWhiteSpace(char) || tagChars.isLast());\n const name = tagChars.grabWhile(validName);\n emitToken(TYPE_TAG, name, start, masterStartPos + tagChars.getLength() + 1);\n checkContextFreeMode(name);\n tagChars.skip();\n prevCol++;\n // in cases when we has [url=someval]GET[/url] and we dont need to parse all\n if (isSingleValueTag) {\n return TAG_STATE_VALUE;\n }\n const hasEQ = tagChars.includes(EQ);\n return hasEQ ? TAG_STATE_ATTR : TAG_STATE_VALUE;\n }\n function stateTag() {\n const currChar = chars.getCurr();\n const nextChar = chars.getNext();\n chars.skip();\n // detect case where we have '[My word [tag][/tag]' or we have '[My last line word'\n const substr = chars.substrUntilChar(closeTag);\n const hasInvalidChars = substr.length === 0 || substr.indexOf(openTag) >= 0;\n if (nextChar && isCharReserved(nextChar) || hasInvalidChars || chars.isLast()) {\n emitToken(TYPE_WORD, currChar);\n return STATE_WORD;\n }\n // [myTag ]\n const isNoAttrsInTag = substr.indexOf(EQ) === -1;\n // [/myTag]\n const isClosingTag = substr[0] === SLASH;\n if (isNoAttrsInTag || isClosingTag) {\n const startPos = chars.getPos() - 1;\n const name = chars.grabWhile((char)=>char !== closeTag);\n const endPos = startPos + name.length + END_POS_OFFSET;\n chars.skip(); // skip closeTag\n emitToken(TYPE_TAG, name, startPos, endPos);\n checkContextFreeMode(name, isClosingTag);\n return STATE_WORD;\n }\n return STATE_TAG_ATTRS;\n }\n function stateAttrs() {\n const startPos = chars.getPos();\n const silent = true;\n const tagStr = chars.grabWhile((char)=>char !== closeTag, silent);\n const tagGrabber = createCharGrabber(tagStr, {\n onSkip\n });\n const hasSpace = tagGrabber.includes(SPACE);\n tagMode = TAG_STATE_NAME;\n while(tagGrabber.hasNext()){\n tagMode = nextTagState(tagGrabber, !hasSpace, startPos);\n }\n chars.skip(); // skip closeTag\n return STATE_WORD;\n }\n function stateWord() {\n if (isNewLine(chars.getCurr())) {\n emitToken(TYPE_NEW_LINE, chars.getCurr());\n chars.skip();\n col = 0;\n prevCol = 0;\n row++;\n return STATE_WORD;\n }\n if (isWhiteSpace(chars.getCurr())) {\n const word = chars.grabWhile(isWhiteSpace);\n emitToken(TYPE_SPACE, word);\n return STATE_WORD;\n }\n if (chars.getCurr() === openTag) {\n if (contextFreeTag) {\n const fullTagLen = openTag.length + SLASH.length + contextFreeTag.length;\n const fullTagName = `${openTag}${SLASH}${contextFreeTag}`;\n const foundTag = chars.grabN(fullTagLen);\n const isEndContextFreeMode = foundTag === fullTagName;\n if (isEndContextFreeMode) {\n return STATE_TAG;\n }\n } else if (chars.includes(closeTag)) {\n return STATE_TAG;\n }\n emitToken(TYPE_WORD, chars.getCurr());\n chars.skip();\n prevCol++;\n return STATE_WORD;\n }\n if (escapeTags) {\n if (isEscapeChar(chars.getCurr())) {\n const currChar = chars.getCurr();\n const nextChar = chars.getNext();\n chars.skip(); // skip the \\ without emitting anything\n if (nextChar && isEscapableChar(nextChar)) {\n chars.skip(); // skip past the [, ] or \\ as well\n emitToken(TYPE_WORD, nextChar);\n return STATE_WORD;\n }\n emitToken(TYPE_WORD, currChar);\n return STATE_WORD;\n }\n const isChar = (char)=>isCharToken(char) && !isEscapeChar(char);\n const word = chars.grabWhile(isChar);\n emitToken(TYPE_WORD, word);\n return STATE_WORD;\n }\n const word = chars.grabWhile(isCharToken);\n emitToken(TYPE_WORD, word);\n return STATE_WORD;\n }\n function tokenize() {\n stateMode = STATE_WORD;\n while(chars.hasNext()){\n switch(stateMode){\n case STATE_TAG:\n stateMode = stateTag();\n break;\n case STATE_TAG_ATTRS:\n stateMode = stateAttrs();\n break;\n case STATE_WORD:\n default:\n stateMode = stateWord();\n break;\n }\n }\n tokens.length = tokenIndex + 1;\n return tokens;\n }\n function isTokenNested(tokenValue) {\n const value = openTag + SLASH + tokenValue;\n if (nestedMap.has(value)) {\n return !!nestedMap.get(value);\n } else {\n const status = caseFreeTags ? buffer.toLowerCase().indexOf(value.toLowerCase()) > -1 : buffer.indexOf(value) > -1;\n nestedMap.set(value, status);\n return status;\n }\n }\n return {\n tokenize,\n isTokenNested\n };\n}\n","import { CLOSE_BRAKET, OPEN_BRAKET, TagNode, isTagNode } from \"@bbob/plugin-helper\";\nimport { createLexer } from \"./lexer\";\nclass NodeList {\n last() {\n if (Array.isArray(this.n) && this.n.length > 0 && typeof this.n[this.n.length - 1] !== \"undefined\") {\n return this.n[this.n.length - 1];\n }\n return null;\n }\n flush() {\n return this.n.length ? this.n.pop() : false;\n }\n push(value) {\n this.n.push(value);\n }\n toArray() {\n return this.n;\n }\n constructor(){\n this.n = [];\n }\n}\nconst createList = ()=>new NodeList();\nfunction parse(input, opts = {}) {\n const options = opts;\n const openTag = options.openTag || OPEN_BRAKET;\n const closeTag = options.closeTag || CLOSE_BRAKET;\n const onlyAllowTags = (options.onlyAllowTags || []).filter(Boolean).map((tag)=>tag.toLowerCase());\n const caseFreeTags = options.caseFreeTags || false;\n let tokenizer = null;\n /**\n * Result AST of nodes\n * @private\n * @type {NodeList}\n */ const nodes = createList();\n /**\n * Temp buffer of nodes that's nested to another node\n * @private\n */ const nestedNodes = createList();\n /**\n * Temp buffer of nodes [tag..]...[/tag]\n * @private\n * @type {NodeList}\n */ const tagNodes = createList();\n /**\n * Temp buffer of tag attributes\n * @private\n * @type {NodeList}\n */ const tagNodesAttrName = createList();\n /**\n * Cache for nested tags checks\n */ const nestedTagsMap = new Set();\n function isTokenNested(token) {\n const tokenValue = token.getValue();\n const value = caseFreeTags ? tokenValue.toLowerCase() : tokenValue;\n const { isTokenNested } = tokenizer || {};\n if (!nestedTagsMap.has(value) && isTokenNested && isTokenNested(value)) {\n nestedTagsMap.add(value);\n return true;\n }\n return nestedTagsMap.has(value);\n }\n /**\n * @private\n */ function isTagNested(tagName) {\n return Boolean(nestedTagsMap.has(caseFreeTags ? tagName.toLowerCase() : tagName));\n }\n /**\n * @private\n */ function isAllowedTag(value) {\n if (onlyAllowTags.length) {\n return onlyAllowTags.indexOf(value.toLowerCase()) >= 0;\n }\n return true;\n }\n /**\n * Flushes temp tag nodes and its attributes buffers\n * @private\n */ function flushTagNodes() {\n if (tagNodes.flush()) {\n tagNodesAttrName.flush();\n }\n }\n /**\n * @private\n */ function getNodes() {\n const lastNestedNode = nestedNodes.last();\n if (lastNestedNode && isTagNode(lastNestedNode)) {\n return lastNestedNode.content;\n }\n return nodes.toArray();\n }\n /**\n * @private\n */ function appendNodeAsString(nodes, node, isNested = true) {\n if (Array.isArray(nodes) && typeof node !== \"undefined\") {\n nodes.push(node.toTagStart({\n openTag,\n closeTag\n }));\n if (Array.isArray(node.content) && node.content.length) {\n node.content.forEach((item)=>{\n nodes.push(item);\n });\n if (isNested) {\n nodes.push(node.toTagEnd({\n openTag,\n closeTag\n }));\n }\n }\n }\n }\n /**\n * @private\n */ function appendNodes(nodes, node) {\n if (Array.isArray(nodes) && typeof node !== \"undefined\") {\n if (isTagNode(node)) {\n if (isAllowedTag(node.tag)) {\n nodes.push(node.toTagNode());\n } else {\n appendNodeAsString(nodes, node);\n }\n } else {\n nodes.push(node);\n }\n }\n }\n /**\n * @private\n * @param {Token} token\n */ function handleTagStart(token) {\n flushTagNodes();\n const tagNode = TagNode.create(token.getValue(), {}, [], {\n from: token.getStart(),\n to: token.getEnd()\n });\n const isNested = isTokenNested(token);\n tagNodes.push(tagNode);\n if (isNested) {\n nestedNodes.push(tagNode);\n } else {\n const nodes = getNodes();\n appendNodes(nodes, tagNode);\n }\n }\n /**\n * @private\n * @param {Token} token\n */ function handleTagEnd(token) {\n const tagName = token.getValue().slice(1);\n const lastNestedNode = nestedNodes.flush();\n flushTagNodes();\n if (lastNestedNode) {\n const nodes = getNodes();\n if (isTagNode(lastNestedNode)) {\n lastNestedNode.setEnd({\n from: token.getStart(),\n to: token.getEnd()\n });\n }\n appendNodes(nodes, lastNestedNode);\n } else if (!isTagNested(tagName)) {\n const nodes = getNodes();\n appendNodes(nodes, token.toString({\n openTag,\n closeTag\n }));\n } else if (typeof options.onError === \"function\") {\n const tag = token.getValue();\n const line = token.getLine();\n const column = token.getColumn();\n options.onError({\n tagName: tag,\n lineNumber: line,\n columnNumber: column\n });\n }\n }\n /**\n * @private\n * @param {Token} token\n */ function handleTag(token) {\n // [tag]\n if (token.isStart()) {\n handleTagStart(token);\n }\n // [/tag]\n if (token.isEnd()) {\n handleTagEnd(token);\n }\n }\n /**\n * @private\n * @param {Token} token\n */ function handleNode(token) {\n /**\n * @type {TagNode}\n */ const activeTagNode = tagNodes.last();\n const tokenValue = token.getValue();\n const isNested = isTagNested(token.toString());\n const nodes = getNodes();\n if (activeTagNode !== null) {\n if (token.isAttrName()) {\n tagNodesAttrName.push(tokenValue);\n const attrName = tagNodesAttrName.last();\n if (attrName) {\n activeTagNode.attr(attrName, \"\");\n }\n } else if (token.isAttrValue()) {\n const attrName = tagNodesAttrName.last();\n if (attrName) {\n activeTagNode.attr(attrName, tokenValue);\n tagNodesAttrName.flush();\n } else {\n activeTagNode.attr(tokenValue, tokenValue);\n }\n } else if (token.isText()) {\n if (isNested) {\n activeTagNode.append(tokenValue);\n } else {\n appendNodes(nodes, tokenValue);\n }\n } else if (token.isTag()) {\n // if tag is not allowed, just pass it as is\n appendNodes(nodes, token.toString({\n openTag,\n closeTag\n }));\n }\n } else if (token.isText()) {\n appendNodes(nodes, tokenValue);\n } else if (token.isTag()) {\n // if tag is not allowed, just pass it as is\n appendNodes(nodes, token.toString({\n openTag,\n closeTag\n }));\n }\n }\n /**\n * @private\n * @param {Token} token\n */ function onToken(token) {\n if (token.isTag()) {\n handleTag(token);\n } else {\n handleNode(token);\n }\n }\n const lexer = opts.createTokenizer ? opts.createTokenizer : createLexer;\n tokenizer = lexer(input, {\n onToken,\n openTag,\n closeTag,\n onlyAllowTags: options.onlyAllowTags,\n contextFreeTags: options.contextFreeTags,\n caseFreeTags: options.caseFreeTags,\n enableEscapeTags: options.enableEscapeTags\n });\n // eslint-disable-next-line no-unused-vars\n const tokens = tokenizer.tokenize();\n // handles situations where we open tag, but forgot close them\n // for ex [q]test[/q][u]some[/u][q]some [u]some[/u] // forgot to close [/q]\n // so we need to flush nested content to nodes array\n const lastNestedNode = nestedNodes.flush();\n if (lastNestedNode !== null && lastNestedNode && isTagNode(lastNestedNode) && isTagNested(lastNestedNode.tag)) {\n appendNodeAsString(getNodes(), lastNestedNode, false);\n }\n return nodes.toArray();\n}\nexport { parse };\nexport default parse;\n","/* eslint-disable no-plusplus */ const isObj = (value)=>typeof value === 'object' && value !== null;\nconst isBool = (value)=>typeof value === 'boolean';\nexport function iterate(t, cb) {\n const tree = t;\n if (Array.isArray(tree)) {\n for(let idx = 0; idx < tree.length; idx++){\n tree[idx] = iterate(cb(tree[idx]), cb);\n }\n } else if (isObj(tree) && 'content' in tree) {\n iterate(tree.content, cb);\n }\n return tree;\n}\nexport function same(expected, actual) {\n if (typeof expected !== typeof actual) {\n return false;\n }\n if (!isObj(expected) || expected === null) {\n return expected === actual;\n }\n if (Array.isArray(expected)) {\n return expected.every((exp)=>[].some.call(actual, (act)=>same(exp, act)));\n }\n if (isObj(expected) && isObj(actual)) {\n return Object.keys(expected).every((key)=>{\n const ao = actual[key];\n const eo = expected[key];\n if (isObj(eo) && isObj(ao)) {\n return same(eo, ao);\n }\n if (isBool(eo)) {\n return eo !== (ao === null);\n }\n return ao === eo;\n });\n }\n return false;\n}\nexport function match(t, expression, cb) {\n if (Array.isArray(expression)) {\n return iterate(t, (node)=>{\n for(let idx = 0; idx < expression.length; idx++){\n if (same(expression[idx], node)) {\n return cb(node);\n }\n }\n return node;\n });\n }\n return iterate(t, (node)=>same(expression, node) ? cb(node) : node);\n}\n","import { parse } from '@bbob/parser';\nimport { iterate, match } from './utils';\nimport { C1, C2 } from './errors';\nexport function createTree(tree, options) {\n const extendedTree = tree;\n extendedTree.messages = [\n ...extendedTree.messages || []\n ];\n extendedTree.options = {\n ...options,\n ...extendedTree.options\n };\n extendedTree.walk = function walkNodes(cb) {\n return iterate(this, cb);\n };\n extendedTree.match = function matchNodes(expr, cb) {\n return match(this, expr, cb);\n };\n return extendedTree;\n}\nexport default function bbob(plugs) {\n const plugins = typeof plugs === 'function' ? [\n plugs\n ] : plugs || [];\n const mockRender = ()=>\"\";\n return {\n process (input, opts) {\n const options = opts || {\n skipParse: false,\n parser: parse,\n render: mockRender,\n data: null\n };\n const parseFn = options.parser || parse;\n const renderFn = options.render;\n const data = options.data || null;\n if (typeof parseFn !== 'function') {\n throw new Error(C1);\n }\n // raw tree before modification with plugins\n const raw = options.skipParse && Array.isArray(input) ? input : parseFn(input, options);\n let tree = options.skipParse && Array.isArray(input) ? createTree(input || [], options) : createTree(raw, options);\n for(let idx = 0; idx < plugins.length; idx++){\n const plugin = plugins[idx];\n if (typeof plugin === 'function' && renderFn) {\n const newTree = plugin(tree, {\n parse: parseFn,\n render: renderFn,\n iterate,\n data\n });\n tree = createTree(newTree || tree, options);\n }\n }\n return {\n get html () {\n if (typeof renderFn !== 'function') {\n throw new Error(C2);\n }\n return renderFn(tree, tree.options);\n },\n tree,\n raw,\n messages: tree.messages\n };\n }\n };\n}\n","import core from '@bbob/core';\nimport { attrsToString, isTagNode } from '@bbob/plugin-helper';\nconst SELFCLOSE_END_TAG = '/>';\nconst CLOSE_START_TAG = '';\nfunction renderNode(node, options) {\n const { stripTags = false } = options || {};\n if (typeof node === 'undefined' || node === null) {\n return '';\n }\n if (typeof node === 'string' || typeof node === 'number') {\n return String(node);\n }\n if (Array.isArray(node)) {\n return render(node, options);\n }\n if (isTagNode(node)) {\n if (stripTags) {\n return render(node.content, options);\n }\n const attrs = attrsToString(node.attrs);\n if (node.content === null) {\n return START_TAG + node.tag + attrs + SELFCLOSE_END_TAG;\n }\n return START_TAG + node.tag + attrs + END_TAG + render(node.content, options) + CLOSE_START_TAG + node.tag + END_TAG;\n }\n return '';\n}\nexport function render(nodes, options) {\n if (nodes && Array.isArray(nodes)) {\n return nodes.reduce((r, node)=>r + renderNode(node, options), '');\n }\n if (nodes) {\n return renderNode(nodes, options);\n }\n return '';\n}\nexport function html(source, plugins, options) {\n return core(plugins).process(source, {\n ...options,\n render: render\n }).html;\n}\nexport default html;\n","/**\n * Generate the node object.\n *\n * Contains additional logic to help break any unintended side effects of the top down parsing of bbob.\n * @param {string} tag name of the tag\n * @param {Object} attrs attributes of the tag\n * @param {any} content contents of the tag. `[]` will create an empty tag. `null` will create a self closing tag\n *\n * @example\n * ```\n * toNode(\"div\", { class: \"class\" }, \"content\")\n * ```\n * becomes\n * ```\n * {\n * tag: \"div\",\n * attrs: { class: \"class\" },\n * content: \"content\",\n * gen: true,\n * }\n */\nconst toNode = (tag, attrs, content = []) => ({\n tag,\n attrs,\n content,\n gen: true,\n});\n\n/**\n * Preprocess attributes of a node to either return the default single attribute\n * or return a keyed attribute list\n * @param {import('@bbob/types').TagNode} node bbcode node to process\n * @param {string} [raw] raw string. Only include if the single attribute is allowed to have spaces\n * @returns processed attributes\n */\nconst preprocessAttr = (node, raw) => {\n const keys = Object.keys(node.attrs).join(\" \");\n const vals = Object.values(node.attrs).join(\" \");\n if (keys !== vals) {\n // [tag key=val]\n return node.attrs;\n }\n if (!raw || !node.start) {\n return {\n _default: vals,\n };\n }\n // [tag=attr]\n // node.start.from = 0\n // node.start.to = 10\n const nodeRaw = raw.substring(node.start.from, node.start.to);\n if (!nodeRaw.includes(\"=\")) {\n // [tag] or [tag attr]\n return node.attrs;\n }\n const openTagParts = nodeRaw.split(\"=\");\n if (openTagParts.length !== 2) {\n return node.attrs;\n }\n let val = openTagParts[1].slice(0, -1).trim(); // `attr` or `\"attr\"`\n if (val.startsWith('\"') && val.endsWith('\"')) {\n val = val.slice(1, -1);\n }\n return {\n _default: val,\n };\n};\n\n/**\n * Attempts to return tag into its original form with proper attributes\n * @returns string of tag start\n */\nconst toOriginalStartTag = (node, raw) => {\n if (node.start) {\n return raw.substring(node.start.from, node.start.to);\n }\n if (!node.attrs) {\n return `[${node.tag}]`;\n }\n const attrs = preprocessAttr(node, raw);\n if (attrs._default) {\n return `[${node.tag}=${attrs._default}]`;\n } else {\n return node.toTagStart();\n }\n};\n\n/**\n * Attempts to return tag into its original form\n * @returns string of tag end\n */\nconst toOriginalEndTag = (node, raw) => {\n if (node.end) {\n return raw.substring(node.end.from, node.end.to);\n }\n return node.toTagEnd();\n};\n\n/**\n * Attempts to convert a node to a Markdown URL-embed-safe raw tag.\n *\n * Depending on the input node, the result is either:\n * - A single raw tag string if no end node is present\n * - Or an array of such strings if an end node is present\n * @returns {(string|string[])} - A Markdown URL-embed-safe raw tag or an array of tags.\n */\nconst toRawTag = (node, raw) => {\n if (node.end) {\n return [\n toOriginalStartTag(node, raw),\n ...node.content,\n toOriginalEndTag(node, raw),\n ];\n }\n return toOriginalStartTag(node, raw);\n};\n\n/**\n * Given a string, find the first position of a regex match\n * @param {string} string to test against\n * @param {RegExp} regex to test with\n * @param {number} startpos starting position. Defaults to 0\n * @returns index of the first match of the regex in the string\n */\nconst regexIndexOf = (string, regex, startpos) => {\n const indexOf = string.substring(startpos || 0).search(regex);\n return indexOf >= 0 ? indexOf + (startpos || 0) : indexOf;\n};\n\nconst MD_NEWLINE_INJECT = \"\\n\\n\";\nconst MD_NEWLINE_PRE_INJECT = \"\\n\\n\";\nconst MD_NEWLINE_INJECT_COMMENT = \"\";\n\nconst URL_REGEX =\n /(http|ftp|https|upload):\\/\\/([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:\\/~+#-]*[\\w@?^=%&\\/~+#-])/;\nconst MD_URL_REGEX =\n /\\!?\\[.*\\]\\((http|ftp|https|upload):\\/\\/([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:\\/~+#-]*[\\w@?^=%&\\/~+#-])\\)/;\nconst URL_REGEX_SINGLE_LINE = new RegExp(`^${URL_REGEX.source}|${MD_URL_REGEX.source}$`);\nconst ESCAPABLES_REGEX =\n /((\\n|^)(?```+|~~~+)(?.*\\n))|(?\\[(?i?code|plain)(=.*)?\\])|(?(?`{1,2})(.*)(?\\k))/im;\nconst MD_TABLE_REGEX = /^(\\|[^\\n]+\\|\\r?\\n)((?:\\| ?:?[-]+:? ?)+\\|)(\\n(?:\\|[^\\n]+\\|\\r?\\n?)*)?$/m;\n\nconst MD_BROKEN_ORDERED_LIST = \"
\\n
    \";\nconst MD_BROKEN_UNORDERED_LIST = \"\\n
      \";\nconst MD_BROKEN_BLOCKQUOTE = \"\\n
      \";\n\n/**\n * Generates a random GUID.\n *\n * Mini Racer doesn't have the crypto module, so we can't use the built-in `crypto.randomUUID` function.\n * @returns {string} a GUID\n */\nfunction generateGUID() {\n let d = new Date().getTime();\n if (window.performance && typeof window.performance.now === \"function\") {\n d += performance.now(); //use high-precision timer if available\n }\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, function (c) {\n // eslint-disable-next-line no-bitwise\n const r = (d + Math.random() * 16) % 16 | 0;\n d = Math.floor(d / 16);\n // eslint-disable-next-line no-bitwise\n return (c === \"x\" ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n\nexport {\n toNode,\n toOriginalStartTag,\n toOriginalEndTag,\n toRawTag,\n generateGUID,\n preprocessAttr,\n regexIndexOf,\n MD_NEWLINE_INJECT,\n MD_NEWLINE_INJECT_COMMENT,\n MD_NEWLINE_PRE_INJECT,\n URL_REGEX,\n MD_URL_REGEX,\n MD_TABLE_REGEX,\n URL_REGEX_SINGLE_LINE,\n ESCAPABLES_REGEX,\n MD_BROKEN_ORDERED_LIST,\n MD_BROKEN_UNORDERED_LIST,\n MD_BROKEN_BLOCKQUOTE,\n};\n","/**\n * Plugin that converts line breaks to `
      ` tags.\n * To use, put as function similar to the presets.\n *\n * If a node is marked with `noLineBreakConversion`, then it'll skip the parsing the children\n *\n * @example\n * ```ts\n * const output = bbob([preset(), lineBreakPlugin()]).process(input, {render}).html\n * ```\n */\nimport { isEOL } from \"@bbob/plugin-helper\";\nimport { MD_NEWLINE_INJECT, MD_NEWLINE_PRE_INJECT, URL_REGEX_SINGLE_LINE } from \"../utils/common\";\n\nconst isObj = (value) => typeof value === \"object\";\nconst isString = (value) => typeof value === \"string\";\n\n/**\n * Walks the tree of nodes. Will add `br` tag to all `\\n` in format that can be used in any renderer.\n * Preserves \\n so that markdown-it doesn't try to treat everything like a block\n *\n * If a node has the property noLineBreakConversion is encountered, will skip parsing children.\n * @param t tree of nodes to be processed\n * @returns modified tree\n */\nconst walk = (t, disableLineBreakConversion = false) => {\n const tree = t;\n\n if (Array.isArray(tree)) {\n reduceWordsToLines(tree);\n if (tree.some(isString)) {\n // array contains strings. Might be md compatible\n tree.unshift(MD_NEWLINE_INJECT);\n tree.push(MD_NEWLINE_INJECT);\n }\n for (let idx = 0; idx < tree.length; idx++) {\n const child = walk(tree[idx], disableLineBreakConversion);\n if (Array.isArray(child)) {\n tree.splice(idx, 1, ...child);\n idx += child.length - 1;\n } else {\n tree[idx] = child;\n }\n }\n } else if (tree && isObj(tree) && tree.content) {\n if (tree.isWhitespaceSensitive) {\n // applies only to [code] and [icode]\n // stop walk. children won't be parsed to have
      \n return tree.tag ? tree : tree.content;\n }\n if (tree.disableLineBreakConversion) {\n disableLineBreakConversion = true;\n }\n walk(tree.content, disableLineBreakConversion);\n return tree.tag ? tree : tree.content;\n } else if (isString(tree) && URL_REGEX_SINGLE_LINE.test(tree.trim())) {\n // if the entire string is a URL, then it should be prepared for onebox.\n // BBob separates strings by newlines anyway, so we can already assume this is sitting on its own line\n // MD_NEWLINE_INJECT is already replacing newline came before or the start of the array,\n // so we only need to make sure \\n\\n is added after the URL\n return [tree, MD_NEWLINE_PRE_INJECT];\n }\n\n if (isString(tree) && isEOL(tree)) {\n return disableLineBreakConversion\n ? [\"\\n\", MD_NEWLINE_INJECT]\n : [{ tag: \"br\", content: null }, MD_NEWLINE_INJECT];\n }\n\n return tree;\n};\n\n/**\n * Reduces the list into lines, so that we can process them by line.\n * Performs in place.\n * @param {(string|Object)[]} words\n */\nconst reduceWordsToLines = (words) => {\n let rightIdx = words.findLastIndex((w) => isString(w) && !isEOL(w)) + 1;\n\n for (let i = rightIdx - 1; i >= 0; i--) {\n if (isString(words[i]) && !isEOL(words[i])) {\n continue;\n }\n if (isEOL(words[i])) {\n if (i !== rightIdx - 1) {\n words.splice(i + 1, rightIdx - i - 1, words.slice(i + 1, rightIdx).join(\"\"));\n }\n rightIdx = i;\n continue;\n }\n if (isObj(words[i])) {\n if (i !== rightIdx - 1) {\n words.splice(i + 1, rightIdx - i - 1, words.slice(i + 1, rightIdx).join(\"\"));\n }\n rightIdx = i;\n }\n }\n\n if (0 !== rightIdx) {\n words.splice(0, rightIdx, words.slice(0, rightIdx).join(\"\"));\n }\n};\n\n/**\n * Converts `\\n` to `
      ` self closing tag. Supply this as the last plugin in the preset lists\n *\n * @example converts all line breaks to br\n * ```ts\n * const output = bbob([preset(), lineBreakPlugin()]).process(input, {render}).html\n * ```\n * @example will not convert line breaks inside [nobr]\n * ```ts\n * const nobr = (node: TagNode) => {return { disableLineBreakConversion: true, content: node.content }}; \\\\ tag in preset\n * ...\n * const output = bbob([preset(), lineBreakPlugin()]).process(input, {render}).html\n * ```\n * @returns plugin to be used in BBob process\n */\nexport const lineBreakPlugin = () => {\n return (tree) => walk(tree);\n};\n","/**\n * Plugin that converts consecutive normal spaces (U+0020) to non-breaking spaces (U+00A0).\n * To use, put as function similar to the presets.\n *\n *\n * @example\n * ```ts\n * const output = bbob([preset(), , preserveWhitespace(), lineBreakPlugin()]).process(input, {render}).html\n * ```\n */\nimport { isStringNode } from \"@bbob/plugin-helper\";\n\n/**\n * Checks if input is an object\n * @param value input\n * @returns if value is an object\n */\nconst isObj = (value) => typeof value === \"object\";\n\n/**\n * Walks the tree of nodes. Checks for node of consecutive spaces. If found replaces every space in\n * node with a nonbreaking space.\n * Preserves multiple spaces so html won't truncate them.\n *\n * Walks through entire tree.\n * @param t tree of nodes to be processed\n * @returns modified tree\n */\nconst walk = (t) => {\n const tree = t;\n\n if (Array.isArray(tree)) {\n for (let idx = 0; idx < tree.length; idx++) {\n const child = walk(tree[idx]);\n if (Array.isArray(child)) {\n tree.splice(idx, 1, ...child);\n idx += child.length - 1;\n } else {\n tree[idx] = child;\n }\n }\n } else if (tree && isObj(tree) && tree.content) {\n walk(tree.content);\n }\n\n //Bbob breaks up nodes by the presence of normal spaces.\n //So a node with a normal space can only have normal spaces in that node.\n if (isStringNode(tree)) {\n if (tree.length > 1 && tree[0] === \" \") {\n let numSpaces = tree.length;\n return [String.fromCharCode(160).repeat(numSpaces)];\n }\n }\n\n return tree;\n};\n\n/**\n * Converts consecutive normal spaces (U+0020) to nonbreaking spaces (U+00A0).\n * Supply this as a plugin in the preset lists.\n *\n * @example converts consecutive normal spaces (U+0020) to nonbreaking spaces (U+00A0)\n * ```ts\n * const output = bbob([preset(), preserveWhitespace(), lineBreakPlugin()]).process(input, {render}).html\n * ```\n *\n * @returns plugin to be used in BBob process\n */\nexport const preserveWhitespace = () => {\n return (tree) => walk(tree);\n};\n","import { isTagNode } from \"@bbob/plugin-helper\";\n\nconst CONSECUTIVE_NEWLINE_REGEX = /\\n{2,}/gm;\n\n/**\n * Removes empty lines from a string\n * @param {string} text\n */\nconst removeEmptyLines = (text) => {\n return text.replace(CONSECUTIVE_NEWLINE_REGEX, \"\\n\");\n};\n\n/**\n * Removes empty lines from attributes\n * @type {import('@bbob/types').BBobPluginFunction}\n */\nexport const removeEmptyLinePlugin = (tree) => {\n return tree.walk((node) => {\n if (isTagNode(node) && node.attrs) {\n Object.keys(node.attrs).forEach((key) => {\n if (typeof node.attrs[key] === \"string\") {\n node.attrs[key] = removeEmptyLines(node.attrs[key]);\n }\n });\n }\n return node;\n });\n};\n","import { isTagNode } from \"@bbob/plugin-helper\";\nexport function process(tags, tree, core, options) {\n return tree.walk((node)=>{\n if (isTagNode(node)) {\n const tag = node.tag;\n const tagCallback = tags[tag];\n if (typeof tagCallback === \"function\") {\n return tagCallback(node, core, options);\n }\n }\n return node;\n });\n}\n/**\n * Create a preset plugin for @bbob/core\n */ function createPreset(defTags, processor = process) {\n const presetFactory = (opts)=>{\n presetFactory.options = Object.assign(presetFactory.options || {}, opts);\n function presetExecutor(tree, core) {\n return processor(defTags, tree, core, presetFactory.options || {});\n }\n presetExecutor.options = presetFactory.options;\n return presetExecutor;\n };\n presetFactory.extend = function presetExtend(callback) {\n const newTags = callback(defTags, presetFactory.options);\n return createPreset(newTags, processor);\n };\n return presetFactory;\n}\nexport { createPreset };\nexport default createPreset;\n","import { isStringNode, isTagNode, TagNode } from \"@bbob/plugin-helper\";\nimport {\n generateGUID,\n preprocessAttr,\n regexIndexOf,\n toNode,\n toOriginalEndTag,\n toOriginalStartTag,\n} from \"../utils/common\";\n\nconst SLIDE_TITLE_OPEN = Symbol(\"slide-title-open\");\nconst SLIDE_TITLE_CLOSE = Symbol(\"slide-title-close\");\nconst SLIDE_CLOSE = Symbol(\"slide-close\");\nconst SLIDE_REGEX =\n /(?\\{slide=)|(?\\})|(?\\{\\/slide\\})/i;\n\n/**\n * Adds the accordion tag\n * [accordion]{slide=name}content{/slide}[/accordion]\n *\n * [accordion][slide=name]content[/slide][/accordion]\n */\nconst accordion = (node, options) => {\n const groupId = generateGUID();\n\n // add support for existing {slide} tags style, due to copious amounts of existing content\n // also the only way to get true custom content inside a slide due to nesting limitations\n const markedContent = generateSlideMarkersFromContent(node.content);\n const generatedSlides = generateSlidesFromMarkers(markedContent);\n\n const filteredContent = generatedSlides\n .filter((n) => isTagNode(n) && n.tag === \"slide\")\n .map((content) => {\n content.isValid = true;\n content.groupId = groupId;\n return content;\n });\n if (!filteredContent.length) {\n // no [slide] tags found\n return [\n toOriginalStartTag(node, options.data.raw),\n ...node.content,\n toOriginalEndTag(node, options.data.raw),\n ];\n }\n const attrs = preprocessAttr(node, options.data.raw);\n\n if (attrs._default) {\n /** @type {string[]} */\n const customSettings = attrs._default.split(\"|\").map((s) => s.trim());\n const lastValidAlignment = customSettings\n .filter((s) => [\"bright\", \"bcenter\", \"bleft\", \"fleft\", \"fright\"].includes(s))\n .pop();\n if (lastValidAlignment) {\n attrs.align ??= lastValidAlignment;\n }\n\n if (\n customSettings.some((s) => s.endsWith(\"px\")) ||\n customSettings.some((s) => s.endsWith(\"%\"))\n ) {\n attrs.width ??= customSettings.find((s) => s.endsWith(\"px\") || s.endsWith(\"%\"));\n }\n }\n\n let classes = attrs.align?.toLowerCase() || \"\";\n let style = \"\";\n if (attrs.width?.endsWith(\"px\") || attrs.width?.endsWith(\"%\")) {\n style = `width: ${attrs.width};`;\n }\n return toNode(\n \"div\",\n { class: \"bb-accordion \" + classes, \"data-group-id\": groupId, style },\n filteredContent,\n );\n};\n\n/**\n * Locates and splits all {slide} tag components into their respective parts while preserving remaining content\n * @param {(TagNode|string)[]} contentArr node content of the accordion tag\n *\n * @example\n * ```\n * [\"{slide=test}\", \"lorem ipsum\", \"{/slide}\"]\n * ```\n * becomes\n * ```\n * [SLIDE_TITLE_OPEN, \"test\", SLIDE_TITLE_CLOSE, \"lorem ipsum\", SLIDE_CLOSE]\n * ```\n */\nfunction generateSlideMarkersFromContent(contentArr) {\n contentArr = [...contentArr]; // shallow clone. object nodes are not modified anyway\n\n const newArr = [];\n while (contentArr.length > 0) {\n const content = contentArr[0];\n if (isTagNode(content)) {\n newArr.push(contentArr.shift());\n continue;\n }\n const foundIndex = regexIndexOf(content, SLIDE_REGEX);\n if (foundIndex === -1) {\n newArr.push(contentArr.shift());\n continue;\n }\n const match = content.match(SLIDE_REGEX);\n const preContent = content.slice(0, foundIndex);\n const postContent = content.slice(foundIndex + match[0].length);\n if (preContent.length) {\n newArr.push(preContent);\n }\n if (match.groups.slideTitleOpen) {\n newArr.push(SLIDE_TITLE_OPEN);\n }\n if (match.groups.slideTitleClose) {\n newArr.push(SLIDE_TITLE_CLOSE);\n }\n if (match.groups.slideClose) {\n newArr.push(SLIDE_CLOSE);\n }\n if (postContent.length) {\n contentArr[0] = postContent;\n } else {\n contentArr.shift();\n }\n }\n\n return newArr;\n}\n\n/**\n * Generates slide nodes from markers\n * @param {(string | typeof SLIDE_TITLE_OPEN | typeof SLIDE_TITLE_CLOSE | typeof SLIDE_CLOSE | TagNode)[]} markedContent\n */\nfunction generateSlidesFromMarkers(markedContent) {\n const nodes = [];\n let currentSlide = null;\n /** @type {typeof SLIDE_TITLE_OPEN | typeof SLIDE_TITLE_CLOSE | null} */\n let prevMarker = null;\n for (const content of markedContent) {\n if (content === SLIDE_TITLE_OPEN && prevMarker === null) {\n currentSlide = TagNode.create(\"slide\");\n currentSlide.content = [];\n currentSlide.customTitle = [];\n prevMarker = SLIDE_TITLE_OPEN;\n } else if (content === SLIDE_TITLE_CLOSE && prevMarker === SLIDE_TITLE_OPEN) {\n prevMarker = SLIDE_TITLE_CLOSE;\n continue;\n } else if (content === SLIDE_CLOSE && currentSlide && prevMarker === SLIDE_TITLE_CLOSE) {\n nodes.push(currentSlide);\n currentSlide = null;\n prevMarker = null;\n } else if (currentSlide) {\n if (prevMarker === SLIDE_TITLE_OPEN) {\n currentSlide.customTitle.push(markerToString(content));\n } else {\n currentSlide.content.push(markerToString(content));\n }\n } else {\n // no slide open, just add content\n nodes.push(markerToString(content));\n }\n }\n return nodes;\n}\n\n/**\n * Processes content into a string. Catches stray markers and converts them back into a string\n * @param {string | typeof SLIDE_TITLE_OPEN | typeof SLIDE_TITLE_CLOSE | typeof SLIDE_CLOSE} marker\n * @returns expected string\n */\nfunction markerToString(marker) {\n switch (marker) {\n case SLIDE_TITLE_OPEN:\n return \"{slide=\";\n case SLIDE_TITLE_CLOSE:\n return \"}\";\n case SLIDE_CLOSE:\n return \"{/slide}\";\n default:\n return marker;\n }\n}\n\nconst slide = (node, options) => {\n if (!node.isValid) {\n // not inside an [accordion] tag\n return [\n toOriginalStartTag(node, options.data.raw),\n ...node.content,\n toOriginalEndTag(node, options.data.raw),\n ];\n }\n const attrs = preprocessAttr(node, options.data.raw);\n let title = [attrs.title || attrs._default || \"Slide\"];\n let isOpen = !!attrs.open || false;\n let titleAlign = attrs.left ? \"left\" : attrs.right ? \"right\" : attrs.center ? \"center\" : \"left\";\n if (node.customTitle?.length) {\n // slide was created from markers\n title = node.customTitle;\n // pull out old options from title if they exist\n const possibleOptions = title\n .filter((t) => typeof t === \"string\")\n .join(\"\")\n .toLowerCase()\n .split(\"|\")\n .map((s) => s.trim());\n if (possibleOptions.includes(\"open\")) {\n isOpen = true;\n }\n if (possibleOptions.includes(\"right\")) {\n titleAlign = \"right\";\n }\n if (possibleOptions.includes(\"center\")) {\n titleAlign = \"center\";\n }\n if (possibleOptions.includes(\"left\")) {\n titleAlign = \"left\";\n }\n title = title.map((t) => {\n if (isStringNode(t)) {\n t = t.replace(/\\|(open|right|center|left)/gi, \"\");\n }\n return t;\n });\n }\n return [\n toNode(\"details\", { class: \"bb-slide\", open: isOpen }, [\n toNode(\n \"summary\",\n { class: \"bb-slide-title\", style: `text-align: ${titleAlign}; ${attrs.style || \"\"}` },\n title,\n ),\n toNode(\"div\", { class: \"bb-slide-content\" }, node.content),\n ]),\n ];\n};\n\nexport const accordionTags = { accordion, slide };\n","import { toNode } from \"../utils/common\";\n/**\n * @file Adds [left], [center], and [right] to bbcode\n * @example [center]content[/center]\n */\nexport const alignment = {\n left: (node) => toNode(\"div\", { class: \"bb-left\" }, node.content),\n center: (node) => toNode(\"div\", { class: \"bb-center\" }, node.content),\n right: (node) => toNode(\"div\", { class: \"bb-right\" }, node.content),\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n/**\n * @file Adds [a] and [goto] to bbcode\n * @example [a=your_anchor_name]An anchor[/a] [goto=your_anchor_name]Jump to an anchor[/goto]\n */\nexport const anchor = {\n // name is not valid in HTML5; however, it correctly displays back while id does not\n a: (node, options) => {\n const attrs = preprocessAttr(node, options.data.raw)._default || \"\";\n return toNode(\n \"a\",\n { id: `user-anchor-${attrs.trim()}`, name: `user-anchor-${attrs.trim()}` },\n node.content,\n );\n },\n goto: (node, options) => {\n const attrs = preprocessAttr(node, options.data.raw)._default || \"\";\n return toNode(\"a\", { href: `#user-anchor-${attrs.trim()}` }, node.content);\n },\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\nconst WEB_FONTS = [\n \"arial\",\n \"book antiqua\",\n \"courier new\",\n \"georgia\",\n \"tahoma\",\n \"times new roman\",\n \"trebuchet ms\",\n \"verdana\",\n];\nconst VALID_FONT_STYLES = {\n thin: \"100\",\n extralight: \"200\",\n light: \"300\",\n regular: \"400\",\n medium: \"500\",\n semibold: \"600\",\n bold: \"700\",\n extrabold: \"800\",\n black: \"900\",\n};\n// registered axis tags https://learn.microsoft.com/en-us/typography/opentype/spec/dvaraxisreg#registered-axis-tags\nconst REGISTERED_AXIS = [\"ital\", \"opsz\", \"slnt\", \"wdth\", \"wght\"];\n\nconst AXES_REGEX = /(?[a-zA-Z]*)?\\s?(?[0-9]*)?\\s?(?italic)?/;\n\nconst axesParser = (attrs) => {\n let axes = {\n ital: 0,\n wght: 400,\n };\n\n if (attrs?.style) {\n // user just copy pasted the name of the style on the google font site, probably\n const style = attrs.style.trim().toLowerCase();\n const matches = AXES_REGEX.exec(style).groups || {};\n if (matches?.italic) {\n axes.ital = 1;\n }\n\n const weight = matches.weight;\n if (weight && weight >= 0 && weight <= 900) {\n axes.wght = weight;\n } else if (Object.keys(VALID_FONT_STYLES).includes(matches.named_weight || \"\")) {\n axes.wght = VALID_FONT_STYLES[matches.named_weight];\n }\n\n axes = {\n ...axes,\n ...Object.fromEntries(Object.entries(attrs).filter(([key]) => REGISTERED_AXIS.includes(key))),\n };\n }\n return axes;\n};\n\n/**\n * Create google font api url\n * @param {string} family name of font\n * @param {object} axes custom font axes\n */\nconst googleFontApiBuild = (family, axes) => {\n family = family.replaceAll(\" \", \"+\");\n // google fonts requires axes names to be in alphabetical order\n axes = Object.keys(axes)\n .sort()\n .reduce((obj, key) => {\n obj[key] = axes[key];\n return obj;\n }, {});\n const axesList = Object.keys(axes).join(\",\") + \"@\" + Object.values(axes).join(\",\");\n return \"https://fonts.googleapis.com/css2?family=\" + family + \":\" + axesList;\n};\n\nexport const font = (node, options) => {\n const attrs = preprocessAttr(node, options.data.raw);\n const fontFamily = attrs?._default || attrs?.family || attrs?.name;\n if (!fontFamily || fontFamily.trim() === \"\") {\n return node.content;\n }\n if (WEB_FONTS.includes(fontFamily.trim().toLowerCase())) {\n return toNode(\"span\", { style: `font-family: '${fontFamily}'` }, node.content);\n }\n\n const axes = axesParser(attrs);\n const url = googleFontApiBuild(fontFamily, axes);\n options.data.fonts.add(url);\n\n const italic = axes.ital === 1 ? \"italic\" : \"normal\";\n\n const custom = Object.entries(axes).filter(([key]) => key !== \"wght\" && key !== \"ital\");\n let fontVar = \"\";\n if (custom.length) {\n fontVar =\n \"font-variation-settings: \" + custom.map(([key, val]) => `'${key}' ${val}`).join(\", \") + \";\";\n }\n\n return toNode(\n \"span\",\n {\n style: `font-family: '${fontFamily}'; font-weight: ${axes.wght}; font-style: ${italic}; ${fontVar}`,\n \"data-font\": url,\n },\n node.content\n );\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * Parse the user provided height and return a valid height value\n * @param {Number} heightValue obtains the input of the user entered height (default is 700)\n * @returns A validated number less than 0.\n */\nfunction parseHeight(heightValue) {\n const maxHeight = 700;\n const parsedHeight =\n heightValue && heightValue.trim() !== \"\" ? heightValue.replace(/[^\\d.]/g, \"\") : 0;\n\n if (parsedHeight && parsedHeight >= 0 && parsedHeight <= maxHeight) {\n return parsedHeight;\n } else {\n // if the value = 0 then nothing will be returned\n return parsedHeight === 0 ? 0 : maxHeight;\n }\n}\n\n/**\n * @file Adds [heightrestrict] to bbcode\n * @example [heightrestrict=50]content[/heightrestrict]\n */\nexport const heightrestrict = (node) => {\n const attrs = preprocessAttr(node)._default;\n const heightInput = parseHeight(attrs).toString();\n // Return image's default size if heightrestrict did not involve a valid value\n return heightInput === \"0\"\n ? toNode(\"div\", { class: \"bb-height-restrict\" }, node.content)\n : toNode(\n \"div\",\n { class: \"bb-height-restrict\", style: `height: ${heightInput}px;` },\n node.content,\n );\n};\n","import { toNode } from \"../utils/common\";\n/**\n * @file Adds [mail] to bbcode\n * @param {string} [type=\"send\"] Denotes type of mail either send or receive\n * @param {string} [person=\"Unknown\"] Denotes the person in the To/From field\n * @param {string} [subject=\"Empty\"] Denotes the subject line of the email\n * @example [mail type=\"send\" person=\"John Doe\" subject=\"Hello World\"]content[/mail]\n */\n\nconst parseEmailContent = (content) => {\n return toNode(\"div\", { class: \"bb-email-content\" }, content);\n};\n\nconst parseEmailSubject = (subject) => {\n return toNode(\"div\", { class: \"bb-email-subject\" }, subject);\n};\n\nconst parseEmailPerson = (person) => {\n return toNode(\"div\", { class: \"bb-email-address\" }, person);\n};\n\nconst emailHeader = toNode(\"div\", { class: \"bb-email-header\" }, \"\");\nconst emailFooter = toNode(\n \"div\",\n { class: \"bb-email-footer\" },\n toNode(\"div\", { class: \"bb-email-button\" }, \"\"),\n);\n\nexport const mail = (node) => {\n const attributes = node.attrs;\n let mailAttr = {\n mailOption: (attributes.type || \"send\").toLowerCase(),\n person: attributes.person || \"Unknown\",\n subject: attributes.subject || \"Empty\",\n };\n\n return toNode(\n \"div\",\n {\n class: \"bb-email\",\n \"data-bb-email\": mailAttr.mailOption,\n },\n [\n emailHeader,\n parseEmailPerson(mailAttr.person),\n parseEmailSubject(mailAttr.subject),\n parseEmailContent(node.content),\n emailFooter,\n ],\n );\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * @file Adds [row][column] to bbcode\n * @example Adds [row][column][/column][/row]\n */\nexport const rowcolumn = {\n row: (node) => toNode(\"div\", { class: \"bb-row\" }, node.content),\n column: (node, options) => {\n const columnAttrs = preprocessAttr(node, options.data.raw)._default || \"8\";\n const columnStyle = columnAttrs.startsWith(\"span\")\n ? `column-width-${columnAttrs}`\n : `column-width-span${columnAttrs}`;\n return toNode(\"div\", { class: `bb-column`, \"data-span\": columnStyle }, node.content);\n },\n};\n","import { preprocessAttr } from \"../utils/common\";\n\nconst EVENTS = [\n \"init\",\n \"click\",\n \"change\",\n \"input\",\n \"dblclick\",\n \"mouseenter\",\n \"mouseleave\",\n \"scroll\",\n];\n\n/**\n * Script tag\n *\n * [script]content[/script]\n *\n * [script class=\"id\" on=\"event\" version=\"2\"]content[/script]\n */\nexport const script = (node, options) => {\n const attrs = preprocessAttr(node, options.data.raw);\n\n if (!options.data.previewing && !options.data.commonGUID) {\n // create a common GUID for the post\n // only applicable for div, style, and script tags\n // this is to prevent the same class name from being used in different posts\n options.data.commonGUID = \"post-\" + Math.random().toString(36).substring(2, 7);\n }\n const classSuffix = options.data.previewing ? \"preview\" : options.data.commonGUID;\n\n const onEvent =\n (EVENTS.includes(attrs.on?.toLowerCase() || \"init\") && attrs.on?.toLowerCase()) || \"init\";\n\n const scriptSetup = {\n id: classSuffix,\n class: attrs.class || \"\",\n on: onEvent,\n version: attrs.version || \"\",\n content: node.content.join(\"\"),\n };\n options.data.bbscripts.push(scriptSetup);\n\n return [];\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * Parses an inputted size value and returns the formatted valid font size\n * @param {string} fontValue the input of the size\n */\nfunction parseFontSize(fontValue) {\n let value;\n let fontSize = { valid: true };\n const parsedSize = /(\\d+\\.?\\d?)(px|rem)?/i.exec(fontValue);\n const sizeRanges = {\n px_max: 36,\n px_min: 8,\n rem_max: 3,\n rem_min: 0.2,\n unitless_max: 7,\n unitless_min: 1,\n };\n\n if (parsedSize && (value = parsedSize[1])) {\n fontSize.unit = (parsedSize[2] || \"\").toLowerCase();\n switch (fontSize.unit) {\n case \"px\":\n if (value > sizeRanges.px_max) {\n value = sizeRanges.px_max;\n } else if (value < sizeRanges.px_min) {\n value = sizeRanges.px_min;\n }\n break;\n case \"rem\":\n if (value > sizeRanges.rem_max) {\n value = sizeRanges.rem_max;\n } else if (value < sizeRanges.rem_min) {\n value = sizeRanges.rem_min;\n }\n break;\n default:\n if ((fontSize.valid = fontValue.length === value.length)) {\n if (value > sizeRanges.unitless_max) {\n value = sizeRanges.unitless_max;\n } else if (value < sizeRanges.unitless_min) {\n value = sizeRanges.unitless_min;\n }\n }\n break;\n }\n\n fontSize.value = value;\n }\n return fontSize;\n}\n\nexport const size = (node) => {\n const input = preprocessAttr(node)._default;\n const fontSize = parseFontSize(input);\n if (!fontSize.valid) {\n return node.content;\n }\n let outputAttr = {};\n if (fontSize.unit) {\n outputAttr = { style: `font-size: ${fontSize.value}${fontSize.unit}` };\n } else {\n outputAttr = { \"data-size\": fontSize.value };\n }\n return toNode(\"span\", outputAttr, node.content);\n};\n","import { isTagNode } from \"@bbob/plugin-helper\";\nimport { preprocessAttr, toNode, toRawTag } from \"../utils/common\";\n\n/**\n * @file Adds textmessage to bbcode\n * @example [textmessage=Recipient][message=them]Hi [/message][message=me] Hey![/message][/textmessage]\n */\n\nconst ACCEPTED_OPTIONS = [\"me\", \"them\", \"right\", \"left\"];\nexport const textmessage = {\n textmessage: (node, options) => {\n const messageList = node.content.filter(\n (contentNode) => isTagNode(contentNode) && contentNode.tag === \"message\"\n );\n messageList.forEach((messageNode) => {\n messageNode.isValid = true;\n });\n\n if (!messageList.length) {\n return toRawTag(node, options.data.raw);\n }\n\n const attr = preprocessAttr(node, options.data.raw)._default || \"Recipient\";\n const recipient = attr && attr.trim() !== \"\" ? attr : \"Recipient\";\n return toNode(\"div\", { class: \"bb-textmessage\" }, [\n toNode(\"div\", { class: \"bb-textmessage-name\" }, recipient),\n toNode(\"div\", { class: \"bb-textmessage-overflow\" }, [\n toNode(\"div\", { class: \"bb-textmessage-content\" }, node.content),\n ]),\n ]);\n },\n message: (node, options) => {\n if (!node.isValid) {\n return toRawTag(node, options.data.raw);\n }\n\n let option = preprocessAttr(node, options?.data?.raw)?._default?.toLowerCase();\n if (!ACCEPTED_OPTIONS.includes(option) || option === \"right\") {\n option = \"me\";\n }\n if (option === \"left\") {\n option = \"them\";\n }\n\n const senderAttrs = option === \"me\" ? \"bb-message-me\" : \"bb-message-them\";\n return toNode(\"div\", { class: senderAttrs }, [\n toNode(\"div\", { class: \"bb-message-content\" }, node.content),\n ]);\n },\n};\n","import { createPreset } from \"@bbob/preset\";\nimport { accordionTags } from \"./tags/accordion\";\nimport { alignment } from \"./tags/alignment\";\nimport { anchor } from \"./tags/anchor\";\nimport { animation, keyframe } from \"./tags/animation\";\nimport { bg } from \"./tags/background\";\nimport { block } from \"./tags/block\";\nimport { blockquote } from \"./tags/blockquote\";\nimport { border } from \"./tags/border\";\nimport { centerblock } from \"./tags/centerblock\";\nimport { check } from \"./tags/check\";\nimport { classStyle } from \"./tags/class\";\nimport { code, icode, savenl } from \"./tags/code\";\nimport { color } from \"./tags/color\";\nimport { comment } from \"./tags/comment\";\nimport { bold, italic, strike, underline } from \"./tags/discourse-core-replacement\";\nimport { div } from \"./tags/div\";\nimport { divide } from \"./tags/divide\";\nimport { fieldset } from \"./tags/fieldset\";\nimport { font } from \"./tags/font\";\nimport { fa } from \"./tags/fontawesome\";\nimport { h, h1, h2, h3, h4, h5, h6, sh } from \"./tags/header\";\nimport { heightrestrict } from \"./tags/heightrestrict\";\nimport { highlight } from \"./tags/highlight\";\nimport { imagefloat } from \"./tags/imagefloat\";\nimport { justify } from \"./tags/justify\";\nimport { br, nobr } from \"./tags/lineBreak\";\nimport { mail } from \"./tags/mail\";\nimport { newspaper } from \"./tags/newspaper\";\nimport { note } from \"./tags/note\";\nimport { ooc } from \"./tags/ooc\";\nimport { pindent } from \"./tags/pindent\";\nimport { plain } from \"./tags/plain\";\nimport { print } from \"./tags/print\";\nimport { progress } from \"./tags/progress\";\nimport { quote } from \"./tags/quote\";\nimport { rowcolumn } from \"./tags/rowcolumn\";\nimport { script } from \"./tags/script\";\nimport { scroll } from \"./tags/scroll\";\nimport { side } from \"./tags/side\";\nimport { size } from \"./tags/size\";\nimport { inlinespoiler, spoiler } from \"./tags/spoiler\";\nimport { sub } from \"./tags/subscript\";\nimport { sup } from \"./tags/superscript\";\nimport { tab, tabs } from \"./tags/tabs\";\nimport { textmessage } from \"./tags/textmessage\";\nimport { thinprogress } from \"./tags/thinprogress\";\n\nconst tags = {\n ...accordionTags,\n ...alignment,\n ...anchor,\n animation,\n bg,\n block,\n blockquote,\n border,\n br,\n centerblock,\n check,\n class: classStyle,\n code,\n color,\n comment,\n div,\n divide,\n fieldset,\n fa,\n font,\n h,\n h1,\n h2,\n h3,\n h4,\n h5,\n h6,\n heightrestrict,\n highlight,\n icode,\n imagefloat,\n inlinespoiler,\n justify,\n keyframe,\n mail,\n newspaper,\n nobr,\n note,\n ooc,\n pindent,\n plain,\n print,\n progress,\n quote,\n ...rowcolumn,\n thinprogress,\n savenl,\n sh,\n script,\n scroll,\n side,\n size,\n spoiler,\n sub,\n sup,\n tab,\n tabs,\n ...textmessage,\n\n // discourse core replacement tags\n b: bold,\n i: italic,\n u: underline,\n s: strike,\n};\n\nconst availableTags = Object.keys(tags);\nconst preventParsing = [\"plain\", \"code\", \"icode\", \"class\", \"fa\"];\n\nconst preset = createPreset(tags);\n\nexport { availableTags, tags, preset, preventParsing };\nexport default preset;\n","import { isStringNode, isTagNode } from \"@bbob/plugin-helper\";\nimport { preprocessAttr, toOriginalEndTag, toOriginalStartTag } from \"../utils/common\";\n\n/**\n * Renders css Keyframes\n *\n * [animation=name][keyframe=0]color: red[/keyframe][/animation]\n */\nexport const animation = (node, options) => {\n if (!options.data.previewing && !options.data.commonGUID) {\n // create a common GUID for the post\n // only applicable for div, style, and script tags\n // this is to prevent the same class name from being used in different posts\n options.data.commonGUID = \"post-\" + Math.random().toString(36).substring(2, 7);\n }\n const commonId = options.data.previewing ? \"preview\" : options.data.commonGUID;\n\n const name = preprocessAttr(node, options.data.raw)?._default || \"\";\n const keyframes = node.content\n .filter((n) => isTagNode(n) && n.tag === \"keyframe\")\n .map((content) => {\n content.isValid = true;\n /** @type {string} */\n const ident = preprocessAttr(content, options.data.raw)._default || \"\";\n content.ident = ident + (ident.match(/^\\d+$/) ? \"%\" : \"\");\n const cleanContent = content.content\n .filter(isStringNode)\n .join(\"\")\n .replaceAll(/[\\[\\]\\{\\}]/g, \"\");\n content.formatted = `${content.ident}{ ${cleanContent} }`;\n return content;\n });\n const keyframeContent = keyframes.map((n) => n.formatted).join(\"\\n\");\n const content = `@keyframes ${commonId}${name} { ${keyframeContent} }`;\n options.data.styles.push(content);\n return [];\n};\n\nexport const keyframe = (node, options) => {\n if (!node.isValid) {\n return [\n toOriginalStartTag(node, options.data.raw),\n ...node.content,\n toOriginalEndTag(node, options.data.raw),\n ];\n }\n return [];\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * Add [bg] tag\n * @example [bg=red]Hello[/bg]\n */\nexport const bg = (node, options) => {\n const color = preprocessAttr(node, options.data.raw)._default;\n return toNode(\n \"div\",\n {\n style: `background-color: ${color};`,\n class: \"bb-background\",\n },\n node.content,\n );\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * Add [block] tag\n * @example [block=treasure]content[/block]\n */\nexport const block = (node, options) => {\n const defaultOp = \"block\";\n const blockAttr = (preprocessAttr(node, options.data.raw)._default || defaultOp).toLowerCase();\n\n const OPTIONS = [\n \"block\",\n \"dice\",\n \"dice10\",\n \"setting\",\n \"warning\",\n \"storyteller\",\n \"announcement\",\n \"important\",\n \"question\",\n \"encounter\",\n \"information\",\n \"character\",\n \"treasure\",\n ];\n\n // Default to block option if user did not provide anything valid\n const blockOption = OPTIONS.includes(blockAttr) ? blockAttr : defaultOp;\n\n return toNode(\"table\", { class: \"bb-block\", \"data-bb-block\": blockOption }, [\n toNode(\"tbody\", [\n toNode(\"tr\", [\n toNode(\"td\", { class: \"bb-block-icon\" }),\n toNode(\"td\", { class: \"bb-block-content\" }, node.content),\n ]),\n ]),\n ]);\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * @file Adds [blockquote] to bbcode\n * @example [blockquote=author]content[/blockquote]\n */\nexport const blockquote = (node, options) => {\n const author = preprocessAttr(node, options.data.raw)._default || \"\";\n\n return toNode(\"div\", { class: \"bb-blockquote\" }, [\n toNode(\"div\", { class: \"bb-blockquote-left\" }),\n toNode(\"div\", { class: \"bb-blockquote-content\" }, [\n node.content,\n toNode(\"div\", { class: \"bb-blockquote-speaker\" }, author !== \"\" ? `- ${author}` : \"\"),\n ]),\n toNode(\"div\", { class: \"bb-blockquote-right\" }),\n ]);\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\nexport const border = (node, options) => {\n const val = preprocessAttr(node, options.data.raw)._default;\n return toNode(\n \"div\",\n {\n style: `border: ${val};`,\n class: \"bb-border\",\n },\n node.content,\n );\n};\n","import { toNode } from \"../utils/common\";\n\n/**\n * Creates a line break html
      tag\n */\nexport const br = () => {\n return toNode(\"br\", {}, null);\n};\n\n/**\n * Disables line breaks for given content\n * @example\n * ```\n * [nobr]test\n * test\n * test\n * [/nobr]\n *\n * test test test\n * ```\n */\nexport const nobr = (node) => {\n return { disableLineBreakConversion: true, content: node.content };\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\nexport const centerblock = (node, options) => {\n const percentageInput = preprocessAttr(node, options.data.raw)._default || \"50\";\n return toNode(\"div\", { style: `margin: 0 auto; width: ${percentageInput}%` }, node.content);\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\nexport const check = (node, options) => {\n const attrs = preprocessAttr(node, options.data.raw)._default || \"dot\";\n return toNode(\"div\", { class: `bb-check`, \"data-type\": attrs }, node.content);\n};\n","import { isStringNode } from \"@bbob/plugin-helper\";\nimport { preprocessAttr } from \"../utils/common\";\n\n/**\n * Class style tag\n *\n * [class=name]content[/class]\n * [class name=\"className\" state=\"psuedo-class\" minWidth=\"\" maxWidth=\"\"]content[/class]\n * [class name=\"className\" selector=\"\"]content[/class]\n */\nexport const classStyle = (node, options) => {\n const attrs = preprocessAttr(node);\n const nameAttr = attrs.name || attrs._default;\n\n if (!options.data.previewing && !options.data.commonGUID) {\n // create a common GUID for the post\n // only applicable for div, style, and script tags\n // this is to prevent the same class name from being used in different posts\n options.data.commonGUID = \"post-\" + Math.random().toString(36).substring(2, 7);\n }\n const classSuffix = options.data.previewing ? \"preview\" : options.data.commonGUID;\n const className = nameAttr + \"__\" + classSuffix;\n const content = node.content\n .filter(isStringNode)\n .map((s) => s.replaceAll(\"{post_id}\", classSuffix).replaceAll(/[\\[\\]\\{\\}]/g, \"\"));\n let selector = \"\";\n const mediaQuery = [];\n if (\n [\"hover\", \"focus\", \"active\", \"focus-within\", \"focus-visible\"].includes(\n attrs.state?.toLowerCase(),\n )\n ) {\n selector = \":\" + attrs.state.toLowerCase();\n }\n if (attrs.selector) {\n selector = attrs.selector.replace(/[,{}\\\\\\n]/g, \"\");\n }\n if (attrs.minWidth?.match(/^[0-9]+[a-z]+$/)) {\n // @media (min-width: )\n mediaQuery.push(`(min-width: ${attrs.minWidth})`);\n }\n if (attrs.maxWidth?.match(/^[0-9]+[a-z]+$/)) {\n // @media (max-width: )\n mediaQuery.push(`(max-width: ${attrs.maxWidth})`);\n }\n\n content.unshift(`.${className}${selector} {`);\n content.push(\"}\");\n if (mediaQuery.length) {\n content.unshift(`@media ${mediaQuery.join(\" and \")} {`);\n content.push(\"}\");\n }\n options.data.styles.push(content.join(\"\"));\n\n return [];\n};\n","import { preprocessAttr } from \"../utils/common\";\n\n/**\n * processes [code] tag and returns a fenced code block\n */\nexport const code = (node) => {\n const lang = preprocessAttr(node)._default || \"bbcode\";\n return {\n isWhitespaceSensitive: true,\n content: [\"```\" + lang + \"\\n\", node.content, \"\\n```\\n\"],\n };\n};\n\n/**\n * processes [icode] tag and returns inline code\n */\nexport const icode = (node) => {\n return {\n isWhitespaceSensitive: true,\n content: [\"`\", node.content, \"`\"],\n };\n};\n\n/**\n * Special tag to save newlines in code blocks. Used for hoisting code blocks\n */\nexport const savenl = (node) => {\n return {\n isWhitespaceSensitive: true,\n content: node.content,\n };\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\nexport const color = (node) => {\n const inputColor = preprocessAttr(node)._default || \"\";\n if (inputColor.trim() === \"\") {\n return node.content;\n }\n return toNode(\"span\", { style: `color: ${inputColor}` }, node.content);\n};\n","import { toNode } from \"../utils/common\";\n\n/**\n * @file Adds [comment] tag\n * @example [comment]Content[/comment]\n */\n\nconst comment = (node) => {\n return toNode(\"span\", { class: \"hidden\" }, node.content);\n};\n\nexport { comment };\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * Adds [div] tag\n * [div=css]Content[/div]\n * [div class=\"class\" style=\"css\"]Content[/div]\n */\nexport const div = (node, options) => {\n if (node.gen) {\n // node is actually a generated node \"div\" made by another tag\n // don't process it\n return node;\n }\n const attrs = preprocessAttr(node, options.data.raw);\n const style = attrs.style || attrs._default;\n const classAttrs = attrs.class;\n if (!classAttrs?.trim()) {\n return toNode(\n \"div\",\n {\n style,\n },\n node.content,\n );\n }\n\n if (!options.data.previewing && !options.data.commonGUID) {\n // create a common GUID for the post\n // only applicable for div, style, and script tags\n // this is to prevent the same class name from being used in different posts\n options.data.commonGUID = \"post-\" + Math.random().toString(36).substring(2, 7);\n }\n const classSuffix = options.data.previewing ? \"preview\" : options.data.commonGUID;\n const classNames = classAttrs\n .split(\" \")\n .map((c) => c + \"__\" + classSuffix)\n .join(\" \");\n\n return toNode(\n \"div\",\n {\n class: classNames,\n style,\n },\n node.content,\n );\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\nexport const divide = (node) => {\n const type = (preprocessAttr(node)._default || \"\").toLowerCase();\n return toNode(\n \"span\",\n {\n class: \"bb-divide\",\n \"data-type\": type,\n },\n node.content,\n );\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * @file Adds [fieldset] to bbcode\n * @example [fieldset=title]content[/fieldset]\n */\nexport const fieldset = (node, options) => {\n const title = preprocessAttr(node, options.data.raw)._default || \"\";\n return toNode(\"fieldset\", { class: \"bb-fieldset\" }, [\n toNode(\"legend\", { class: \"bb-fieldset-legend\" }, title),\n toNode(\"div\", { class: \"bb-fieldset\" }, node.content),\n ]);\n};\n","import { toNode } from \"../utils/common\";\n\n/**\n * Adds [fa] tag\n * [fa]fa-icon[/fa]\n * [fa style=\"\" fa-transform=\"\"]fa-solid fa-icon[/fa]\n * [fa primary-color=\"\" secondary-color=\"\" primary-opacity=\"\" secondary-opacity=\"\" rotate-angle=\"\"]fa-duotone fa-icon[/fa]\n */\nexport const fa = (node) => {\n const attrs = node.attrs;\n let style = attrs.style || \"\";\n style += attrs[\"primary-color\"] ? `--fa-primary-color: ${attrs[\"primary-color\"]};` : \"\";\n style += attrs[\"secondary-color\"] ? `--fa-secondary-color: ${attrs[\"secondary-color\"]};` : \"\";\n style += attrs[\"primary-opacity\"] ? `--fa-primary-opacity: ${attrs[\"primary-opacity\"]};` : \"\";\n style += attrs[\"secondary-opacity\"]\n ? `--fa-secondary-opacity: ${attrs[\"secondary-opacity\"]};`\n : \"\";\n style += attrs[\"rotate-angle\"] ? `--fa-rotate-angle: ${attrs[\"rotate-angle\"]};` : \"\";\n\n return toNode(\n \"i\",\n {\n \"data-bbcode-fa\": null,\n },\n [\n toNode(\n \"i\",\n {\n class: (node.content || []).join(\"\"),\n style,\n \"data-fa-transform\": attrs[\"fa-transform\"] || \"\",\n },\n [],\n ),\n ],\n );\n};\n","import { toNode } from \"../utils/common\";\n\n/**\n * @file Adds Header to bbcode\n * @example [h]content[/h], [h2]content[/h2], [h3]content[/h3],\n * [h4]content[/h4], [h5]content[/h5], [h6]content[/h6].\n */\n\nconst h = (node) => {\n return toNode(\"h1\", {}, node.content);\n};\n\nconst h1 = (node) => {\n return toNode(\"h1\", {}, node.content);\n};\n\nconst h2 = (node) => {\n return toNode(\"h2\", {}, node.content);\n};\n\nconst sh = (node) => {\n return toNode(\"h2\", {}, node.content);\n};\n\nconst h3 = (node) => {\n return toNode(\"h3\", {}, node.content);\n};\n\nconst h4 = (node) => {\n return toNode(\"h4\", {}, node.content);\n};\n\nconst h5 = (node) => {\n return toNode(\"h5\", {}, node.content);\n};\n\nconst h6 = (node) => {\n return toNode(\"h6\", {}, node.content);\n};\n\nexport { h, sh, h1, h2, h3, h4, h5, h6 };\n","import { toNode } from \"../utils/common\";\n/**\n * @file Adds [highlight] to bbcode\n * @example [highlight]content[/highlight]\n */\nexport const highlight = (node) => {\n return toNode(\"span\", { class: \"bb-highlight\" }, node.content);\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n/**\n * @file Adds [imagefloat] to bbcode\n * @exmaple [imagefloat=left]content[/imagefloat]\n */\nexport const imagefloat = (node) => {\n const attrs = preprocessAttr(node)._default || \"\";\n return toNode(\"div\", { class: `bb-float-${attrs}` }, node.content);\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n/**\n * @file Adds [spoiler] and [inlinespoiler] to bbcode\n *\n * Defaults to \"Spoiler\" name if no title provided\n *\n * @example `[spoiler=Title]text[/spoiler]`\n * @example `[inlinespoiler]hidden content[/inlinespoiler]\n */\n\nexport const spoiler = (node, options) => {\n const providedTitle = preprocessAttr(node, options.data.raw)._default;\n const title = \"Spoiler\" + (providedTitle ? `: ${providedTitle}` : \"\");\n\n /**\n *
      \n * Title\n *
      \n * lorem ipsum\n *
      \n *
      \n */\n return toNode(\"details\", { class: \"bb-spoiler\" }, [\n toNode(\"summary\", {}, title),\n toNode(\"div\", { class: \"bb-spoiler-content\" }, node.content),\n ]);\n};\n\nexport const inlinespoiler = (node) => {\n return toNode(\"span\", { class: \"bb-inline-spoiler\" }, node.content);\n};\n","import { toNode } from \"../utils/common\";\n\n/**\n * @file Adds [justify] to bbcode\n * @example [justify]content[/justify]\n */\nexport const justify = (node) => {\n return toNode(\"div\", { class: \"bb-justify\" }, node.content);\n};\n","import { toNode } from \"../utils/common\";\n/**\n * @file Adds [newspaper] to bbcode\n * @example [newspaper]content[/newspaper]\n */\nexport const newspaper = (node) => {\n return toNode(\"div\", { class: \"bb-newspaper\" }, node.content);\n};\n","import { toNode } from \"../utils/common\";\n/**\n * @file Adds [note] to bbcode\n * @example [note]content[/note]\n */\n\nexport const note = (node) => {\n return toNode(\"div\", { class: \"bb-note\" }, [\n toNode(\"div\", { class: \"bb-note-tape\" }, \"\"),\n toNode(\"div\", { class: \"bb-note-content\" }, [\n node.content,\n toNode(\"div\", { class: \"bb-note-footer\" }, \"\"),\n ]),\n ]);\n};\n","import { toNode } from \"../utils/common\";\n\n/**\n * @file Adds [ooc] to bbcode\n * @example [ooc]content[/ooc]\n */\nexport const ooc = (node) => {\n return toNode(\n \"div\",\n {\n class: \"bb-ooc\",\n },\n node.content,\n );\n};\n","import { toNode } from \"../utils/common\";\n/**\n * @file Adds [pindent] to bbcode\n * @example [pindent]content[/pindent]\n */\nexport const pindent = (node) => {\n return toNode(\"span\", { class: \"bb-pindent\" }, node.content);\n};\n","/**\n * [plain] bbcode tag that prevents parsing of inner tags\n * @example\n * ```\n * [plain]This is [b]bold[/b] and [i]italic[/i][/plain]\n * ```\n * outputs to\n * ```\n * This is [b]bold[/b] and [i]italic[/i]\n * ```\n */\nexport const plain = (node) => {\n return node.content;\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * Add [print] tag\n * @example [print=lined]content[/print]\n */\nexport const print = (node) => {\n const defaultOp = \"print\";\n const printAttr = (preprocessAttr(node)._default || defaultOp).toLowerCase();\n\n const OPTIONS = [\"print\", \"line\", \"graph\", \"parchment\"];\n\n // Default to print if option is not valid\n const printOption = OPTIONS.includes(printAttr) ? printAttr : defaultOp;\n\n return toNode(\n \"div\",\n { class: printOption === defaultOp ? `bb-print` : `bb-print-${printOption}` },\n node.content,\n );\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * @file Adds [progress] to bbcode\n * @exmaple [progress=percentageInt]content[/progress]\n */\nexport const progress = (node) => {\n const percentageInt = preprocessAttr(node)._default;\n return toNode(\"div\", { class: \"bb-progress\" }, [\n toNode(\"div\", { class: \"bb-progress-text\" }, node.content),\n toNode(\"div\", { class: \"bb-progress-bar\", style: `width: calc(${percentageInt}% - 6px)` }, \"\"),\n toNode(\"div\", { class: \"bb-progress-bar-other\" }, \"\"),\n ]);\n};\n","import { preprocessAttr } from \"../utils/common\";\n\n/**\n * rebuild the [quote] tag so that markdown-it engine can parse it for itself\n */\nexport const quote = (node, options) => {\n const attrs = preprocessAttr(node, options.data.raw);\n if (node.content[0] === \"\\n\") {\n node.content.shift();\n }\n return [`\\n[${node.tag}=\"${attrs._default}\"]\\n\\n`, ...node.content, \"\\n\\n[/quote]\\n\"];\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * @file Adds [thinprogress] to bbcode\n * @exmaple [thinprogress=percentageInt]content[/progthinprogressress]\n */\nexport const thinprogress = (node, options) => {\n const percentageInt = preprocessAttr(node, options.data.raw)._default;\n return toNode(\"div\", { class: \"bb-progress-thin\" }, [\n toNode(\"div\", { class: \"bb-progress-text\" }, node.content),\n toNode(\"div\", { class: \"bb-progress-bar\", style: `width: calc(${percentageInt}% - 6px)` }, \"\"),\n toNode(\"div\", { class: \"bb-progress-bar-other\" }, \"\"),\n ]);\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * Parse the user provided height and return a valid height value\n * @param {Number} heightValue obtains the input of the user entered height (default is 700)\n * @returns A validated number less than 0.\n */\nfunction parseHeight(heightValue) {\n const maxHeight = 700;\n const parsedHeight =\n heightValue && heightValue.trim() !== \"\" ? heightValue.replace(/[^\\d.]/g, \"\") : 0;\n\n if (parsedHeight && parsedHeight >= 0 && parsedHeight <= maxHeight) {\n return parsedHeight;\n } else {\n // if the value = 0 then nothing will be returned\n return parsedHeight === 0 ? 0 : maxHeight;\n }\n}\n\n/**\n * @file Adds [scroll] to bbcode\n * @example [scroll]content[/scroll]\n */\nexport const scroll = (node, options) => {\n const attrs = preprocessAttr(node, options.data.raw)._default;\n const heightInput = parseHeight(attrs);\n return toNode(\"div\", { class: \"bb-scroll\", style: `height: ${heightInput}px` }, node.content);\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\nexport const side = (node) => {\n const attrs = preprocessAttr(node)._default || \"left\";\n return toNode(\"div\", { class: \"bb-side\", \"data-side\": attrs }, node.content);\n};\n","import { toNode } from \"../utils/common\";\n\n/**\n * @file Adds subscript to BBCode\n * @example [sub]content[/sub]\n */\n\nconst sub = (node) => {\n return toNode(\"sub\", {}, node.content);\n};\n\nexport { sub };\n","import { toNode } from \"../utils/common\";\n\n/**\n * @file Adds superscript to bbcode\n * @example [sup]content[/sup]\n */\n\nconst sup = (node) => {\n return toNode(\"sup\", {}, node.content);\n};\n\nexport { sup };\n","import { isTagNode } from \"@bbob/plugin-helper\";\nimport {\n generateGUID,\n preprocessAttr,\n toNode,\n toRawTag,\n} from \"../utils/common\";\n\n/**\n * @file Adds [tabs][tab] to bbcode\n * @example [tabs][tab=name 1]content[/tab][tab=name 2]content[/tab][/tabs]\n */\nexport const tabs = (node, options) => {\n const tabsList = node.content.filter(\n (contentNode) => isTagNode(contentNode) && contentNode.tag === \"tab\"\n );\n const groupId = generateGUID();\n tabsList.forEach((tabNode) => {\n tabNode.isValid = true;\n tabNode.groupId = groupId;\n });\n\n if (!tabsList.length) {\n return toRawTag(node, options.data.raw);\n }\n\n tabsList[0].open = true;\n return toNode(\n \"div\",\n {\n class: \"bb-tabs\",\n },\n tabsList\n );\n};\n\n/**\n * [tab=name]content[/tab]\n * [tab name=\"name\" style=\"style\"]content[/tab]\n */\nexport const tab = (node, options) => {\n if (!node.isValid) {\n return toRawTag(node, options.data.raw);\n }\n\n const attrs = preprocessAttr(node, options.data.raw);\n const name = attrs?._default || attrs?.name || \"Tab\";\n const tabId = `tab-${name.replace(/\\W/g, \"_\")}-${generateGUID()}`;\n return [\n toNode(\"input\", {\n type: \"radio\",\n id: tabId,\n name: \"tab-group-\" + node.groupId,\n class: \"bb-tab\",\n checked: node.open,\n }),\n toNode(\n \"label\",\n {\n class: \"bb-tab-label\",\n for: tabId,\n style: attrs.style,\n },\n name\n ),\n toNode(\n \"div\",\n {\n class: \"bb-tab-content\",\n },\n node.content\n ),\n ];\n};\n","/**\n * @file discourse-core-replacement.js\n * This is a dedicated file for replacing the standard Discourse BBCode tags in core.\n * In the markdown-it engine, discourse has added these bbcode tags in the inline parser.\n * However this means that if the parser detects a block level tag inside an inline tag,\n * it will not parse the inline tag.\n *\n * This file is meant to fix such scenarios by doing the parsing of bbcode tags for it.\n *\n * @example\n * [b][h]bold[/h][/b] // this should properly parse the bold tag inside the h tag\n *\n * https://github.com/discourse/discourse/blob/d7ece61252d7671a1f124483836279b99852c08c/app/assets/javascripts/discourse-markdown-it/src/features/bbcode-inline.js\n */\nimport { toNode } from \"../utils/common\";\n\nexport const bold = (node) => {\n return toNode(\"span\", { class: \"bbcode-b\" }, node.content);\n};\n\nexport const italic = (node) => {\n if (node.gen) {\n // node is actually a generated node \"i\" made by another tag\n // don't process it\n return node;\n }\n return toNode(\"span\", { class: \"bbcode-i\" }, node.content);\n};\n\nexport const underline = (node) => {\n return toNode(\"span\", { class: \"bbcode-u\" }, node.content);\n};\n\nexport const strike = (node) => {\n return toNode(\"span\", { class: \"bbcode-s\" }, node.content);\n};\n","import {\n MD_BROKEN_BLOCKQUOTE,\n MD_BROKEN_ORDERED_LIST,\n MD_BROKEN_UNORDERED_LIST,\n MD_NEWLINE_INJECT,\n MD_NEWLINE_INJECT_COMMENT,\n MD_NEWLINE_PRE_INJECT,\n} from \"./common\";\n\n/**\n * Post Processing designed to fix issues with Markdown and BBCode that the parser can't fix.\n *\n * Separate from markdown-it post processing as it'll be able to manipulate the full string.\n * @param {string} raw string from processing through both BBCode and Markdown\n * @returns post processed string\n */\nfunction removeNewlineInjects(raw) {\n const processed = raw\n .replaceAll(MD_NEWLINE_INJECT, \"\")\n .replaceAll(MD_NEWLINE_PRE_INJECT, \"\")\n .replaceAll(\"\\n\" + MD_NEWLINE_INJECT_COMMENT, \"\")\n .replaceAll(MD_NEWLINE_INJECT_COMMENT + \"\\n\", \"\")\n .replaceAll(MD_NEWLINE_INJECT_COMMENT, \"\"); // Remove all instances of the injected newline\n return processed;\n}\n\nfunction cleanMultilineMDBlocks(raw) {\n const processed = raw\n .replaceAll(MD_BROKEN_ORDERED_LIST, \"\")\n .replaceAll(MD_BROKEN_UNORDERED_LIST, \"\")\n .replaceAll(MD_BROKEN_BLOCKQUOTE, \"\");\n return processed;\n}\n\n/**\n * Injects hoisted code blocks back into the raw string\n * @param {string} raw input to inject hoisted code blocks into\n * @param {any} data contains hoist map\n * @returns string with hoisted code blocks injected\n */\nfunction renderHoistedCodeBlocks(raw, data) {\n const hoistMap = data.hoistMap;\n for (const [uuid, content] of Object.entries(hoistMap)) {\n raw = raw.replaceAll(uuid, content);\n }\n return raw;\n}\n\n/**\n * Setups the class style tag template for the post\n * @param {string} raw\n * @param {{styles: string[]}} data - contains styles array\n * @returns string\n */\nfunction createClassStyleTagTemplate(raw, data) {\n if (data.styles.length === 0) {\n return raw;\n }\n const template = '\";\n return template + raw;\n}\n\n/**\n * Setups the script tag template for the post\n * @param {string} raw\n * @param {{\n * bbscripts: {\n * id: string,\n * class: string,\n * on: string,\n * version: string,\n * content: string\n * }[]}} data - contains scripts array\n * @returns string\n */\nfunction createScriptTagTemplate(raw, data) {\n if (data.bbscripts.length === 0) {\n return raw;\n }\n const templates = data.bbscripts.map(\n (s) =>\n ``\n );\n return templates.join(\"\") + raw;\n}\n\n/**\n * Performs post processing on the raw string to address any necessary functionality that BBob/MD can't handle with a plugin (i.e. hoisting).\n * @param {string} raw processed input from after bbob and md\n * @param {any} data from bbob data\n * @returns final processed string\n */\nexport function postprocess(raw, data) {\n let final = raw;\n const postprocessors = [\n removeNewlineInjects,\n createClassStyleTagTemplate,\n createScriptTagTemplate,\n cleanMultilineMDBlocks,\n renderHoistedCodeBlocks,\n ];\n for (const postprocessor of postprocessors) {\n final = postprocessor(final, data);\n }\n return final;\n}\n","import { ESCAPABLES_REGEX, generateGUID, MD_TABLE_REGEX, regexIndexOf } from \"./common\";\n\n/**\n * Find all code blocks and hoist them out of the content and into a map for later insertion\n * @param {string} raw input to preprocess\n * @returns processed string and hoist map\n */\nfunction fenceCodeBlockPreprocess(content, data) {\n /** @type {Object.} */\n const hoistMap = {};\n let index = 0;\n\n const addHoistAndReturnNewStartPoint = (cutOffStart, cutOffEnd, expected, trim = false) => {\n const uuid = generateGUID();\n if (cutOffEnd !== -1) {\n hoistMap[uuid] = content.substring(cutOffStart, cutOffEnd);\n content = content.substring(0, cutOffStart) + uuid + content.substring(cutOffEnd);\n } else {\n hoistMap[uuid] = content.substring(cutOffStart);\n content = content.substring(0, cutOffStart) + uuid + expected;\n }\n if (trim) {\n if (hoistMap[uuid].startsWith(\"\\n\")) {\n hoistMap[uuid] = hoistMap[uuid].substring(1);\n }\n if (hoistMap[uuid].endsWith(\"\\n\")) {\n hoistMap[uuid] = hoistMap[uuid].substring(0, hoistMap[uuid].length - 1);\n }\n }\n return cutOffStart + uuid.length + expected.length;\n };\n\n while ((index = regexIndexOf(content, ESCAPABLES_REGEX, index)) !== -1) {\n const match = ESCAPABLES_REGEX.exec(content.substring(index));\n if (match.groups?.fence) {\n const fence = match.groups.fence;\n const fenceInfo = match.groups.fenceInfo;\n if (content[index] === \"\\n\") {\n // Check if the fence is not at the start of the content\n index += 1;\n }\n const closingFenceRegex = new RegExp(\"\\n\" + fence + \"(\\n|$)\"); // Find the next fence. By commonmark spec, it should be the same fence length and type\n const nextIndex = regexIndexOf(content, closingFenceRegex, index + fence.length);\n\n const uuid = generateGUID();\n if (nextIndex !== -1) {\n hoistMap[uuid] = content.substring(index + fence.length + fenceInfo.length, nextIndex);\n } else {\n hoistMap[uuid] = content.substring(index + fence.length + fenceInfo.length);\n }\n // inject bbcode tag before and after the code block. This is to prevent BBob plugin from injecting newlines\n const replacement = `[saveNL]\\n${fence}${fenceInfo}${uuid}\\n${fence}\\n[/saveNL]`;\n content =\n content.substring(0, index) +\n replacement +\n (nextIndex !== -1 ? content.substring(nextIndex + 1 + fence.length) : \"\");\n index = index + replacement.length;\n } else if (match.groups?.bbcode) {\n const bbcode = match.groups.bbcode;\n const bbcodeTag = match.groups.bbcodeTag.toLowerCase(); // coerce to lowercase for caseinsensitive matching\n const closingTag = `[/${bbcodeTag}]`;\n const nextIndex = content.toLowerCase().indexOf(closingTag, index + 1);\n index = addHoistAndReturnNewStartPoint(index + bbcode.length, nextIndex, closingTag, true);\n } else if (match.groups.backtick) {\n const backtick = match.groups.backtick; // contains whole content\n const tickStart = match.groups.tickStart;\n const tickEnd = match.groups.tickEnd;\n index = addHoistAndReturnNewStartPoint(\n index + tickStart.length,\n index + backtick.length - tickEnd.length,\n tickEnd,\n );\n }\n }\n\n data.hoistMap = hoistMap;\n return [content, data];\n}\n\n/**\n * Find all markdown table blocks and mark them to ignore newlines\n * @param {string} raw input to preprocess\n * @returns processed string\n */\nfunction mdTableBlockPreprocess(content, data) {\n let index = 0;\n while ((index = regexIndexOf(content, MD_TABLE_REGEX, index)) !== -1) {\n const match = MD_TABLE_REGEX.exec(content.substring(index));\n const table = match[0];\n const replacement = `[saveNL]\\n${table}\\n[/saveNL]`;\n content = content.substring(0, index) + replacement + content.substring(index + table.length);\n index = index + replacement.length;\n }\n return [content, data];\n}\n\n/**\n * Preprocesses input to be formatted for bbob to intake. Handles any necessary functionality that BBob can't handle with a plugin (i.e. hoisting).\n * @param {string} raw input to preprocess\n * @returns formatted input for bbob to intake\n */\nexport function preprocessRaw(raw) {\n let data = {};\n const preprocessors = [fenceCodeBlockPreprocess, mdTableBlockPreprocess];\n for (const preprocessor of preprocessors) {\n [raw, data] = preprocessor(raw, data);\n }\n return [raw, data];\n}\n","import bbob from \"@bbob/core\";\nimport { render } from \"@bbob/html\";\nimport { lineBreakPlugin } from \"./plugins/lineBreak\";\nimport { preserveWhitespace } from \"./plugins/preserveWhitespace\";\nimport { removeEmptyLinePlugin } from \"./plugins/removeEmptyLinesInAttr\";\nimport { availableTags, preset, preventParsing } from \"./preset\";\nimport { postprocess } from \"./utils/postprocess\";\nimport { preprocessRaw } from \"./utils/preprocess\";\n\nconst options = {\n onlyAllowTags: [...availableTags],\n caseFreeTags: true,\n contextFreeTags: preventParsing, // prevent parsing of children\n enableEscapeTags: true,\n onError: (err) => {\n if (options.previewing) {\n // eslint-disable-next-line no-console\n console.warn(err.message, err.lineNumber, err.columnNumber);\n }\n },\n};\nconst presetTags = preset();\n\nexport const RpNBBCode = (code, opts) => {\n const plugins = [presetTags];\n if (opts.preserveWhitespace) {\n plugins.push(preserveWhitespace());\n }\n plugins.push(lineBreakPlugin(), removeEmptyLinePlugin);\n const [preprocessed, preprocessedData] = preprocessRaw(code);\n return bbob(plugins).process(preprocessed, {\n render,\n ...options,\n data: {\n ...preprocessedData,\n raw: preprocessed,\n previewing: opts.previewing,\n fonts: new Set(),\n styles: [],\n bbscripts: [],\n },\n });\n};\n\nexport { postprocess };\n","let C1 = 'C1';\nlet C2 = 'C2';\nif (process.env.NODE_ENV !== 'production') {\n C1 = '\"parser\" is not a function, please pass to \"process(input, { parser })\" right function';\n C2 = '\"render\" function not defined, please pass to \"process(input, { render })\"';\n}\nexport { C1, C2 };\n"],"names":["N","TAB","EQ","QUOTEMARK","SPACE","OPEN_BRAKET","CLOSE_BRAKET","SLASH","BACKSLASH","isTagNode","el","isStringNode","isEOL","keysReduce","obj","reduce","def","Object","keys","acc","key","getNodeLength","node","Array","isArray","content","count","contentNode","String","length","escapeAttrValue","value","replace","attrValue","name","JSON","stringify","attrsToString","values","arr","join","getTagAttrs","tag","params","uniqAttr","res","tagAttr","attrs","TagNode","attr","this","append","push","appendToNode","setStart","start","setEnd","end","toTagStart","openTag","closeTag","toTagEnd","toTagNode","newNode","toLowerCase","toString","r","renderContent","tagStart","create","isOf","type","constructor","TOKEN_TYPE_ID","getTokenValue","token","isTagEnd","charCodeAt","Token","isEmpty","isNaN","isText","isTag","isAttrName","isAttrValue","isStart","isEnd","getName","slice","getTagName","getValue","getLine","getColumn","getStart","getEnd","text","tokenToText","row","col","CharGrabber","skip","num","silent","c","pos","o","onSkip","hasNext","len","getCurr","s","getPos","getLength","getRest","substring","getNext","nextPos","getPrev","prevPos","isLast","includes","val","indexOf","grabWhile","condition","grabN","substrUntilChar","char","idx","source","options","createCharGrabber","WHITESPACES","SPECIAL_CHARS","isWhiteSpace","isEscapeChar","unq","str","charToRemove","charAt","trimChar","createLexer","buffer","prevCol","tokenIndex","stateMode","tagMode","contextFreeTag","tokens","Math","floor","escapeTags","enableEscapeTags","contextFreeTags","filter","Boolean","map","caseFreeTags","nestedMap","Map","onToken","RESERVED_CHARS","NOT_CHAR_TOKENS","isCharToken","checkContextFreeMode","isClosingTag","chars","emitToken","startPos","endPos","cl","p","e","createTokenOfType","nextTagState","tagChars","isSingleValueTag","masterStartPos","validAttrName","isValue","stateSpecial","validAttrValue","isQM","prevChar","nextChar","isPrevSLASH","isNextEQ","isWS","isNextWS","isSpecialChar","stateTag","currChar","substr","hasInvalidChars","isNoAttrsInTag","stateAttrs","tagStr","tagGrabber","hasSpace","stateWord","fullTagLen","fullTagName","isEscapableChar","isChar","tokenize","isTokenNested","tokenValue","has","get","status","set","NodeList","last","n","flush","pop","toArray","createList","parse","input","opts","onlyAllowTags","tokenizer","nodes","nestedNodes","tagNodes","tagNodesAttrName","nestedTagsMap","Set","isTagNested","tagName","flushTagNodes","getNodes","lastNestedNode","appendNodeAsString","isNested","forEach","item","appendNodes","handleTagStart","tagNode","from","to","add","handleTag","onError","line","column","lineNumber","columnNumber","handleTagEnd","lexer","createTokenizer","activeTagNode","attrName","handleNode","isObj","iterate","t","cb","tree","same","expected","actual","every","exp","some","call","act","ao","eo","createTree","extendedTree","messages","walk","match","expr","expression","renderNode","stripTags","render","toNode","gen","preprocessAttr","raw","vals","_default","nodeRaw","openTagParts","split","trim","startsWith","endsWith","toOriginalStartTag","toOriginalEndTag","toRawTag","regexIndexOf","string","regex","startpos","search","MD_NEWLINE_INJECT","MD_NEWLINE_PRE_INJECT","MD_NEWLINE_INJECT_COMMENT","URL_REGEX_SINGLE_LINE","RegExp","ESCAPABLES_REGEX","MD_TABLE_REGEX","generateGUID","d","Date","getTime","window","performance","now","random","isString","disableLineBreakConversion","reduceWordsToLines","unshift","child","splice","isWhitespaceSensitive","test","words","rightIdx","findLastIndex","w","i","numSpaces","fromCharCode","repeat","CONSECUTIVE_NEWLINE_REGEX","removeEmptyLinePlugin","process","tags","core","tagCallback","SLIDE_TITLE_OPEN","Symbol","SLIDE_TITLE_CLOSE","SLIDE_CLOSE","SLIDE_REGEX","markerToString","marker","accordionTags","accordion","groupId","markedContent","contentArr","newArr","shift","foundIndex","preContent","postContent","groups","slideTitleOpen","slideTitleClose","slideClose","generateSlideMarkersFromContent","generatedSlides","currentSlide","prevMarker","customTitle","generateSlidesFromMarkers","filteredContent","isValid","data","customSettings","lastValidAlignment","align","width","find","classes","style","class","slide","title","isOpen","open","titleAlign","left","right","center","possibleOptions","alignment","anchor","a","id","goto","href","WEB_FONTS","VALID_FONT_STYLES","thin","extralight","light","regular","medium","semibold","bold","extrabold","black","REGISTERED_AXIS","AXES_REGEX","emailHeader","emailFooter","rowcolumn","columnAttrs","columnStyle","EVENTS","ACCEPTED_OPTIONS","textmessage","messageList","messageNode","recipient","message","option","animation","previewing","commonGUID","commonId","keyframes","ident","cleanContent","replaceAll","formatted","styles","bg","color","block","defaultOp","blockAttr","blockOption","blockquote","author","border","br","centerblock","percentageInput","check","nameAttr","classSuffix","className","selector","mediaQuery","state","minWidth","maxWidth","code","inputColor","comment","div","classAttrs","classNames","divide","fieldset","fa","font","fontFamily","family","axes","ital","wght","matches","exec","italic","weight","named_weight","fromEntries","entries","axesParser","url","sort","googleFontApiBuild","fonts","custom","fontVar","h","h1","h2","h3","h4","h5","h6","heightrestrict","heightInput","heightValue","parsedHeight","parseHeight","highlight","icode","imagefloat","inlinespoiler","justify","keyframe","mail","attributes","mailAttr","mailOption","person","subject","newspaper","nobr","note","ooc","pindent","plain","print","printAttr","printOption","progress","percentageInt","quote","thinprogress","savenl","sh","script","onEvent","on","scriptSetup","version","bbscripts","scroll","side","size","fontSize","fontValue","valid","parsedSize","sizeRanges","unit","parseFontSize","outputAttr","spoiler","providedTitle","sub","sup","tab","tabId","checked","for","tabs","tabsList","tabNode","b","u","availableTags","preset","createPreset","defTags","processor","presetFactory","presetExecutor","assign","extend","callback","removeNewlineInjects","cleanMultilineMDBlocks","renderHoistedCodeBlocks","hoistMap","uuid","createClassStyleTagTemplate","createScriptTagTemplate","fenceCodeBlockPreprocess","index","addHoistAndReturnNewStartPoint","cutOffStart","cutOffEnd","fence","fenceInfo","closingFenceRegex","nextIndex","replacement","bbcode","closingTag","bbcodeTag","backtick","tickStart","tickEnd","mdTableBlockPreprocess","table","err","console","warn","presetTags","plugins","preserveWhitespace","preprocessed","preprocessedData","preprocessors","preprocessor","preprocessRaw","plugs","mockRender","skipParse","parser","parseFn","renderFn","Error","plugin","newTree","html","bbob","final","postprocessors","postprocessor"],"mappings":"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","x_google_ignoreList":[0,1,2,3,4,5,6,7,8,9,14,65]} \ No newline at end of file +{"version":3,"file":"bbcode-parser.min.js","sources":["../../node_modules/@bbob/plugin-helper/es/char.js","../../node_modules/@bbob/plugin-helper/es/helpers.js","../../node_modules/@bbob/plugin-helper/es/TagNode.js","../../node_modules/@bbob/parser/es/Token.js","../../node_modules/@bbob/parser/es/utils.js","../../node_modules/@bbob/parser/es/lexer.js","../../node_modules/@bbob/parser/es/parse.js","../../node_modules/@bbob/core/es/utils.js","../../node_modules/@bbob/core/es/index.js","../../node_modules/@bbob/html/es/index.js","../../bbcode-src/utils/common.js","../../bbcode-src/plugins/lineBreak.js","../../bbcode-src/plugins/preserveWhitespace.js","../../bbcode-src/plugins/removeEmptyLinesInAttr.js","../../node_modules/@bbob/preset/es/preset.js","../../bbcode-src/tags/accordion.js","../../bbcode-src/tags/alignment.js","../../bbcode-src/tags/anchor.js","../../bbcode-src/tags/font.js","../../bbcode-src/tags/heightrestrict.js","../../bbcode-src/tags/mail.js","../../bbcode-src/tags/rowcolumn.js","../../bbcode-src/tags/script.js","../../bbcode-src/tags/size.js","../../bbcode-src/tags/table.js","../../bbcode-src/tags/textmessage.js","../../bbcode-src/preset.js","../../bbcode-src/tags/animation.js","../../bbcode-src/tags/background.js","../../bbcode-src/tags/block.js","../../bbcode-src/tags/blockquote.js","../../bbcode-src/tags/border.js","../../bbcode-src/tags/lineBreak.js","../../bbcode-src/tags/centerblock.js","../../bbcode-src/tags/check.js","../../bbcode-src/tags/class.js","../../bbcode-src/tags/code.js","../../bbcode-src/tags/color.js","../../bbcode-src/tags/comment.js","../../bbcode-src/tags/div.js","../../bbcode-src/tags/divide.js","../../bbcode-src/tags/fieldset.js","../../bbcode-src/tags/fontawesome.js","../../bbcode-src/tags/header.js","../../bbcode-src/tags/highlight.js","../../bbcode-src/tags/imagefloat.js","../../bbcode-src/tags/spoiler.js","../../bbcode-src/tags/justify.js","../../bbcode-src/tags/list.js","../../bbcode-src/tags/newspaper.js","../../bbcode-src/tags/note.js","../../bbcode-src/tags/ooc.js","../../bbcode-src/tags/pindent.js","../../bbcode-src/tags/plain.js","../../bbcode-src/tags/print.js","../../bbcode-src/tags/progress.js","../../bbcode-src/tags/quote.js","../../bbcode-src/tags/thinprogress.js","../../bbcode-src/tags/scroll.js","../../bbcode-src/tags/side.js","../../bbcode-src/tags/subscript.js","../../bbcode-src/tags/superscript.js","../../bbcode-src/tags/tabs.js","../../bbcode-src/tags/discourse-core-replacement.js","../../bbcode-src/utils/postprocess.js","../../bbcode-src/utils/preprocess.js","../../bbcode-src/utils/tokenizer.js","../../bbcode-src/index.js","../../node_modules/@bbob/core/es/errors.js"],"sourcesContent":["const N = '\\n';\nconst TAB = '\\t';\nconst F = '\\f';\nconst R = '\\r';\nconst EQ = '=';\nconst QUOTEMARK = '\"';\nconst SPACE = ' ';\nconst OPEN_BRAKET = '[';\nconst CLOSE_BRAKET = ']';\nconst SLASH = '/';\nconst BACKSLASH = '\\\\';\nexport { N, F, R, EQ, TAB, SPACE, SLASH, BACKSLASH, QUOTEMARK, OPEN_BRAKET, CLOSE_BRAKET };\n","import { N } from './char';\nfunction isTagNode(el) {\n return typeof el === 'object' && el !== null && 'tag' in el;\n}\nfunction isStringNode(el) {\n return typeof el === 'string';\n}\n// check string is end of line\nfunction isEOL(el) {\n return el === N;\n}\nfunction keysReduce(obj, reduce, def) {\n const keys = Object.keys(obj);\n return keys.reduce((acc, key)=>reduce(acc, key, obj), def);\n}\nfunction getNodeLength(node) {\n if (isTagNode(node) && Array.isArray(node.content)) {\n return node.content.reduce((count, contentNode)=>{\n return count + getNodeLength(contentNode);\n }, 0);\n }\n if (isStringNode(node)) {\n return String(node).length;\n }\n return 0;\n}\nfunction appendToNode(node, value) {\n if (Array.isArray(node.content)) {\n node.content.push(value);\n }\n}\n/**\n * Replaces \" to &qquot;\n * @param {string} value\n */ function escapeAttrValue(value) {\n return value.replace(/&/g, '&').replace(//g, '>').replace(/\"/g, '"').replace(/'/g, ''')// eslint-disable-next-line no-script-url\n .replace(/(javascript|data|vbscript):/gi, '$1%3A');\n}\n/**\n * @deprecated use escapeAttrValue\n */ const escapeHTML = escapeAttrValue;\n/**\n * Accept name and value and return valid html5 attribute string\n */ function attrValue(name, value) {\n // in case of performance\n switch(typeof value){\n case 'boolean':\n return value ? `${name}` : '';\n case 'number':\n return `${name}=\"${value}\"`;\n case 'string':\n return `${name}=\"${escapeAttrValue(value)}\"`;\n case 'object':\n return `${name}=\"${escapeAttrValue(JSON.stringify(value))}\"`;\n default:\n return '';\n }\n}\n/**\n * Transforms attrs to html params string\n * @example\n * attrsToString({ 'foo': true, 'bar': bar' }) => 'foo=\"true\" bar=\"bar\"'\n */ function attrsToString(values) {\n // To avoid some malformed attributes\n if (values == null) {\n return '';\n }\n return keysReduce(values, (arr, key, obj)=>[\n ...arr,\n attrValue(key, obj[key])\n ], [\n ''\n ]).join(' ');\n}\n/**\n * Gets value from\n * @example\n * getUniqAttr({ 'foo': true, 'bar': bar' }) => 'bar'\n */ function getUniqAttr(attrs) {\n return keysReduce(attrs || {}, (res, key, obj)=>obj[key] === key ? obj[key] : null, null);\n}\nexport { attrsToString, attrValue, appendToNode, escapeHTML, escapeAttrValue, getNodeLength, getUniqAttr, isTagNode, isStringNode, isEOL };\n","import { OPEN_BRAKET, CLOSE_BRAKET, SLASH } from './char';\nimport { getUniqAttr, getNodeLength, appendToNode, attrsToString, attrValue, isTagNode } from './helpers';\nconst getTagAttrs = (tag, params)=>{\n const uniqAttr = getUniqAttr(params);\n if (uniqAttr) {\n const tagAttr = attrValue(tag, uniqAttr);\n const attrs = {\n ...params\n };\n delete attrs[String(uniqAttr)];\n const attrsStr = attrsToString(attrs);\n return `${tagAttr}${attrsStr}`;\n }\n return `${tag}${attrsToString(params)}`;\n};\nconst renderContent = (content, openTag, closeTag)=>{\n const toString = (node)=>{\n if (isTagNode(node)) {\n return node.toString({\n openTag,\n closeTag\n });\n }\n return String(node);\n };\n if (Array.isArray(content)) {\n return content.reduce((r, node)=>{\n if (node !== null) {\n return r + toString(node);\n }\n return r;\n }, '');\n }\n if (content) {\n return toString(content);\n }\n return null;\n};\nexport class TagNode {\n attr(name, value) {\n if (typeof value !== 'undefined') {\n this.attrs[name] = value;\n }\n return this.attrs[name];\n }\n append(value) {\n return appendToNode(this, value);\n }\n setStart(value) {\n this.start = value;\n }\n setEnd(value) {\n this.end = value;\n }\n get length() {\n return getNodeLength(this);\n }\n toTagStart({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}) {\n const tagAttrs = getTagAttrs(String(this.tag), this.attrs);\n return `${openTag}${tagAttrs}${closeTag}`;\n }\n toTagEnd({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}) {\n return `${openTag}${SLASH}${this.tag}${closeTag}`;\n }\n toTagNode() {\n const newNode = new TagNode(String(this.tag).toLowerCase(), this.attrs, this.content);\n if (this.start) {\n newNode.setStart(this.start);\n }\n if (this.end) {\n newNode.setEnd(this.end);\n }\n return newNode;\n }\n toString({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}) {\n const content = this.content ? renderContent(this.content, openTag, closeTag) : '';\n const tagStart = this.toTagStart({\n openTag,\n closeTag\n });\n if (this.content === null || Array.isArray(this.content) && this.content.length === 0) {\n return tagStart;\n }\n return `${tagStart}${content}${this.toTagEnd({\n openTag,\n closeTag\n })}`;\n }\n static create(tag, attrs = {}, content = null, start) {\n const node = new TagNode(tag, attrs, content);\n if (start) {\n node.setStart(start);\n }\n return node;\n }\n static isOf(node, type) {\n return node.tag === type;\n }\n constructor(tag, attrs, content){\n this.tag = tag;\n this.attrs = attrs;\n this.content = content;\n }\n}\n","import { OPEN_BRAKET, CLOSE_BRAKET, SLASH } from '@bbob/plugin-helper';\n// type, value, line, row, start pos, end pos\nconst TOKEN_TYPE_ID = 't'; // 0;\nconst TOKEN_VALUE_ID = 'v'; // 1;\nconst TOKEN_COLUMN_ID = 'r'; // 2;\nconst TOKEN_LINE_ID = 'l'; // 3;\nconst TOKEN_START_POS_ID = 's'; // 4;\nconst TOKEN_END_POS_ID = 'e'; // 5;\nconst TOKEN_TYPE_WORD = 1; // 'word';\nconst TOKEN_TYPE_TAG = 2; // 'tag';\nconst TOKEN_TYPE_ATTR_NAME = 3; // 'attr-name';\nconst TOKEN_TYPE_ATTR_VALUE = 4; // 'attr-value';\nconst TOKEN_TYPE_SPACE = 5; // 'space';\nconst TOKEN_TYPE_NEW_LINE = 6; // 'new-line';\nconst getTokenValue = (token)=>{\n if (token && typeof token[TOKEN_VALUE_ID] !== 'undefined') {\n return token[TOKEN_VALUE_ID];\n }\n return '';\n};\nconst getTokenLine = (token)=>token && token[TOKEN_LINE_ID] || 0;\nconst getTokenColumn = (token)=>token && token[TOKEN_COLUMN_ID] || 0;\nconst getStartPosition = (token)=>token && token[TOKEN_START_POS_ID] || 0;\nconst getEndPosition = (token)=>token && token[TOKEN_END_POS_ID] || 0;\nconst isTextToken = (token)=>{\n if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') {\n return token[TOKEN_TYPE_ID] === TOKEN_TYPE_SPACE || token[TOKEN_TYPE_ID] === TOKEN_TYPE_NEW_LINE || token[TOKEN_TYPE_ID] === TOKEN_TYPE_WORD;\n }\n return false;\n};\nconst isTagToken = (token)=>{\n if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') {\n return token[TOKEN_TYPE_ID] === TOKEN_TYPE_TAG;\n }\n return false;\n};\nconst isTagEnd = (token)=>getTokenValue(token).charCodeAt(0) === SLASH.charCodeAt(0);\nconst isTagStart = (token)=>!isTagEnd(token);\nconst isAttrNameToken = (token)=>{\n if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') {\n return token[TOKEN_TYPE_ID] === TOKEN_TYPE_ATTR_NAME;\n }\n return false;\n};\nconst isAttrValueToken = (token)=>{\n if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') {\n return token[TOKEN_TYPE_ID] === TOKEN_TYPE_ATTR_VALUE;\n }\n return false;\n};\nconst getTagName = (token)=>{\n const value = getTokenValue(token);\n return isTagEnd(token) ? value.slice(1) : value;\n};\nconst tokenToText = (token, openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET)=>{\n let text = openTag;\n text += getTokenValue(token);\n text += closeTag;\n return text;\n};\n/**\n * @export\n * @class Token\n */ class Token {\n get type() {\n return this[TOKEN_TYPE_ID];\n }\n isEmpty() {\n return this[TOKEN_TYPE_ID] === 0 || isNaN(this[TOKEN_TYPE_ID]);\n }\n isText() {\n return isTextToken(this);\n }\n isTag() {\n return isTagToken(this);\n }\n isAttrName() {\n return isAttrNameToken(this);\n }\n isAttrValue() {\n return isAttrValueToken(this);\n }\n isStart() {\n return isTagStart(this);\n }\n isEnd() {\n return isTagEnd(this);\n }\n getName() {\n return getTagName(this);\n }\n getValue() {\n return getTokenValue(this);\n }\n getLine() {\n return getTokenLine(this);\n }\n getColumn() {\n return getTokenColumn(this);\n }\n getStart() {\n return getStartPosition(this);\n }\n getEnd() {\n return getEndPosition(this);\n }\n toString({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}) {\n return tokenToText(this, openTag, closeTag);\n }\n constructor(type, value, row = 0, col = 0, start = 0, end = 0){\n this[TOKEN_LINE_ID] = row;\n this[TOKEN_COLUMN_ID] = col;\n this[TOKEN_TYPE_ID] = type || 0;\n this[TOKEN_VALUE_ID] = String(value);\n this[TOKEN_START_POS_ID] = start;\n this[TOKEN_END_POS_ID] = end;\n }\n}\nexport const TYPE_ID = TOKEN_TYPE_ID;\nexport const VALUE_ID = TOKEN_VALUE_ID;\nexport const LINE_ID = TOKEN_LINE_ID;\nexport const COLUMN_ID = TOKEN_COLUMN_ID;\nexport const START_POS_ID = TOKEN_START_POS_ID;\nexport const END_POS_ID = TOKEN_END_POS_ID;\nexport const TYPE_WORD = TOKEN_TYPE_WORD;\nexport const TYPE_TAG = TOKEN_TYPE_TAG;\nexport const TYPE_ATTR_NAME = TOKEN_TYPE_ATTR_NAME;\nexport const TYPE_ATTR_VALUE = TOKEN_TYPE_ATTR_VALUE;\nexport const TYPE_SPACE = TOKEN_TYPE_SPACE;\nexport const TYPE_NEW_LINE = TOKEN_TYPE_NEW_LINE;\nexport { Token };\nexport default Token;\n","import { QUOTEMARK, BACKSLASH } from '@bbob/plugin-helper';\nexport class CharGrabber {\n skip(num = 1, silent) {\n this.c.pos += num;\n if (this.o && this.o.onSkip && !silent) {\n this.o.onSkip();\n }\n }\n hasNext() {\n return this.c.len > this.c.pos;\n }\n getCurr() {\n if (typeof this.s[this.c.pos] === 'undefined') {\n return '';\n }\n return this.s[this.c.pos];\n }\n getPos() {\n return this.c.pos;\n }\n getLength() {\n return this.c.len;\n }\n getRest() {\n return this.s.substring(this.c.pos);\n }\n getNext() {\n const nextPos = this.c.pos + 1;\n return nextPos <= this.s.length - 1 ? this.s[nextPos] : null;\n }\n getPrev() {\n const prevPos = this.c.pos - 1;\n if (typeof this.s[prevPos] === 'undefined') {\n return null;\n }\n return this.s[prevPos];\n }\n isLast() {\n return this.c.pos === this.c.len;\n }\n includes(val) {\n return this.s.indexOf(val, this.c.pos) >= 0;\n }\n grabWhile(condition, silent) {\n let start = 0;\n if (this.hasNext()) {\n start = this.c.pos;\n while(this.hasNext() && condition(this.getCurr())){\n this.skip(1, silent);\n }\n }\n return this.s.substring(start, this.c.pos);\n }\n grabN(num = 0) {\n return this.s.substring(this.c.pos, this.c.pos + num);\n }\n /**\n * Grabs rest of string until it find a char\n */ substrUntilChar(char) {\n const { pos } = this.c;\n const idx = this.s.indexOf(char, pos);\n return idx >= 0 ? this.s.substring(pos, idx) : '';\n }\n constructor(source, options = {}){\n this.s = source;\n this.c = {\n pos: 0,\n len: source.length\n };\n this.o = options;\n }\n}\n/**\n * Creates a grabber wrapper for source string, that helps to iterate over string char by char\n */ export const createCharGrabber = (source, options)=>new CharGrabber(source, options);\n/**\n * Trims string from start and end by char\n * @example\n * trimChar('*hello*', '*') ==> 'hello'\n */ export const trimChar = (str, charToRemove)=>{\n while(str.charAt(0) === charToRemove){\n // eslint-disable-next-line no-param-reassign\n str = str.substring(1);\n }\n while(str.charAt(str.length - 1) === charToRemove){\n // eslint-disable-next-line no-param-reassign\n str = str.substring(0, str.length - 1);\n }\n return str;\n};\n/**\n * Unquotes \\\" to \"\n */ export const unquote = (str)=>str.replace(BACKSLASH + QUOTEMARK, QUOTEMARK);\n","/* eslint-disable no-plusplus,no-param-reassign */ import { OPEN_BRAKET, CLOSE_BRAKET, QUOTEMARK, BACKSLASH, SLASH, SPACE, TAB, EQ, N } from '@bbob/plugin-helper';\nimport { Token, TYPE_ATTR_NAME, TYPE_ATTR_VALUE, TYPE_NEW_LINE, TYPE_SPACE, TYPE_TAG, TYPE_WORD } from './Token';\nimport { createCharGrabber, trimChar, unquote } from './utils';\n// for cases \nconst EM = '!';\nexport function createTokenOfType(type, value, r = 0, cl = 0, p = 0, e = 0) {\n return new Token(type, value, r, cl, p, e);\n}\nconst STATE_WORD = 0;\nconst STATE_TAG = 1;\nconst STATE_TAG_ATTRS = 2;\nconst TAG_STATE_NAME = 0;\nconst TAG_STATE_ATTR = 1;\nconst TAG_STATE_VALUE = 2;\nconst WHITESPACES = [\n SPACE,\n TAB\n];\nconst SPECIAL_CHARS = [\n EQ,\n SPACE,\n TAB\n];\nconst END_POS_OFFSET = 2; // length + start position offset\nconst isWhiteSpace = (char)=>WHITESPACES.indexOf(char) >= 0;\nconst isEscapeChar = (char)=>char === BACKSLASH;\nconst isSpecialChar = (char)=>SPECIAL_CHARS.indexOf(char) >= 0;\nconst isNewLine = (char)=>char === N;\nconst unq = (val)=>unquote(trimChar(val, QUOTEMARK));\nexport function createLexer(buffer, options = {}) {\n let row = 0;\n let prevCol = 0;\n let col = 0;\n let tokenIndex = -1;\n let stateMode = STATE_WORD;\n let tagMode = TAG_STATE_NAME;\n let contextFreeTag = '';\n const tokens = new Array(Math.floor(buffer.length));\n const openTag = options.openTag || OPEN_BRAKET;\n const closeTag = options.closeTag || CLOSE_BRAKET;\n const escapeTags = !!options.enableEscapeTags;\n const contextFreeTags = (options.contextFreeTags || []).filter(Boolean).map((tag)=>tag.toLowerCase());\n const caseFreeTags = options.caseFreeTags || false;\n const nestedMap = new Map();\n const onToken = options.onToken || (()=>{});\n const RESERVED_CHARS = [\n closeTag,\n openTag,\n QUOTEMARK,\n BACKSLASH,\n SPACE,\n TAB,\n EQ,\n N,\n EM\n ];\n const NOT_CHAR_TOKENS = [\n openTag,\n SPACE,\n TAB,\n N\n ];\n const isCharReserved = (char)=>RESERVED_CHARS.indexOf(char) >= 0;\n const isCharToken = (char)=>NOT_CHAR_TOKENS.indexOf(char) === -1;\n const isEscapableChar = (char)=>char === openTag || char === closeTag || char === BACKSLASH;\n const onSkip = ()=>{\n col++;\n };\n const checkContextFreeMode = (name, isClosingTag)=>{\n if (contextFreeTag !== '' && isClosingTag) {\n contextFreeTag = '';\n }\n if (contextFreeTag === '' && contextFreeTags.includes(name.toLowerCase())) {\n contextFreeTag = name;\n }\n };\n const chars = createCharGrabber(buffer, {\n onSkip\n });\n /**\n * Emits newly created token to subscriber\n */ function emitToken(type, value, startPos, endPos) {\n const token = createTokenOfType(type, value, row, prevCol, startPos, endPos);\n onToken(token);\n prevCol = col;\n tokenIndex += 1;\n tokens[tokenIndex] = token;\n }\n function nextTagState(tagChars, isSingleValueTag, masterStartPos) {\n if (tagMode === TAG_STATE_ATTR) {\n const validAttrName = (char)=>!(char === EQ || isWhiteSpace(char));\n const name = tagChars.grabWhile(validAttrName);\n const isEnd = tagChars.isLast();\n const isValue = tagChars.getCurr() !== EQ;\n tagChars.skip();\n if (isEnd || isValue) {\n emitToken(TYPE_ATTR_VALUE, unq(name));\n } else {\n emitToken(TYPE_ATTR_NAME, name);\n }\n if (isEnd) {\n return TAG_STATE_NAME;\n }\n if (isValue) {\n return TAG_STATE_ATTR;\n }\n return TAG_STATE_VALUE;\n }\n if (tagMode === TAG_STATE_VALUE) {\n let stateSpecial = false;\n const validAttrValue = (char)=>{\n // const isEQ = char === EQ;\n const isQM = char === QUOTEMARK;\n const prevChar = tagChars.getPrev();\n const nextChar = tagChars.getNext();\n const isPrevSLASH = prevChar === BACKSLASH;\n const isNextEQ = nextChar === EQ;\n const isWS = isWhiteSpace(char);\n // const isPrevWS = isWhiteSpace(prevChar);\n const isNextWS = nextChar && isWhiteSpace(nextChar);\n if (stateSpecial && isSpecialChar(char)) {\n return true;\n }\n if (isQM && !isPrevSLASH) {\n stateSpecial = !stateSpecial;\n if (!stateSpecial && !(isNextEQ || isNextWS)) {\n return false;\n }\n }\n if (!isSingleValueTag) {\n return !isWS;\n // return (isEQ || isWS) === false;\n }\n return true;\n };\n const name = tagChars.grabWhile(validAttrValue);\n tagChars.skip();\n emitToken(TYPE_ATTR_VALUE, unq(name));\n if (tagChars.getPrev() === QUOTEMARK) {\n prevCol++;\n }\n if (tagChars.isLast()) {\n return TAG_STATE_NAME;\n }\n return TAG_STATE_ATTR;\n }\n const start = masterStartPos + tagChars.getPos() - 1;\n const validName = (char)=>!(char === EQ || isWhiteSpace(char) || tagChars.isLast());\n const name = tagChars.grabWhile(validName);\n emitToken(TYPE_TAG, name, start, masterStartPos + tagChars.getLength() + 1);\n checkContextFreeMode(name);\n tagChars.skip();\n prevCol++;\n // in cases when we has [url=someval]GET[/url] and we dont need to parse all\n if (isSingleValueTag) {\n return TAG_STATE_VALUE;\n }\n const hasEQ = tagChars.includes(EQ);\n return hasEQ ? TAG_STATE_ATTR : TAG_STATE_VALUE;\n }\n function stateTag() {\n const currChar = chars.getCurr();\n const nextChar = chars.getNext();\n chars.skip();\n // detect case where we have '[My word [tag][/tag]' or we have '[My last line word'\n const substr = chars.substrUntilChar(closeTag);\n const hasInvalidChars = substr.length === 0 || substr.indexOf(openTag) >= 0;\n if (nextChar && isCharReserved(nextChar) || hasInvalidChars || chars.isLast()) {\n emitToken(TYPE_WORD, currChar);\n return STATE_WORD;\n }\n // [myTag ]\n const isNoAttrsInTag = substr.indexOf(EQ) === -1;\n // [/myTag]\n const isClosingTag = substr[0] === SLASH;\n if (isNoAttrsInTag || isClosingTag) {\n const startPos = chars.getPos() - 1;\n const name = chars.grabWhile((char)=>char !== closeTag);\n const endPos = startPos + name.length + END_POS_OFFSET;\n chars.skip(); // skip closeTag\n emitToken(TYPE_TAG, name, startPos, endPos);\n checkContextFreeMode(name, isClosingTag);\n return STATE_WORD;\n }\n return STATE_TAG_ATTRS;\n }\n function stateAttrs() {\n const startPos = chars.getPos();\n const silent = true;\n const tagStr = chars.grabWhile((char)=>char !== closeTag, silent);\n const tagGrabber = createCharGrabber(tagStr, {\n onSkip\n });\n const hasSpace = tagGrabber.includes(SPACE);\n tagMode = TAG_STATE_NAME;\n while(tagGrabber.hasNext()){\n tagMode = nextTagState(tagGrabber, !hasSpace, startPos);\n }\n chars.skip(); // skip closeTag\n return STATE_WORD;\n }\n function stateWord() {\n if (isNewLine(chars.getCurr())) {\n emitToken(TYPE_NEW_LINE, chars.getCurr());\n chars.skip();\n col = 0;\n prevCol = 0;\n row++;\n return STATE_WORD;\n }\n if (isWhiteSpace(chars.getCurr())) {\n const word = chars.grabWhile(isWhiteSpace);\n emitToken(TYPE_SPACE, word);\n return STATE_WORD;\n }\n if (chars.getCurr() === openTag) {\n if (contextFreeTag) {\n const fullTagLen = openTag.length + SLASH.length + contextFreeTag.length;\n const fullTagName = `${openTag}${SLASH}${contextFreeTag}`;\n const foundTag = chars.grabN(fullTagLen);\n const isEndContextFreeMode = foundTag === fullTagName;\n if (isEndContextFreeMode) {\n return STATE_TAG;\n }\n } else if (chars.includes(closeTag)) {\n return STATE_TAG;\n }\n emitToken(TYPE_WORD, chars.getCurr());\n chars.skip();\n prevCol++;\n return STATE_WORD;\n }\n if (escapeTags) {\n if (isEscapeChar(chars.getCurr())) {\n const currChar = chars.getCurr();\n const nextChar = chars.getNext();\n chars.skip(); // skip the \\ without emitting anything\n if (nextChar && isEscapableChar(nextChar)) {\n chars.skip(); // skip past the [, ] or \\ as well\n emitToken(TYPE_WORD, nextChar);\n return STATE_WORD;\n }\n emitToken(TYPE_WORD, currChar);\n return STATE_WORD;\n }\n const isChar = (char)=>isCharToken(char) && !isEscapeChar(char);\n const word = chars.grabWhile(isChar);\n emitToken(TYPE_WORD, word);\n return STATE_WORD;\n }\n const word = chars.grabWhile(isCharToken);\n emitToken(TYPE_WORD, word);\n return STATE_WORD;\n }\n function tokenize() {\n stateMode = STATE_WORD;\n while(chars.hasNext()){\n switch(stateMode){\n case STATE_TAG:\n stateMode = stateTag();\n break;\n case STATE_TAG_ATTRS:\n stateMode = stateAttrs();\n break;\n case STATE_WORD:\n default:\n stateMode = stateWord();\n break;\n }\n }\n tokens.length = tokenIndex + 1;\n return tokens;\n }\n function isTokenNested(tokenValue) {\n const value = openTag + SLASH + tokenValue;\n if (nestedMap.has(value)) {\n return !!nestedMap.get(value);\n } else {\n const status = caseFreeTags ? buffer.toLowerCase().indexOf(value.toLowerCase()) > -1 : buffer.indexOf(value) > -1;\n nestedMap.set(value, status);\n return status;\n }\n }\n return {\n tokenize,\n isTokenNested\n };\n}\n","import { CLOSE_BRAKET, OPEN_BRAKET, TagNode, isTagNode } from \"@bbob/plugin-helper\";\nimport { createLexer } from \"./lexer\";\nclass NodeList {\n last() {\n if (Array.isArray(this.n) && this.n.length > 0 && typeof this.n[this.n.length - 1] !== \"undefined\") {\n return this.n[this.n.length - 1];\n }\n return null;\n }\n flush() {\n return this.n.length ? this.n.pop() : false;\n }\n push(value) {\n this.n.push(value);\n }\n toArray() {\n return this.n;\n }\n constructor(){\n this.n = [];\n }\n}\nconst createList = ()=>new NodeList();\nfunction parse(input, opts = {}) {\n const options = opts;\n const openTag = options.openTag || OPEN_BRAKET;\n const closeTag = options.closeTag || CLOSE_BRAKET;\n const onlyAllowTags = (options.onlyAllowTags || []).filter(Boolean).map((tag)=>tag.toLowerCase());\n const caseFreeTags = options.caseFreeTags || false;\n let tokenizer = null;\n /**\n * Result AST of nodes\n * @private\n * @type {NodeList}\n */ const nodes = createList();\n /**\n * Temp buffer of nodes that's nested to another node\n * @private\n */ const nestedNodes = createList();\n /**\n * Temp buffer of nodes [tag..]...[/tag]\n * @private\n * @type {NodeList}\n */ const tagNodes = createList();\n /**\n * Temp buffer of tag attributes\n * @private\n * @type {NodeList}\n */ const tagNodesAttrName = createList();\n /**\n * Cache for nested tags checks\n */ const nestedTagsMap = new Set();\n function isTokenNested(token) {\n const tokenValue = token.getValue();\n const value = caseFreeTags ? tokenValue.toLowerCase() : tokenValue;\n const { isTokenNested } = tokenizer || {};\n if (!nestedTagsMap.has(value) && isTokenNested && isTokenNested(value)) {\n nestedTagsMap.add(value);\n return true;\n }\n return nestedTagsMap.has(value);\n }\n /**\n * @private\n */ function isTagNested(tagName) {\n return Boolean(nestedTagsMap.has(caseFreeTags ? tagName.toLowerCase() : tagName));\n }\n /**\n * @private\n */ function isAllowedTag(value) {\n if (onlyAllowTags.length) {\n return onlyAllowTags.indexOf(value.toLowerCase()) >= 0;\n }\n return true;\n }\n /**\n * Flushes temp tag nodes and its attributes buffers\n * @private\n */ function flushTagNodes() {\n if (tagNodes.flush()) {\n tagNodesAttrName.flush();\n }\n }\n /**\n * @private\n */ function getNodes() {\n const lastNestedNode = nestedNodes.last();\n if (lastNestedNode && isTagNode(lastNestedNode)) {\n return lastNestedNode.content;\n }\n return nodes.toArray();\n }\n /**\n * @private\n */ function appendNodeAsString(nodes, node, isNested = true) {\n if (Array.isArray(nodes) && typeof node !== \"undefined\") {\n nodes.push(node.toTagStart({\n openTag,\n closeTag\n }));\n if (Array.isArray(node.content) && node.content.length) {\n node.content.forEach((item)=>{\n nodes.push(item);\n });\n if (isNested) {\n nodes.push(node.toTagEnd({\n openTag,\n closeTag\n }));\n }\n }\n }\n }\n /**\n * @private\n */ function appendNodes(nodes, node) {\n if (Array.isArray(nodes) && typeof node !== \"undefined\") {\n if (isTagNode(node)) {\n if (isAllowedTag(node.tag)) {\n nodes.push(node.toTagNode());\n } else {\n appendNodeAsString(nodes, node);\n }\n } else {\n nodes.push(node);\n }\n }\n }\n /**\n * @private\n * @param {Token} token\n */ function handleTagStart(token) {\n flushTagNodes();\n const tagNode = TagNode.create(token.getValue(), {}, [], {\n from: token.getStart(),\n to: token.getEnd()\n });\n const isNested = isTokenNested(token);\n tagNodes.push(tagNode);\n if (isNested) {\n nestedNodes.push(tagNode);\n } else {\n const nodes = getNodes();\n appendNodes(nodes, tagNode);\n }\n }\n /**\n * @private\n * @param {Token} token\n */ function handleTagEnd(token) {\n const tagName = token.getValue().slice(1);\n const lastNestedNode = nestedNodes.flush();\n flushTagNodes();\n if (lastNestedNode) {\n const nodes = getNodes();\n if (isTagNode(lastNestedNode)) {\n lastNestedNode.setEnd({\n from: token.getStart(),\n to: token.getEnd()\n });\n }\n appendNodes(nodes, lastNestedNode);\n } else if (!isTagNested(tagName)) {\n const nodes = getNodes();\n appendNodes(nodes, token.toString({\n openTag,\n closeTag\n }));\n } else if (typeof options.onError === \"function\") {\n const tag = token.getValue();\n const line = token.getLine();\n const column = token.getColumn();\n options.onError({\n tagName: tag,\n lineNumber: line,\n columnNumber: column\n });\n }\n }\n /**\n * @private\n * @param {Token} token\n */ function handleTag(token) {\n // [tag]\n if (token.isStart()) {\n handleTagStart(token);\n }\n // [/tag]\n if (token.isEnd()) {\n handleTagEnd(token);\n }\n }\n /**\n * @private\n * @param {Token} token\n */ function handleNode(token) {\n /**\n * @type {TagNode}\n */ const activeTagNode = tagNodes.last();\n const tokenValue = token.getValue();\n const isNested = isTagNested(token.toString());\n const nodes = getNodes();\n if (activeTagNode !== null) {\n if (token.isAttrName()) {\n tagNodesAttrName.push(tokenValue);\n const attrName = tagNodesAttrName.last();\n if (attrName) {\n activeTagNode.attr(attrName, \"\");\n }\n } else if (token.isAttrValue()) {\n const attrName = tagNodesAttrName.last();\n if (attrName) {\n activeTagNode.attr(attrName, tokenValue);\n tagNodesAttrName.flush();\n } else {\n activeTagNode.attr(tokenValue, tokenValue);\n }\n } else if (token.isText()) {\n if (isNested) {\n activeTagNode.append(tokenValue);\n } else {\n appendNodes(nodes, tokenValue);\n }\n } else if (token.isTag()) {\n // if tag is not allowed, just pass it as is\n appendNodes(nodes, token.toString({\n openTag,\n closeTag\n }));\n }\n } else if (token.isText()) {\n appendNodes(nodes, tokenValue);\n } else if (token.isTag()) {\n // if tag is not allowed, just pass it as is\n appendNodes(nodes, token.toString({\n openTag,\n closeTag\n }));\n }\n }\n /**\n * @private\n * @param {Token} token\n */ function onToken(token) {\n if (token.isTag()) {\n handleTag(token);\n } else {\n handleNode(token);\n }\n }\n const lexer = opts.createTokenizer ? opts.createTokenizer : createLexer;\n tokenizer = lexer(input, {\n onToken,\n openTag,\n closeTag,\n onlyAllowTags: options.onlyAllowTags,\n contextFreeTags: options.contextFreeTags,\n caseFreeTags: options.caseFreeTags,\n enableEscapeTags: options.enableEscapeTags\n });\n // eslint-disable-next-line no-unused-vars\n const tokens = tokenizer.tokenize();\n // handles situations where we open tag, but forgot close them\n // for ex [q]test[/q][u]some[/u][q]some [u]some[/u] // forgot to close [/q]\n // so we need to flush nested content to nodes array\n const lastNestedNode = nestedNodes.flush();\n if (lastNestedNode !== null && lastNestedNode && isTagNode(lastNestedNode) && isTagNested(lastNestedNode.tag)) {\n appendNodeAsString(getNodes(), lastNestedNode, false);\n }\n return nodes.toArray();\n}\nexport { parse };\nexport default parse;\n","/* eslint-disable no-plusplus */ const isObj = (value)=>typeof value === 'object' && value !== null;\nconst isBool = (value)=>typeof value === 'boolean';\nexport function iterate(t, cb) {\n const tree = t;\n if (Array.isArray(tree)) {\n for(let idx = 0; idx < tree.length; idx++){\n tree[idx] = iterate(cb(tree[idx]), cb);\n }\n } else if (isObj(tree) && 'content' in tree) {\n iterate(tree.content, cb);\n }\n return tree;\n}\nexport function same(expected, actual) {\n if (typeof expected !== typeof actual) {\n return false;\n }\n if (!isObj(expected) || expected === null) {\n return expected === actual;\n }\n if (Array.isArray(expected)) {\n return expected.every((exp)=>[].some.call(actual, (act)=>same(exp, act)));\n }\n if (isObj(expected) && isObj(actual)) {\n return Object.keys(expected).every((key)=>{\n const ao = actual[key];\n const eo = expected[key];\n if (isObj(eo) && isObj(ao)) {\n return same(eo, ao);\n }\n if (isBool(eo)) {\n return eo !== (ao === null);\n }\n return ao === eo;\n });\n }\n return false;\n}\nexport function match(t, expression, cb) {\n if (Array.isArray(expression)) {\n return iterate(t, (node)=>{\n for(let idx = 0; idx < expression.length; idx++){\n if (same(expression[idx], node)) {\n return cb(node);\n }\n }\n return node;\n });\n }\n return iterate(t, (node)=>same(expression, node) ? cb(node) : node);\n}\n","import { parse } from '@bbob/parser';\nimport { iterate, match } from './utils';\nimport { C1, C2 } from './errors';\nexport function createTree(tree, options) {\n const extendedTree = tree;\n extendedTree.messages = [\n ...extendedTree.messages || []\n ];\n extendedTree.options = {\n ...options,\n ...extendedTree.options\n };\n extendedTree.walk = function walkNodes(cb) {\n return iterate(this, cb);\n };\n extendedTree.match = function matchNodes(expr, cb) {\n return match(this, expr, cb);\n };\n return extendedTree;\n}\nexport default function bbob(plugs) {\n const plugins = typeof plugs === 'function' ? [\n plugs\n ] : plugs || [];\n const mockRender = ()=>\"\";\n return {\n process (input, opts) {\n const options = opts || {\n skipParse: false,\n parser: parse,\n render: mockRender,\n data: null\n };\n const parseFn = options.parser || parse;\n const renderFn = options.render;\n const data = options.data || null;\n if (typeof parseFn !== 'function') {\n throw new Error(C1);\n }\n // raw tree before modification with plugins\n const raw = options.skipParse && Array.isArray(input) ? input : parseFn(input, options);\n let tree = options.skipParse && Array.isArray(input) ? createTree(input || [], options) : createTree(raw, options);\n for(let idx = 0; idx < plugins.length; idx++){\n const plugin = plugins[idx];\n if (typeof plugin === 'function' && renderFn) {\n const newTree = plugin(tree, {\n parse: parseFn,\n render: renderFn,\n iterate,\n data\n });\n tree = createTree(newTree || tree, options);\n }\n }\n return {\n get html () {\n if (typeof renderFn !== 'function') {\n throw new Error(C2);\n }\n return renderFn(tree, tree.options);\n },\n tree,\n raw,\n messages: tree.messages\n };\n }\n };\n}\n","import core from '@bbob/core';\nimport { attrsToString, isTagNode } from '@bbob/plugin-helper';\nconst SELFCLOSE_END_TAG = '/>';\nconst CLOSE_START_TAG = '';\nfunction renderNode(node, options) {\n const { stripTags = false } = options || {};\n if (typeof node === 'undefined' || node === null) {\n return '';\n }\n if (typeof node === 'string' || typeof node === 'number') {\n return String(node);\n }\n if (Array.isArray(node)) {\n return render(node, options);\n }\n if (isTagNode(node)) {\n if (stripTags) {\n return render(node.content, options);\n }\n const attrs = attrsToString(node.attrs);\n if (node.content === null) {\n return START_TAG + node.tag + attrs + SELFCLOSE_END_TAG;\n }\n return START_TAG + node.tag + attrs + END_TAG + render(node.content, options) + CLOSE_START_TAG + node.tag + END_TAG;\n }\n return '';\n}\nexport function render(nodes, options) {\n if (nodes && Array.isArray(nodes)) {\n return nodes.reduce((r, node)=>r + renderNode(node, options), '');\n }\n if (nodes) {\n return renderNode(nodes, options);\n }\n return '';\n}\nexport function html(source, plugins, options) {\n return core(plugins).process(source, {\n ...options,\n render: render\n }).html;\n}\nexport default html;\n","/**\n * Generate the node object.\n *\n * Contains additional logic to help break any unintended side effects of the top down parsing of bbob.\n * @param {string} tag name of the tag\n * @param {Object} attrs attributes of the tag\n * @param {any} content contents of the tag. `[]` will create an empty tag. `null` will create a self closing tag\n *\n * @example\n * ```\n * toNode(\"div\", { class: \"class\" }, \"content\")\n * ```\n * becomes\n * ```\n * {\n * tag: \"div\",\n * attrs: { class: \"class\" },\n * content: \"content\",\n * gen: true,\n * }\n */\nconst toNode = (tag, attrs, content = []) => ({\n tag,\n attrs,\n content,\n gen: true,\n});\n\n/**\n * Preprocess attributes of a node to either return the default single attribute\n * or return a keyed attribute list\n * @param {import('@bbob/types').TagNode} node bbcode node to process\n * @param {string} [raw] raw string. Only include if the single attribute is allowed to have spaces\n * @returns processed attributes\n */\nconst preprocessAttr = (node, raw) => {\n if (raw && node.start) {\n const nodeRaw = raw.substring(node.start.from, node.start.to);\n const defaultOption = /^\\[[^\\s=\\]]+\\s*=/.exec(nodeRaw);\n if (defaultOption) {\n // A default option can contain complete CSS, including spaces, equals\n // signs in URLs, and quoted font names. Read it from the original tag\n // rather than reconstructing it from the tokenized keyed attributes.\n let value = nodeRaw.slice(defaultOption[0].length, -1).trim();\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n return { _default: value };\n }\n }\n\n const keys = Object.keys(node.attrs).join(\" \");\n const vals = Object.values(node.attrs).join(\" \");\n if (keys !== vals) {\n // [tag key=val]\n return node.attrs;\n }\n return raw && node.start ? node.attrs : { _default: vals };\n};\n\n/**\n * Attempts to return tag into its original form with proper attributes\n * @returns string of tag start\n */\nconst toOriginalStartTag = (node, raw) => {\n if (node.start) {\n return raw.substring(node.start.from, node.start.to);\n }\n if (!node.attrs) {\n return `[${node.tag}]`;\n }\n const attrs = preprocessAttr(node, raw);\n if (attrs._default) {\n return `[${node.tag}=${attrs._default}]`;\n } else {\n return node.toTagStart();\n }\n};\n\n/**\n * Attempts to return tag into its original form\n * @returns string of tag end\n */\nconst toOriginalEndTag = (node, raw) => {\n if (node.end) {\n return raw.substring(node.end.from, node.end.to);\n }\n return node.toTagEnd();\n};\n\n/**\n * Attempts to convert a node to a Markdown URL-embed-safe raw tag.\n *\n * Depending on the input node, the result is either:\n * - A single raw tag string if no end node is present\n * - Or an array of such strings if an end node is present\n * @returns {(string|string[])} - A Markdown URL-embed-safe raw tag or an array of tags.\n */\nconst toRawTag = (node, raw) => {\n if (node.end) {\n return [toOriginalStartTag(node, raw), ...node.content, toOriginalEndTag(node, raw)];\n }\n return toOriginalStartTag(node, raw);\n};\n\n/**\n * Given a string, find the first position of a regex match\n * @param {string} string to test against\n * @param {RegExp} regex to test with\n * @param {number} startpos starting position. Defaults to 0\n * @returns index of the first match of the regex in the string\n */\nconst regexIndexOf = (string, regex, startpos) => {\n const indexOf = string.substring(startpos || 0).search(regex);\n return indexOf >= 0 ? indexOf + (startpos || 0) : indexOf;\n};\n\nconst MD_NEWLINE_INJECT = \"\\n\\n\";\nconst MD_NEWLINE_PRE_INJECT = \"\\n\\n\";\nconst MD_NEWLINE_INJECT_COMMENT = \"\";\n\nconst URL_REGEX =\n /(http|ftp|https|upload):\\/\\/([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:\\/~+#-]*[\\w@?^=%&\\/~+#-])/;\nconst MD_URL_REGEX =\n /\\!?\\[.*\\]\\((http|ftp|https|upload):\\/\\/([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:\\/~+#-]*[\\w@?^=%&\\/~+#-])\\)/;\nconst URL_REGEX_SINGLE_LINE = new RegExp(`^${URL_REGEX.source}|${MD_URL_REGEX.source}$`);\n// Both ends of an inline-code delimiter must be whole backtick runs. In\n// particular, never retry shorter openers from inside an unmatched long run.\nconst ESCAPABLES_REGEX =\n /((\\n|^)(?```+|~~~+)(?.*\\n))|(?\\[(?i?code|plain)(=[^\\]\\r\\n]*)?\\])|(?(?`+)(?!`)([\\s\\S]*?)(?(?)(?!`))/im;\nconst MD_TABLE_REGEX = /^(\\|[^\\n]+\\|\\r?\\n)((?:\\| ?:?[-]+:? ?)+\\|)(\\n(?:\\|[^\\n]+\\|\\r?\\n?)*)?$/m;\n\nconst MD_BROKEN_ORDERED_LIST = \"
\\n
    \";\nconst MD_BROKEN_UNORDERED_LIST = \"\\n
      \";\nconst MD_BROKEN_BLOCKQUOTE = \"\\n
      \";\n\n/**\n * Generates a random GUID.\n *\n * Mini Racer doesn't have the crypto module, so we can't use the built-in `crypto.randomUUID` function.\n * @returns {string} a GUID\n */\nfunction generateGUID() {\n let d = new Date().getTime();\n if (window.performance && typeof window.performance.now === \"function\") {\n d += performance.now(); //use high-precision timer if available\n }\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, function (c) {\n // eslint-disable-next-line no-bitwise\n const r = (d + Math.random() * 16) % 16 | 0;\n d = Math.floor(d / 16);\n // eslint-disable-next-line no-bitwise\n return (c === \"x\" ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n\nexport {\n toNode,\n toOriginalStartTag,\n toOriginalEndTag,\n toRawTag,\n generateGUID,\n preprocessAttr,\n regexIndexOf,\n MD_NEWLINE_INJECT,\n MD_NEWLINE_INJECT_COMMENT,\n MD_NEWLINE_PRE_INJECT,\n URL_REGEX,\n MD_URL_REGEX,\n MD_TABLE_REGEX,\n URL_REGEX_SINGLE_LINE,\n ESCAPABLES_REGEX,\n MD_BROKEN_ORDERED_LIST,\n MD_BROKEN_UNORDERED_LIST,\n MD_BROKEN_BLOCKQUOTE,\n};\n","/**\n * Plugin that converts line breaks to `
      ` tags.\n * To use, put as function similar to the presets.\n *\n * If a node is marked with `noLineBreakConversion`, then it'll skip the parsing the children\n *\n * @example\n * ```ts\n * const output = bbob([preset(), lineBreakPlugin()]).process(input, {render}).html\n * ```\n */\nimport { isEOL } from \"@bbob/plugin-helper\";\nimport { MD_NEWLINE_INJECT, MD_NEWLINE_PRE_INJECT, URL_REGEX_SINGLE_LINE } from \"../utils/common\";\n\nconst isObj = (value) => typeof value === \"object\";\nconst isString = (value) => typeof value === \"string\";\n\n/**\n * Walks the tree of nodes. Will add `br` tag to all `\\n` in format that can be used in any renderer.\n * Preserves \\n so that markdown-it doesn't try to treat everything like a block\n *\n * If a node has the property noLineBreakConversion is encountered, will skip parsing children.\n * @param t tree of nodes to be processed\n * @returns modified tree\n */\nconst walk = (t, disableLineBreakConversion = false, preserveHeadingText = false) => {\n let tree = t;\n\n if (isString(tree) && preserveHeadingText) {\n // Text such as \"# character sheet\" is a small literal label in legacy\n // layouts. Encode its first hash before Markdown's block rules run; inline\n // entity parsing restores the original text without creating a heading.\n // This never rewrites attributes, code examples or Markdown outside layouts.\n tree = tree.replace(/^([ \\t]*)(#{1,6})(?=[ \\t\\r\\n]|$)/gm, (_, indent, hashes) => {\n return `${indent}#${hashes.slice(1)}`;\n });\n }\n\n if (Array.isArray(tree)) {\n reduceWordsToLines(tree);\n if (tree.some(isString)) {\n // array contains strings. Might be md compatible\n tree.unshift(MD_NEWLINE_INJECT);\n tree.push(MD_NEWLINE_INJECT);\n }\n for (let idx = 0; idx < tree.length; idx++) {\n const child = walk(tree[idx], disableLineBreakConversion, preserveHeadingText);\n if (Array.isArray(child)) {\n tree.splice(idx, 1, ...child);\n idx += child.length - 1;\n } else {\n tree[idx] = child;\n }\n }\n } else if (tree && isObj(tree) && tree.content) {\n if (tree.isWhitespaceSensitive) {\n // applies only to [code] and [icode]\n // stop walk. children won't be parsed to have
      \n return tree.tag ? tree : tree.content;\n }\n if (tree.disableLineBreakConversion) {\n disableLineBreakConversion = true;\n }\n // Only author-created DIVs and NOBR regions own their heading typography.\n // Generated DIVs (spoilers, quotes, etc.) do not change Markdown semantics.\n preserveHeadingText ||= tree.preserveHeadingText || tree.attrs?.[\"data-bbcode-div\"] === \"true\";\n walk(tree.content, disableLineBreakConversion, preserveHeadingText);\n return tree.tag ? tree : tree.content;\n } else if (isString(tree) && URL_REGEX_SINGLE_LINE.test(tree.trim())) {\n // if the entire string is a URL, then it should be prepared for onebox.\n // BBob separates strings by newlines anyway, so we can already assume this is sitting on its own line\n // MD_NEWLINE_INJECT is already replacing newline came before or the start of the array,\n // so we only need to make sure \\n\\n is added after the URL\n return [tree, MD_NEWLINE_PRE_INJECT];\n }\n\n if (isString(tree) && isEOL(tree)) {\n return disableLineBreakConversion\n ? [\"\\n\", MD_NEWLINE_INJECT]\n : [{ tag: \"br\", content: null }, MD_NEWLINE_INJECT];\n }\n\n return tree;\n};\n\n/**\n * Reduces the list into lines, so that we can process them by line.\n * Performs in place.\n * @param {(string|Object)[]} words\n */\nconst reduceWordsToLines = (words) => {\n let rightIdx = words.findLastIndex((w) => isString(w) && !isEOL(w)) + 1;\n\n for (let i = rightIdx - 1; i >= 0; i--) {\n if (isString(words[i]) && !isEOL(words[i])) {\n continue;\n }\n if (isEOL(words[i])) {\n if (i !== rightIdx - 1) {\n words.splice(i + 1, rightIdx - i - 1, words.slice(i + 1, rightIdx).join(\"\"));\n }\n rightIdx = i;\n continue;\n }\n if (isObj(words[i])) {\n if (i !== rightIdx - 1) {\n words.splice(i + 1, rightIdx - i - 1, words.slice(i + 1, rightIdx).join(\"\"));\n }\n rightIdx = i;\n }\n }\n\n if (0 !== rightIdx) {\n words.splice(0, rightIdx, words.slice(0, rightIdx).join(\"\"));\n }\n};\n\n/**\n * Converts `\\n` to `
      ` self closing tag. Supply this as the last plugin in the preset lists\n *\n * @example converts all line breaks to br\n * ```ts\n * const output = bbob([preset(), lineBreakPlugin()]).process(input, {render}).html\n * ```\n * @example will not convert line breaks inside [nobr]\n * ```ts\n * const nobr = (node: TagNode) => {return { disableLineBreakConversion: true, content: node.content }}; \\\\ tag in preset\n * ...\n * const output = bbob([preset(), lineBreakPlugin()]).process(input, {render}).html\n * ```\n * @returns plugin to be used in BBob process\n */\nexport const lineBreakPlugin = () => {\n return (tree) => walk(tree);\n};\n","/**\n * Plugin that converts consecutive normal spaces (U+0020) to non-breaking spaces (U+00A0).\n * To use, put as function similar to the presets.\n *\n *\n * @example\n * ```ts\n * const output = bbob([preset(), , preserveWhitespace(), lineBreakPlugin()]).process(input, {render}).html\n * ```\n */\nimport { isStringNode } from \"@bbob/plugin-helper\";\n\n/**\n * Checks if input is an object\n * @param value input\n * @returns if value is an object\n */\nconst isObj = (value) => typeof value === \"object\";\n\n/**\n * Walks the tree of nodes. Checks for node of consecutive spaces. If found replaces every space in\n * node with a nonbreaking space.\n * Preserves multiple spaces so html won't truncate them.\n *\n * Walks through entire tree.\n * @param t tree of nodes to be processed\n * @returns modified tree\n */\nconst walk = (t) => {\n const tree = t;\n\n if (Array.isArray(tree)) {\n for (let idx = 0; idx < tree.length; idx++) {\n const child = walk(tree[idx]);\n if (Array.isArray(child)) {\n tree.splice(idx, 1, ...child);\n idx += child.length - 1;\n } else {\n tree[idx] = child;\n }\n }\n } else if (tree && isObj(tree) && tree.content) {\n walk(tree.content);\n }\n\n //Bbob breaks up nodes by the presence of normal spaces.\n //So a node with a normal space can only have normal spaces in that node.\n if (isStringNode(tree)) {\n if (tree.length > 1 && tree[0] === \" \") {\n let numSpaces = tree.length;\n return [String.fromCharCode(160).repeat(numSpaces)];\n }\n }\n\n return tree;\n};\n\n/**\n * Converts consecutive normal spaces (U+0020) to nonbreaking spaces (U+00A0).\n * Supply this as a plugin in the preset lists.\n *\n * @example converts consecutive normal spaces (U+0020) to nonbreaking spaces (U+00A0)\n * ```ts\n * const output = bbob([preset(), preserveWhitespace(), lineBreakPlugin()]).process(input, {render}).html\n * ```\n *\n * @returns plugin to be used in BBob process\n */\nexport const preserveWhitespace = () => {\n return (tree) => walk(tree);\n};\n","import { isTagNode } from \"@bbob/plugin-helper\";\n\nconst CONSECUTIVE_NEWLINE_REGEX = /\\n{2,}/gm;\n\n/**\n * Removes empty lines from a string\n * @param {string} text\n */\nconst removeEmptyLines = (text) => {\n return text.replace(CONSECUTIVE_NEWLINE_REGEX, \"\\n\");\n};\n\n/**\n * Removes empty lines from attributes\n * @type {import('@bbob/types').BBobPluginFunction}\n */\nexport const removeEmptyLinePlugin = (tree) => {\n return tree.walk((node) => {\n if (isTagNode(node) && node.attrs) {\n Object.keys(node.attrs).forEach((key) => {\n if (typeof node.attrs[key] === \"string\") {\n node.attrs[key] = removeEmptyLines(node.attrs[key]);\n }\n });\n }\n return node;\n });\n};\n","import { isTagNode } from \"@bbob/plugin-helper\";\nexport function process(tags, tree, core, options) {\n return tree.walk((node)=>{\n if (isTagNode(node)) {\n const tag = node.tag;\n const tagCallback = tags[tag];\n if (typeof tagCallback === \"function\") {\n return tagCallback(node, core, options);\n }\n }\n return node;\n });\n}\n/**\n * Create a preset plugin for @bbob/core\n */ function createPreset(defTags, processor = process) {\n const presetFactory = (opts)=>{\n presetFactory.options = Object.assign(presetFactory.options || {}, opts);\n function presetExecutor(tree, core) {\n return processor(defTags, tree, core, presetFactory.options || {});\n }\n presetExecutor.options = presetFactory.options;\n return presetExecutor;\n };\n presetFactory.extend = function presetExtend(callback) {\n const newTags = callback(defTags, presetFactory.options);\n return createPreset(newTags, processor);\n };\n return presetFactory;\n}\nexport { createPreset };\nexport default createPreset;\n","import { isStringNode, isTagNode, TagNode } from \"@bbob/plugin-helper\";\nimport {\n generateGUID,\n preprocessAttr,\n regexIndexOf,\n toNode,\n toOriginalEndTag,\n toOriginalStartTag,\n} from \"../utils/common\";\n\nconst SLIDE_TITLE_OPEN = Symbol(\"slide-title-open\");\nconst SLIDE_TITLE_CLOSE = Symbol(\"slide-title-close\");\nconst SLIDE_CLOSE = Symbol(\"slide-close\");\nconst SLIDE_REGEX =\n /(?\\{slide=)|(?\\})|(?\\{\\/slide\\})/i;\n\n/**\n * Adds the accordion tag\n * [accordion]{slide=name}content{/slide}[/accordion]\n *\n * [accordion][slide=name]content[/slide][/accordion]\n */\nconst accordion = (node, options) => {\n const groupId = generateGUID();\n\n // add support for existing {slide} tags style, due to copious amounts of existing content\n // also the only way to get true custom content inside a slide due to nesting limitations\n const markedContent = generateSlideMarkersFromContent(node.content);\n const generatedSlides = generateSlidesFromMarkers(markedContent);\n\n const filteredContent = generatedSlides\n .filter((n) => isTagNode(n) && n.tag === \"slide\")\n .map((content) => {\n content.isValid = true;\n content.groupId = groupId;\n return content;\n });\n if (!filteredContent.length) {\n // no [slide] tags found\n return [\n toOriginalStartTag(node, options.data.raw),\n ...node.content,\n toOriginalEndTag(node, options.data.raw),\n ];\n }\n const attrs = preprocessAttr(node, options.data.raw);\n\n if (attrs._default) {\n /** @type {string[]} */\n const customSettings = attrs._default.split(\"|\").map((s) => s.trim());\n const lastValidAlignment = customSettings\n .filter((s) => [\"bright\", \"bcenter\", \"bleft\", \"fleft\", \"fright\"].includes(s))\n .pop();\n if (lastValidAlignment) {\n attrs.align ??= lastValidAlignment;\n }\n\n if (\n customSettings.some((s) => s.endsWith(\"px\")) ||\n customSettings.some((s) => s.endsWith(\"%\"))\n ) {\n attrs.width ??= customSettings.find((s) => s.endsWith(\"px\") || s.endsWith(\"%\"));\n }\n }\n\n let classes = attrs.align?.toLowerCase() || \"\";\n let style = \"\";\n if (attrs.width?.endsWith(\"px\") || attrs.width?.endsWith(\"%\")) {\n style = `width: ${attrs.width};`;\n }\n return toNode(\n \"div\",\n { class: \"bb-accordion \" + classes, \"data-group-id\": groupId, style },\n filteredContent,\n );\n};\n\n/**\n * Locates and splits all {slide} tag components into their respective parts while preserving remaining content\n * @param {(TagNode|string)[]} contentArr node content of the accordion tag\n *\n * @example\n * ```\n * [\"{slide=test}\", \"lorem ipsum\", \"{/slide}\"]\n * ```\n * becomes\n * ```\n * [SLIDE_TITLE_OPEN, \"test\", SLIDE_TITLE_CLOSE, \"lorem ipsum\", SLIDE_CLOSE]\n * ```\n */\nfunction generateSlideMarkersFromContent(contentArr) {\n contentArr = [...contentArr]; // shallow clone. object nodes are not modified anyway\n\n const newArr = [];\n while (contentArr.length > 0) {\n const content = contentArr[0];\n if (isTagNode(content)) {\n newArr.push(contentArr.shift());\n continue;\n }\n const foundIndex = regexIndexOf(content, SLIDE_REGEX);\n if (foundIndex === -1) {\n newArr.push(contentArr.shift());\n continue;\n }\n const match = content.match(SLIDE_REGEX);\n const preContent = content.slice(0, foundIndex);\n const postContent = content.slice(foundIndex + match[0].length);\n if (preContent.length) {\n newArr.push(preContent);\n }\n if (match.groups.slideTitleOpen) {\n newArr.push(SLIDE_TITLE_OPEN);\n }\n if (match.groups.slideTitleClose) {\n newArr.push(SLIDE_TITLE_CLOSE);\n }\n if (match.groups.slideClose) {\n newArr.push(SLIDE_CLOSE);\n }\n if (postContent.length) {\n contentArr[0] = postContent;\n } else {\n contentArr.shift();\n }\n }\n\n return newArr;\n}\n\n/**\n * Generates slide nodes from markers\n * @param {(string | typeof SLIDE_TITLE_OPEN | typeof SLIDE_TITLE_CLOSE | typeof SLIDE_CLOSE | TagNode)[]} markedContent\n */\nfunction generateSlidesFromMarkers(markedContent) {\n const nodes = [];\n let currentSlide = null;\n /** @type {typeof SLIDE_TITLE_OPEN | typeof SLIDE_TITLE_CLOSE | null} */\n let prevMarker = null;\n for (const content of markedContent) {\n if (content === SLIDE_TITLE_OPEN && prevMarker === null) {\n currentSlide = TagNode.create(\"slide\");\n currentSlide.content = [];\n currentSlide.customTitle = [];\n prevMarker = SLIDE_TITLE_OPEN;\n } else if (content === SLIDE_TITLE_CLOSE && prevMarker === SLIDE_TITLE_OPEN) {\n prevMarker = SLIDE_TITLE_CLOSE;\n continue;\n } else if (content === SLIDE_CLOSE && currentSlide && prevMarker === SLIDE_TITLE_CLOSE) {\n nodes.push(currentSlide);\n currentSlide = null;\n prevMarker = null;\n } else if (currentSlide) {\n if (prevMarker === SLIDE_TITLE_OPEN) {\n currentSlide.customTitle.push(markerToString(content));\n } else {\n currentSlide.content.push(markerToString(content));\n }\n } else {\n // no slide open, just add content\n nodes.push(markerToString(content));\n }\n }\n return nodes;\n}\n\n/**\n * Processes content into a string. Catches stray markers and converts them back into a string\n * @param {string | typeof SLIDE_TITLE_OPEN | typeof SLIDE_TITLE_CLOSE | typeof SLIDE_CLOSE} marker\n * @returns expected string\n */\nfunction markerToString(marker) {\n switch (marker) {\n case SLIDE_TITLE_OPEN:\n return \"{slide=\";\n case SLIDE_TITLE_CLOSE:\n return \"}\";\n case SLIDE_CLOSE:\n return \"{/slide}\";\n default:\n return marker;\n }\n}\n\nconst slide = (node, options) => {\n if (!node.isValid) {\n // not inside an [accordion] tag\n return [\n toOriginalStartTag(node, options.data.raw),\n ...node.content,\n toOriginalEndTag(node, options.data.raw),\n ];\n }\n const attrs = preprocessAttr(node, options.data.raw);\n let title = [attrs.title || attrs._default || \"Slide\"];\n let isOpen = !!attrs.open || false;\n let titleAlign = attrs.left ? \"left\" : attrs.right ? \"right\" : attrs.center ? \"center\" : \"left\";\n if (node.customTitle?.length) {\n // slide was created from markers\n title = node.customTitle;\n // pull out old options from title if they exist\n const possibleOptions = title\n .filter((t) => typeof t === \"string\")\n .join(\"\")\n .toLowerCase()\n .split(\"|\")\n .map((s) => s.trim());\n if (possibleOptions.includes(\"open\")) {\n isOpen = true;\n }\n if (possibleOptions.includes(\"right\")) {\n titleAlign = \"right\";\n }\n if (possibleOptions.includes(\"center\")) {\n titleAlign = \"center\";\n }\n if (possibleOptions.includes(\"left\")) {\n titleAlign = \"left\";\n }\n title = title.map((t) => {\n if (isStringNode(t)) {\n t = t.replace(/\\|(open|right|center|left)/gi, \"\");\n }\n return t;\n });\n }\n return [\n toNode(\"details\", { class: \"bb-slide\", open: isOpen }, [\n toNode(\n \"summary\",\n { class: \"bb-slide-title\", style: `text-align: ${titleAlign}; ${attrs.style || \"\"}` },\n title,\n ),\n toNode(\"div\", { class: \"bb-slide-content\" }, node.content),\n ]),\n ];\n};\n\nexport const accordionTags = { accordion, slide };\n","import { toNode } from \"../utils/common\";\n/**\n * @file Adds [left], [center], and [right] to bbcode\n * @example [center]content[/center]\n */\nexport const alignment = {\n left: (node) => toNode(\"div\", { class: \"bb-left\" }, node.content),\n center: (node) => toNode(\"div\", { class: \"bb-center\" }, node.content),\n right: (node) => toNode(\"div\", { class: \"bb-right\" }, node.content),\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n/**\n * @file Adds [a] and [goto] to bbcode\n * @example [a=your_anchor_name]An anchor[/a] [goto=your_anchor_name]Jump to an anchor[/goto]\n */\nexport const anchor = {\n // name is not valid in HTML5; however, it correctly displays back while id does not\n a: (node, options) => {\n const attrs = preprocessAttr(node, options.data.raw)._default || \"\";\n return toNode(\n \"a\",\n { id: `user-anchor-${attrs.trim()}`, name: `user-anchor-${attrs.trim()}` },\n node.content,\n );\n },\n goto: (node, options) => {\n const attrs = preprocessAttr(node, options.data.raw)._default || \"\";\n return toNode(\"a\", { href: `#user-anchor-${attrs.trim()}` }, node.content);\n },\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\nconst WEB_FONTS = [\n \"arial\",\n \"book antiqua\",\n \"courier new\",\n \"georgia\",\n \"tahoma\",\n \"times new roman\",\n \"trebuchet ms\",\n \"verdana\",\n];\nconst VALID_FONT_STYLES = {\n thin: \"100\",\n extralight: \"200\",\n light: \"300\",\n regular: \"400\",\n medium: \"500\",\n semibold: \"600\",\n bold: \"700\",\n extrabold: \"800\",\n black: \"900\",\n};\n// registered axis tags https://learn.microsoft.com/en-us/typography/opentype/spec/dvaraxisreg#registered-axis-tags\nconst REGISTERED_AXIS = [\"ital\", \"opsz\", \"slnt\", \"wdth\", \"wght\"];\n\nconst AXES_REGEX = /(?[a-zA-Z]*)?\\s?(?[0-9]*)?\\s?(?italic)?/;\n\nconst axesParser = (attrs) => {\n let axes = {\n ital: 0,\n wght: 400,\n };\n\n if (attrs?.style) {\n // user just copy pasted the name of the style on the google font site, probably\n const style = attrs.style.trim().toLowerCase();\n const matches = AXES_REGEX.exec(style).groups || {};\n if (matches?.italic) {\n axes.ital = 1;\n }\n\n const weight = matches.weight;\n if (weight && weight >= 0 && weight <= 900) {\n axes.wght = weight;\n } else if (Object.keys(VALID_FONT_STYLES).includes(matches.named_weight || \"\")) {\n axes.wght = VALID_FONT_STYLES[matches.named_weight];\n }\n\n axes = {\n ...axes,\n ...Object.fromEntries(Object.entries(attrs).filter(([key]) => REGISTERED_AXIS.includes(key))),\n };\n }\n return axes;\n};\n\n/**\n * Create google font api url\n * @param {string} family name of font\n * @param {object} axes custom font axes\n */\nconst googleFontApiBuild = (family, axes) => {\n family = family.replaceAll(\" \", \"+\");\n // google fonts requires axes names to be in alphabetical order\n axes = Object.keys(axes)\n .sort()\n .reduce((obj, key) => {\n obj[key] = axes[key];\n return obj;\n }, {});\n const axesList = Object.keys(axes).join(\",\") + \"@\" + Object.values(axes).join(\",\");\n return \"https://fonts.googleapis.com/css2?family=\" + family + \":\" + axesList;\n};\n\nexport const font = (node, options) => {\n const attrs = preprocessAttr(node, options.data.raw);\n const fontFamily = attrs?._default || attrs?.family || attrs?.name;\n if (!fontFamily || fontFamily.trim() === \"\") {\n return node.content;\n }\n if (WEB_FONTS.includes(fontFamily.trim().toLowerCase())) {\n return toNode(\"span\", { style: `font-family: '${fontFamily}'` }, node.content);\n }\n\n const axes = axesParser(attrs);\n const url = googleFontApiBuild(fontFamily, axes);\n options.data.fonts.add(url);\n\n const italic = axes.ital === 1 ? \"italic\" : \"normal\";\n\n const custom = Object.entries(axes).filter(([key]) => key !== \"wght\" && key !== \"ital\");\n let fontVar = \"\";\n if (custom.length) {\n fontVar =\n \"font-variation-settings: \" + custom.map(([key, val]) => `'${key}' ${val}`).join(\", \") + \";\";\n }\n\n return toNode(\n \"span\",\n {\n style: `font-family: '${fontFamily}'; font-weight: ${axes.wght}; font-style: ${italic}; ${fontVar}`,\n \"data-font\": url,\n },\n node.content\n );\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * Parse the user provided height and return a valid height value\n * @param {Number} heightValue obtains the input of the user entered height (default is 700)\n * @returns A validated number less than 0.\n */\nfunction parseHeight(heightValue) {\n const maxHeight = 700;\n const parsedHeight =\n heightValue && heightValue.trim() !== \"\" ? heightValue.replace(/[^\\d.]/g, \"\") : 0;\n\n if (parsedHeight && parsedHeight >= 0 && parsedHeight <= maxHeight) {\n return parsedHeight;\n } else {\n // if the value = 0 then nothing will be returned\n return parsedHeight === 0 ? 0 : maxHeight;\n }\n}\n\n/**\n * @file Adds [heightrestrict] to bbcode\n * @example [heightrestrict=50]content[/heightrestrict]\n */\nexport const heightrestrict = (node) => {\n const attrs = preprocessAttr(node)._default;\n const heightInput = parseHeight(attrs).toString();\n // Return image's default size if heightrestrict did not involve a valid value\n return heightInput === \"0\"\n ? toNode(\"div\", { class: \"bb-height-restrict\" }, node.content)\n : toNode(\n \"div\",\n { class: \"bb-height-restrict\", style: `height: ${heightInput}px;` },\n node.content,\n );\n};\n","import { toNode } from \"../utils/common\";\n/**\n * @file Adds [mail] to bbcode\n * @param {string} [type=\"send\"] Denotes type of mail either send or receive\n * @param {string} [person=\"Unknown\"] Denotes the person in the To/From field\n * @param {string} [subject=\"Empty\"] Denotes the subject line of the email\n * @example [mail type=\"send\" person=\"John Doe\" subject=\"Hello World\"]content[/mail]\n */\n\nconst parseEmailContent = (content) => {\n return toNode(\"div\", { class: \"bb-email-content\" }, content);\n};\n\nconst parseEmailSubject = (subject) => {\n return toNode(\"div\", { class: \"bb-email-subject\" }, subject);\n};\n\nconst parseEmailPerson = (person) => {\n return toNode(\"div\", { class: \"bb-email-address\" }, person);\n};\n\nconst emailHeader = toNode(\"div\", { class: \"bb-email-header\" }, \"\");\nconst emailFooter = toNode(\n \"div\",\n { class: \"bb-email-footer\" },\n toNode(\"div\", { class: \"bb-email-button\" }, \"\"),\n);\n\nexport const mail = (node) => {\n const attributes = node.attrs;\n let mailAttr = {\n mailOption: (attributes.type || \"send\").toLowerCase(),\n person: attributes.person || \"Unknown\",\n subject: attributes.subject || \"Empty\",\n };\n\n return toNode(\n \"div\",\n {\n class: \"bb-email\",\n \"data-bb-email\": mailAttr.mailOption,\n },\n [\n emailHeader,\n parseEmailPerson(mailAttr.person),\n parseEmailSubject(mailAttr.subject),\n parseEmailContent(node.content),\n emailFooter,\n ],\n );\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * @file Adds [row][column] to bbcode\n * @example Adds [row][column][/column][/row]\n */\nexport const rowcolumn = {\n row: (node) => toNode(\"div\", { class: \"bb-row\" }, node.content),\n column: (node, options) => {\n const columnAttrs = preprocessAttr(node, options.data.raw)._default || \"8\";\n const columnStyle = columnAttrs.startsWith(\"span\")\n ? `column-width-${columnAttrs}`\n : `column-width-span${columnAttrs}`;\n return toNode(\"div\", { class: `bb-column`, \"data-span\": columnStyle }, node.content);\n },\n};\n","import { preprocessAttr } from \"../utils/common\";\n\nconst EVENTS = [\n \"init\",\n \"click\",\n \"change\",\n \"input\",\n \"dblclick\",\n \"mouseenter\",\n \"mouseleave\",\n \"scroll\",\n];\n\n/**\n * Script tag\n *\n * [script]content[/script]\n *\n * [script class=\"id\" on=\"event\" version=\"2\"]content[/script]\n */\nexport const script = (node, options) => {\n const attrs = preprocessAttr(node, options.data.raw);\n\n if (!options.data.previewing && !options.data.commonGUID) {\n // create a common GUID for the post\n // only applicable for div, style, and script tags\n // this is to prevent the same class name from being used in different posts\n options.data.commonGUID = \"post-\" + Math.random().toString(36).substring(2, 7);\n }\n const classSuffix = options.data.previewing ? \"preview\" : options.data.commonGUID;\n\n const onEvent =\n (EVENTS.includes(attrs.on?.toLowerCase() || \"init\") && attrs.on?.toLowerCase()) || \"init\";\n\n const scriptSetup = {\n id: classSuffix,\n class: attrs.class || \"\",\n on: onEvent,\n version: attrs.version || \"\",\n content: node.content.join(\"\"),\n };\n options.data.bbscripts.push(scriptSetup);\n\n return [];\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * Parses an inputted size value and returns the formatted valid font size\n * @param {string} fontValue the input of the size\n */\nfunction parseFontSize(fontValue) {\n let value;\n let fontSize = { valid: true };\n const parsedSize = /(\\d+\\.?\\d?)(px|rem)?/i.exec(fontValue);\n const sizeRanges = {\n px_max: 36,\n px_min: 8,\n rem_max: 3,\n rem_min: 0.2,\n unitless_max: 7,\n unitless_min: 1,\n };\n\n if (parsedSize && (value = parsedSize[1])) {\n fontSize.unit = (parsedSize[2] || \"\").toLowerCase();\n switch (fontSize.unit) {\n case \"px\":\n if (value > sizeRanges.px_max) {\n value = sizeRanges.px_max;\n } else if (value < sizeRanges.px_min) {\n value = sizeRanges.px_min;\n }\n break;\n case \"rem\":\n if (value > sizeRanges.rem_max) {\n value = sizeRanges.rem_max;\n } else if (value < sizeRanges.rem_min) {\n value = sizeRanges.rem_min;\n }\n break;\n default:\n if ((fontSize.valid = fontValue.length === value.length)) {\n if (value > sizeRanges.unitless_max) {\n value = sizeRanges.unitless_max;\n } else if (value < sizeRanges.unitless_min) {\n value = sizeRanges.unitless_min;\n }\n }\n break;\n }\n\n fontSize.value = value;\n }\n return fontSize;\n}\n\nexport const size = (node) => {\n const input = preprocessAttr(node)._default;\n const fontSize = parseFontSize(input);\n if (!fontSize.valid) {\n return node.content;\n }\n let outputAttr = {};\n if (fontSize.unit) {\n outputAttr = { style: `font-size: ${fontSize.value}${fontSize.unit}` };\n } else {\n outputAttr = { \"data-size\": fontSize.value };\n }\n return toNode(\"span\", outputAttr, node.content);\n};\n","import { isTagNode } from \"@bbob/plugin-helper\";\nimport { preprocessAttr, toNode, toRawTag } from \"../utils/common\";\n\n// These are the variants supported by RpNation's XenForo table stylesheet.\nconst TABLE_STYLE = /^(?:(?:none|dotted|dark)(?:-zebra2?)?|zebra2?)$/;\nconst ROW_STYLES = new Set([\"blue\", \"gray\", \"grey\"]);\nconst ROW_TAGS = new Set([\"tr\"]);\nconst CELL_TAGS = new Set([\"td\", \"th\", \"tf\"]);\n\nfunction hasUnexpectedContent(content, allowedTags) {\n return content.some((child) => {\n if (typeof child === \"string\") {\n return child.trim().length > 0;\n }\n return !isTagNode(child) || !allowedTags.has(child.tag.toLowerCase());\n });\n}\n\nfunction option(node, options) {\n return String(preprocessAttr(node, options.data.raw)._default || \"\")\n .trim()\n .toLowerCase();\n}\n\nfunction table(node, options) {\n if (node.gen) {\n return node;\n }\n\n const rows = node.content.filter((child) => isTagNode(child) && child.tag.toLowerCase() === \"tr\");\n if (\n !rows.length ||\n hasUnexpectedContent(node.content, ROW_TAGS) ||\n rows.some((row) => hasUnexpectedContent(row.content, CELL_TAGS))\n ) {\n // Invalid legacy tables can contain meaningful text outside their cells.\n // Keep their source visible instead of silently discarding that content.\n return toRawTag(node, options.data.raw);\n }\n rows.forEach((row) => (row.isBBCodeTableRow = true));\n\n const style = option(node, options);\n const attrs = { class: \"bb-table\" };\n if (TABLE_STYLE.test(style)) {\n attrs[\"data-bb-table-style\"] = style;\n }\n return toNode(\"table\", attrs, rows);\n}\n\nfunction tr(node, options) {\n if (node.gen) {\n return node;\n }\n if (!node.isBBCodeTableRow) {\n return toRawTag(node, options.data.raw);\n }\n\n const cells = node.content.filter(\n (child) => isTagNode(child) && CELL_TAGS.has(child.tag.toLowerCase())\n );\n cells.forEach((tableCell) => (tableCell.isBBCodeTableCell = true));\n\n const style = option(node, options);\n const attrs = {};\n if (ROW_STYLES.has(style)) {\n attrs[\"data-bb-table-row\"] = style === \"grey\" ? \"gray\" : style;\n }\n return toNode(\"tr\", attrs, cells);\n}\n\nfunction cell(node, options) {\n if (node.gen) {\n return node;\n }\n if (!node.isBBCodeTableCell) {\n return toRawTag(node, options.data.raw);\n }\n\n const tag = node.tag.toLowerCase();\n const attrs = tag === \"tf\" ? { class: \"bb-table-footer\" } : {};\n const span = option(node, options);\n // HTML colspans are positive integers, with a maximum of 1000.\n if (/^\\d{1,4}$/.test(span) && Number(span) > 0 && Number(span) <= 1000) {\n attrs.colspan = String(Number(span));\n }\n return toNode(tag === \"th\" ? \"th\" : \"td\", attrs, node.content);\n}\n\nexport const tableTags = { table, tr, td: cell, th: cell, tf: cell };\n","import { isTagNode } from \"@bbob/plugin-helper\";\nimport { preprocessAttr, toNode, toRawTag } from \"../utils/common\";\n\n/**\n * @file Adds textmessage to bbcode\n * @example [textmessage=Recipient][message=them]Hi [/message][message=me] Hey![/message][/textmessage]\n */\n\nconst ACCEPTED_OPTIONS = [\"me\", \"them\", \"right\", \"left\"];\nexport const textmessage = {\n textmessage: (node, options) => {\n const messageList = node.content.filter(\n (contentNode) => isTagNode(contentNode) && contentNode.tag === \"message\"\n );\n messageList.forEach((messageNode) => {\n messageNode.isValid = true;\n });\n\n if (!messageList.length) {\n return toRawTag(node, options.data.raw);\n }\n\n const attr = preprocessAttr(node, options.data.raw)._default || \"Recipient\";\n const recipient = attr && attr.trim() !== \"\" ? attr : \"Recipient\";\n return toNode(\"div\", { class: \"bb-textmessage\" }, [\n toNode(\"div\", { class: \"bb-textmessage-name\" }, recipient),\n toNode(\"div\", { class: \"bb-textmessage-overflow\" }, [\n toNode(\"div\", { class: \"bb-textmessage-content\" }, node.content),\n ]),\n ]);\n },\n message: (node, options) => {\n if (!node.isValid) {\n return toRawTag(node, options.data.raw);\n }\n\n let option = preprocessAttr(node, options?.data?.raw)?._default?.toLowerCase();\n if (!ACCEPTED_OPTIONS.includes(option) || option === \"right\") {\n option = \"me\";\n }\n if (option === \"left\") {\n option = \"them\";\n }\n\n const senderAttrs = option === \"me\" ? \"bb-message-me\" : \"bb-message-them\";\n return toNode(\"div\", { class: senderAttrs }, [\n toNode(\"div\", { class: \"bb-message-content\" }, node.content),\n ]);\n },\n};\n","import { createPreset } from \"@bbob/preset\";\nimport { accordionTags } from \"./tags/accordion\";\nimport { alignment } from \"./tags/alignment\";\nimport { anchor } from \"./tags/anchor\";\nimport { animation, keyframe } from \"./tags/animation\";\nimport { bg } from \"./tags/background\";\nimport { block } from \"./tags/block\";\nimport { blockquote } from \"./tags/blockquote\";\nimport { border } from \"./tags/border\";\nimport { centerblock } from \"./tags/centerblock\";\nimport { check } from \"./tags/check\";\nimport { classStyle } from \"./tags/class\";\nimport { code, icode, savenl } from \"./tags/code\";\nimport { color } from \"./tags/color\";\nimport { comment } from \"./tags/comment\";\nimport { bold, italic, strike, underline } from \"./tags/discourse-core-replacement\";\nimport { div } from \"./tags/div\";\nimport { divide } from \"./tags/divide\";\nimport { fieldset } from \"./tags/fieldset\";\nimport { font } from \"./tags/font\";\nimport { fa } from \"./tags/fontawesome\";\nimport { h, h1, h2, h3, h4, h5, h6, sh } from \"./tags/header\";\nimport { heightrestrict } from \"./tags/heightrestrict\";\nimport { highlight } from \"./tags/highlight\";\nimport { imagefloat } from \"./tags/imagefloat\";\nimport { justify } from \"./tags/justify\";\nimport { br, nobr } from \"./tags/lineBreak\";\nimport { list } from \"./tags/list\";\nimport { mail } from \"./tags/mail\";\nimport { newspaper } from \"./tags/newspaper\";\nimport { note } from \"./tags/note\";\nimport { ooc } from \"./tags/ooc\";\nimport { pindent } from \"./tags/pindent\";\nimport { plain } from \"./tags/plain\";\nimport { print } from \"./tags/print\";\nimport { progress } from \"./tags/progress\";\nimport { quote } from \"./tags/quote\";\nimport { rowcolumn } from \"./tags/rowcolumn\";\nimport { script } from \"./tags/script\";\nimport { scroll } from \"./tags/scroll\";\nimport { side } from \"./tags/side\";\nimport { size } from \"./tags/size\";\nimport { inlinespoiler, spoiler } from \"./tags/spoiler\";\nimport { sub } from \"./tags/subscript\";\nimport { sup } from \"./tags/superscript\";\nimport { tableTags } from \"./tags/table\";\nimport { tab, tabs } from \"./tags/tabs\";\nimport { textmessage } from \"./tags/textmessage\";\nimport { thinprogress } from \"./tags/thinprogress\";\n\nconst tags = {\n ...accordionTags,\n ...alignment,\n ...tableTags,\n ...anchor,\n animation,\n bg,\n block,\n blockquote,\n border,\n br,\n centerblock,\n check,\n class: classStyle,\n code,\n color,\n comment,\n div,\n divide,\n fieldset,\n fa,\n font,\n h,\n h1,\n h2,\n h3,\n h4,\n h5,\n h6,\n heightrestrict,\n highlight,\n icode,\n imagefloat,\n inlinespoiler,\n justify,\n keyframe,\n list,\n mail,\n newspaper,\n nobr,\n note,\n ooc,\n pindent,\n plain,\n print,\n progress,\n quote,\n ...rowcolumn,\n thinprogress,\n savenl,\n sh,\n script,\n scroll,\n side,\n size,\n spoiler,\n sub,\n sup,\n tab,\n tabs,\n ...textmessage,\n\n // discourse core replacement tags\n b: bold,\n i: italic,\n u: underline,\n s: strike,\n};\n\nconst availableTags = Object.keys(tags);\nconst preventParsing = [\"plain\", \"code\", \"icode\", \"class\", \"fa\"];\n\nconst preset = createPreset(tags);\n\nexport { availableTags, tags, preset, preventParsing };\nexport default preset;\n","import { isStringNode, isTagNode } from \"@bbob/plugin-helper\";\nimport { preprocessAttr, toOriginalEndTag, toOriginalStartTag } from \"../utils/common\";\n\n/**\n * Renders css Keyframes\n *\n * [animation=name][keyframe=0]color: red[/keyframe][/animation]\n */\nexport const animation = (node, options) => {\n if (!options.data.previewing && !options.data.commonGUID) {\n // create a common GUID for the post\n // only applicable for div, style, and script tags\n // this is to prevent the same class name from being used in different posts\n options.data.commonGUID = \"post-\" + Math.random().toString(36).substring(2, 7);\n }\n const commonId = options.data.previewing ? \"preview\" : options.data.commonGUID;\n\n const name = preprocessAttr(node, options.data.raw)?._default || \"\";\n const keyframes = node.content\n .filter((n) => isTagNode(n) && n.tag === \"keyframe\")\n .map((content) => {\n content.isValid = true;\n /** @type {string} */\n const ident = preprocessAttr(content, options.data.raw)._default || \"\";\n content.ident = ident + (ident.match(/^\\d+$/) ? \"%\" : \"\");\n const cleanContent = content.content\n .filter(isStringNode)\n .join(\"\")\n .replaceAll(/[\\[\\]\\{\\}]/g, \"\");\n content.formatted = `${content.ident}{ ${cleanContent} }`;\n return content;\n });\n const keyframeContent = keyframes.map((n) => n.formatted).join(\"\\n\");\n const content = `@keyframes ${commonId}${name} { ${keyframeContent} }`;\n options.data.styles.push(content);\n return [];\n};\n\nexport const keyframe = (node, options) => {\n if (!node.isValid) {\n return [\n toOriginalStartTag(node, options.data.raw),\n ...node.content,\n toOriginalEndTag(node, options.data.raw),\n ];\n }\n return [];\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * Add [bg] tag\n * @example [bg=red]Hello[/bg]\n */\nexport const bg = (node, options) => {\n const color = preprocessAttr(node, options.data.raw)._default;\n return toNode(\n \"div\",\n {\n style: `background-color: ${color};`,\n class: \"bb-background\",\n },\n node.content,\n );\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * Add [block] tag\n * @example [block=treasure]content[/block]\n */\nexport const block = (node, options) => {\n const defaultOp = \"block\";\n const blockAttr = (preprocessAttr(node, options.data.raw)._default || defaultOp).toLowerCase();\n\n const OPTIONS = [\n \"block\",\n \"dice\",\n \"dice10\",\n \"setting\",\n \"warning\",\n \"storyteller\",\n \"announcement\",\n \"important\",\n \"question\",\n \"encounter\",\n \"information\",\n \"character\",\n \"treasure\",\n ];\n\n // Default to block option if user did not provide anything valid\n const blockOption = OPTIONS.includes(blockAttr) ? blockAttr : defaultOp;\n\n return toNode(\"table\", { class: \"bb-block\", \"data-bb-block\": blockOption }, [\n toNode(\"tbody\", [\n toNode(\"tr\", [\n toNode(\"td\", { class: \"bb-block-icon\" }),\n toNode(\"td\", { class: \"bb-block-content\" }, node.content),\n ]),\n ]),\n ]);\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * @file Adds [blockquote] to bbcode\n * @example [blockquote=author]content[/blockquote]\n */\nexport const blockquote = (node, options) => {\n const author = preprocessAttr(node, options.data.raw)._default || \"\";\n\n return toNode(\"div\", { class: \"bb-blockquote\" }, [\n toNode(\"div\", { class: \"bb-blockquote-left\" }),\n toNode(\"div\", { class: \"bb-blockquote-content\" }, [\n node.content,\n toNode(\"div\", { class: \"bb-blockquote-speaker\" }, author !== \"\" ? `- ${author}` : \"\"),\n ]),\n toNode(\"div\", { class: \"bb-blockquote-right\" }),\n ]);\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\nexport const border = (node, options) => {\n const val = preprocessAttr(node, options.data.raw)._default;\n return toNode(\n \"div\",\n {\n style: `border: ${val};`,\n class: \"bb-border\",\n },\n node.content,\n );\n};\n","import { toNode } from \"../utils/common\";\n\n/**\n * Creates a line break html
      tag\n */\nexport const br = () => {\n return toNode(\"br\", {}, null);\n};\n\n/**\n * Disables line breaks for given content\n * @example\n * ```\n * [nobr]test\n * test\n * test\n * [/nobr]\n *\n * test test test\n * ```\n */\nexport const nobr = (node) => {\n return {\n disableLineBreakConversion: true,\n preserveHeadingText: true,\n content: node.content,\n };\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\nexport const centerblock = (node, options) => {\n const percentageInput = preprocessAttr(node, options.data.raw)._default || \"50\";\n return toNode(\"div\", { style: `margin: 0 auto; width: ${percentageInput}%` }, node.content);\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\nexport const check = (node, options) => {\n const attrs = preprocessAttr(node, options.data.raw)._default || \"dot\";\n return toNode(\"div\", { class: `bb-check`, \"data-type\": attrs }, node.content);\n};\n","import { isStringNode } from \"@bbob/plugin-helper\";\nimport { preprocessAttr } from \"../utils/common\";\n\n/**\n * Class style tag\n *\n * [class=name]content[/class]\n * [class name=\"className\" state=\"psuedo-class\" minWidth=\"\" maxWidth=\"\"]content[/class]\n * [class name=\"className\" selector=\"\"]content[/class]\n */\nexport const classStyle = (node, options) => {\n const attrs = preprocessAttr(node);\n const nameAttr = attrs.name || attrs._default;\n\n if (!options.data.previewing && !options.data.commonGUID) {\n // create a common GUID for the post\n // only applicable for div, style, and script tags\n // this is to prevent the same class name from being used in different posts\n options.data.commonGUID = \"post-\" + Math.random().toString(36).substring(2, 7);\n }\n const classSuffix = options.data.previewing ? \"preview\" : options.data.commonGUID;\n const className = nameAttr + \"__\" + classSuffix;\n const content = node.content\n .filter(isStringNode)\n .map((s) => s.replaceAll(\"{post_id}\", classSuffix).replaceAll(/[\\[\\]\\{\\}]/g, \"\"));\n let selector = \"\";\n const mediaQuery = [];\n if (\n [\"hover\", \"focus\", \"active\", \"focus-within\", \"focus-visible\"].includes(\n attrs.state?.toLowerCase(),\n )\n ) {\n selector = \":\" + attrs.state.toLowerCase();\n }\n if (attrs.selector) {\n selector = attrs.selector.replace(/[,{}\\\\\\n]/g, \"\");\n }\n if (attrs.minWidth?.match(/^[0-9]+[a-z]+$/)) {\n // @media (min-width: )\n mediaQuery.push(`(min-width: ${attrs.minWidth})`);\n }\n if (attrs.maxWidth?.match(/^[0-9]+[a-z]+$/)) {\n // @media (max-width: )\n mediaQuery.push(`(max-width: ${attrs.maxWidth})`);\n }\n\n content.unshift(`.${className}${selector} {`);\n content.push(\"}\");\n if (mediaQuery.length) {\n content.unshift(`@media ${mediaQuery.join(\" and \")} {`);\n content.push(\"}\");\n }\n options.data.styles.push(content.join(\"\"));\n\n return [];\n};\n","import { preprocessAttr } from \"../utils/common\";\n\n/**\n * processes [code] tag and returns a fenced code block\n */\nexport const code = (node) => {\n const lang = preprocessAttr(node)._default || \"bbcode\";\n return {\n isWhitespaceSensitive: true,\n content: [\"```\" + lang + \"\\n\", node.content, \"\\n```\\n\"],\n };\n};\n\n/**\n * processes [icode] tag and returns inline code\n */\nexport const icode = (node) => {\n return {\n isWhitespaceSensitive: true,\n content: [\"`\", node.content, \"`\"],\n };\n};\n\n/**\n * Special tag to save newlines in code blocks. Used for hoisting code blocks\n */\nexport const savenl = (node) => {\n return {\n isWhitespaceSensitive: true,\n content: node.content,\n };\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\nexport const color = (node) => {\n const inputColor = preprocessAttr(node)._default || \"\";\n if (inputColor.trim() === \"\") {\n return node.content;\n }\n return toNode(\"span\", { style: `color: ${inputColor}` }, node.content);\n};\n","import { toNode } from \"../utils/common\";\n\n/**\n * @file Adds [comment] tag\n * @example [comment]Content[/comment]\n */\n\nconst comment = (node) => {\n return toNode(\"span\", { class: \"hidden\" }, node.content);\n};\n\nexport { comment };\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * Adds [div] tag\n * [div=css]Content[/div]\n * [div class=\"class\" style=\"css\"]Content[/div]\n */\nexport const div = (node, options) => {\n if (node.gen) {\n // node is actually a generated node \"div\" made by another tag\n // don't process it\n return node;\n }\n const attrs = preprocessAttr(node, options.data.raw);\n const style = attrs.style || attrs._default;\n const classAttrs = attrs.class;\n if (!classAttrs?.trim()) {\n return toNode(\n \"div\",\n {\n \"data-bbcode-div\": \"true\",\n style,\n },\n node.content\n );\n }\n\n if (!options.data.previewing && !options.data.commonGUID) {\n // create a common GUID for the post\n // only applicable for div, style, and script tags\n // this is to prevent the same class name from being used in different posts\n options.data.commonGUID = \"post-\" + Math.random().toString(36).substring(2, 7);\n }\n const classSuffix = options.data.previewing ? \"preview\" : options.data.commonGUID;\n const classNames = classAttrs\n .split(\" \")\n .map((c) => c + \"__\" + classSuffix)\n .join(\" \");\n\n return toNode(\n \"div\",\n {\n \"data-bbcode-div\": \"true\",\n class: classNames,\n style,\n },\n node.content\n );\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\nexport const divide = (node) => {\n const type = (preprocessAttr(node)._default || \"\").toLowerCase();\n return toNode(\n \"span\",\n {\n class: \"bb-divide\",\n \"data-type\": type,\n },\n node.content,\n );\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * @file Adds [fieldset] to bbcode\n * @example [fieldset=title]content[/fieldset]\n */\nexport const fieldset = (node, options) => {\n const title = preprocessAttr(node, options.data.raw)._default || \"\";\n return toNode(\"fieldset\", { class: \"bb-fieldset\" }, [\n toNode(\"legend\", { class: \"bb-fieldset-legend\" }, title),\n toNode(\"div\", { class: \"bb-fieldset\" }, node.content),\n ]);\n};\n","import { toNode } from \"../utils/common\";\n\n/**\n * Adds [fa] tag\n * [fa]fa-icon[/fa]\n * [fa style=\"\" fa-transform=\"\"]fa-solid fa-icon[/fa]\n * [fa primary-color=\"\" secondary-color=\"\" primary-opacity=\"\" secondary-opacity=\"\" rotate-angle=\"\"]fa-duotone fa-icon[/fa]\n */\nexport const fa = (node) => {\n const attrs = node.attrs;\n let style = attrs.style || \"\";\n style += attrs[\"primary-color\"] ? `--fa-primary-color: ${attrs[\"primary-color\"]};` : \"\";\n style += attrs[\"secondary-color\"] ? `--fa-secondary-color: ${attrs[\"secondary-color\"]};` : \"\";\n style += attrs[\"primary-opacity\"] ? `--fa-primary-opacity: ${attrs[\"primary-opacity\"]};` : \"\";\n style += attrs[\"secondary-opacity\"]\n ? `--fa-secondary-opacity: ${attrs[\"secondary-opacity\"]};`\n : \"\";\n style += attrs[\"rotate-angle\"] ? `--fa-rotate-angle: ${attrs[\"rotate-angle\"]};` : \"\";\n\n return toNode(\n \"i\",\n {\n \"data-bbcode-fa\": null,\n },\n [\n toNode(\n \"i\",\n {\n class: (node.content || []).join(\"\"),\n style,\n \"data-fa-transform\": attrs[\"fa-transform\"] || \"\",\n },\n [],\n ),\n ],\n );\n};\n","import { toNode } from \"../utils/common\";\n\n/**\n * @file Adds Header to bbcode\n * @example [h]content[/h], [h2]content[/h2], [h3]content[/h3],\n * [h4]content[/h4], [h5]content[/h5], [h6]content[/h6].\n */\n\nconst h = (node) => {\n return toNode(\"h1\", {}, node.content);\n};\n\nconst h1 = (node) => {\n return toNode(\"h1\", {}, node.content);\n};\n\nconst h2 = (node) => {\n return toNode(\"h2\", {}, node.content);\n};\n\nconst sh = (node) => {\n return toNode(\"h2\", {}, node.content);\n};\n\nconst h3 = (node) => {\n return toNode(\"h3\", {}, node.content);\n};\n\nconst h4 = (node) => {\n return toNode(\"h4\", {}, node.content);\n};\n\nconst h5 = (node) => {\n return toNode(\"h5\", {}, node.content);\n};\n\nconst h6 = (node) => {\n return toNode(\"h6\", {}, node.content);\n};\n\nexport { h, sh, h1, h2, h3, h4, h5, h6 };\n","import { toNode } from \"../utils/common\";\n/**\n * @file Adds [highlight] to bbcode\n * @example [highlight]content[/highlight]\n */\nexport const highlight = (node) => {\n return toNode(\"span\", { class: \"bb-highlight\" }, node.content);\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n/**\n * @file Adds [imagefloat] to bbcode\n * @exmaple [imagefloat=left]content[/imagefloat]\n */\nexport const imagefloat = (node) => {\n const attrs = preprocessAttr(node)._default || \"\";\n return toNode(\"div\", { class: `bb-float-${attrs}` }, node.content);\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n/**\n * @file Adds [spoiler] and [inlinespoiler] to bbcode\n *\n * Defaults to \"Spoiler\" name if no title provided\n *\n * @example `[spoiler=Title]text[/spoiler]`\n * @example `[inlinespoiler]hidden content[/inlinespoiler]\n */\n\nexport const spoiler = (node, options) => {\n const providedTitle = preprocessAttr(node, options.data.raw)._default;\n const title = \"Spoiler\" + (providedTitle ? `: ${providedTitle}` : \"\");\n\n /**\n *
      \n * Title\n *
      \n * lorem ipsum\n *
      \n *
      \n */\n return toNode(\"details\", { class: \"bb-spoiler\" }, [\n toNode(\"summary\", {}, title),\n toNode(\"div\", { class: \"bb-spoiler-content\" }, node.content),\n ]);\n};\n\nexport const inlinespoiler = (node) => {\n return toNode(\"span\", { class: \"bb-inline-spoiler\" }, node.content);\n};\n","import { toNode } from \"../utils/common\";\n\n/**\n * @file Adds [justify] to bbcode\n * @example [justify]content[/justify]\n */\nexport const justify = (node) => {\n return toNode(\"div\", { class: \"bb-justify\" }, node.content);\n};\n","import { isStringNode } from \"@bbob/plugin-helper\";\nimport { preprocessAttr, toNode } from \"../utils/common\";\n\n// XenForo's [*] is an item separator, not a paired BBCode tag. Split only\n// strings here so nested lists and formatting nodes stay inside their item.\nexport const list = (node, options) => {\n const items = [];\n let content = [];\n\n function appendItem() {\n while (content.length && isStringNode(content[0]) && !content[0].trim()) {\n content.shift();\n }\n while (\n content.length &&\n isStringNode(content[content.length - 1]) &&\n !content[content.length - 1].trim()\n ) {\n content.pop();\n }\n if (content.length) {\n if (isStringNode(content[0])) {\n content[0] = content[0].trimStart();\n }\n const last = content.length - 1;\n if (isStringNode(content[last])) {\n content[last] = content[last].trimEnd();\n }\n items.push(toNode(\"li\", {}, content));\n }\n content = [];\n }\n\n for (const child of node.content) {\n if (!isStringNode(child)) {\n content.push(child);\n continue;\n }\n const parts = child.split(\"[*]\");\n content.push(parts[0]);\n for (const part of parts.slice(1)) {\n appendItem();\n content.push(part);\n }\n }\n appendItem();\n\n let style = String(preprocessAttr(node, options.data.raw)._default || \"\").trim();\n if (\n (style.startsWith(\"'\") && style.endsWith(\"'\")) ||\n (style.startsWith('\"') && style.endsWith('\"'))\n ) {\n style = style.slice(1, -1);\n }\n return toNode(style === \"1\" ? \"ol\" : \"ul\", {}, items);\n};\n","import { toNode } from \"../utils/common\";\n/**\n * @file Adds [newspaper] to bbcode\n * @example [newspaper]content[/newspaper]\n */\nexport const newspaper = (node) => {\n return toNode(\"div\", { class: \"bb-newspaper\" }, node.content);\n};\n","import { toNode } from \"../utils/common\";\n/**\n * @file Adds [note] to bbcode\n * @example [note]content[/note]\n */\n\nexport const note = (node) => {\n return toNode(\"div\", { class: \"bb-note\" }, [\n toNode(\"div\", { class: \"bb-note-tape\" }, \"\"),\n toNode(\"div\", { class: \"bb-note-content\" }, [\n node.content,\n toNode(\"div\", { class: \"bb-note-footer\" }, \"\"),\n ]),\n ]);\n};\n","import { toNode } from \"../utils/common\";\n\n/**\n * @file Adds [ooc] to bbcode\n * @example [ooc]content[/ooc]\n */\nexport const ooc = (node) => {\n return toNode(\n \"div\",\n {\n class: \"bb-ooc\",\n },\n node.content,\n );\n};\n","import { toNode } from \"../utils/common\";\n/**\n * @file Adds [pindent] to bbcode\n * @example [pindent]content[/pindent]\n */\nexport const pindent = (node) => {\n return toNode(\"span\", { class: \"bb-pindent\" }, node.content);\n};\n","/**\n * [plain] bbcode tag that prevents parsing of inner tags\n * @example\n * ```\n * [plain]This is [b]bold[/b] and [i]italic[/i][/plain]\n * ```\n * outputs to\n * ```\n * This is [b]bold[/b] and [i]italic[/i]\n * ```\n */\nexport const plain = (node) => {\n return node.content;\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * Add [print] tag\n * @example [print=lined]content[/print]\n */\nexport const print = (node) => {\n const defaultOp = \"print\";\n const printAttr = (preprocessAttr(node)._default || defaultOp).toLowerCase();\n\n const OPTIONS = [\"print\", \"line\", \"graph\", \"parchment\"];\n\n // Default to print if option is not valid\n const printOption = OPTIONS.includes(printAttr) ? printAttr : defaultOp;\n\n return toNode(\n \"div\",\n { class: printOption === defaultOp ? `bb-print` : `bb-print-${printOption}` },\n node.content,\n );\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * @file Adds [progress] to bbcode\n * @exmaple [progress=percentageInt]content[/progress]\n */\nexport const progress = (node) => {\n const percentageInt = preprocessAttr(node)._default;\n return toNode(\"div\", { class: \"bb-progress\" }, [\n toNode(\"div\", { class: \"bb-progress-text\" }, node.content),\n toNode(\"div\", { class: \"bb-progress-bar\", style: `width: calc(${percentageInt}% - 6px)` }, \"\"),\n toNode(\"div\", { class: \"bb-progress-bar-other\" }, \"\"),\n ]);\n};\n","import { preprocessAttr } from \"../utils/common\";\n\n/**\n * rebuild the [quote] tag so that markdown-it engine can parse it for itself\n */\nexport const quote = (node, options) => {\n const attrs = preprocessAttr(node, options.data.raw);\n if (node.content[0] === \"\\n\") {\n node.content.shift();\n }\n return [`\\n[${node.tag}=\"${attrs._default}\"]\\n\\n`, ...node.content, \"\\n\\n[/quote]\\n\"];\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * @file Adds [thinprogress] to bbcode\n * @exmaple [thinprogress=percentageInt]content[/progthinprogressress]\n */\nexport const thinprogress = (node, options) => {\n const percentageInt = preprocessAttr(node, options.data.raw)._default;\n return toNode(\"div\", { class: \"bb-progress-thin\" }, [\n toNode(\"div\", { class: \"bb-progress-text\" }, node.content),\n toNode(\"div\", { class: \"bb-progress-bar\", style: `width: calc(${percentageInt}% - 6px)` }, \"\"),\n toNode(\"div\", { class: \"bb-progress-bar-other\" }, \"\"),\n ]);\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\n/**\n * Parse the user provided height and return a valid height value\n * @param {Number} heightValue obtains the input of the user entered height (default is 700)\n * @returns A validated number less than 0.\n */\nfunction parseHeight(heightValue) {\n const maxHeight = 700;\n const parsedHeight =\n heightValue && heightValue.trim() !== \"\" ? heightValue.replace(/[^\\d.]/g, \"\") : 0;\n\n if (parsedHeight && parsedHeight >= 0 && parsedHeight <= maxHeight) {\n return parsedHeight;\n } else {\n // if the value = 0 then nothing will be returned\n return parsedHeight === 0 ? 0 : maxHeight;\n }\n}\n\n/**\n * @file Adds [scroll] to bbcode\n * @example [scroll]content[/scroll]\n */\nexport const scroll = (node, options) => {\n const attrs = preprocessAttr(node, options.data.raw)._default;\n const heightInput = parseHeight(attrs);\n return toNode(\"div\", { class: \"bb-scroll\", style: `height: ${heightInput}px` }, node.content);\n};\n","import { preprocessAttr, toNode } from \"../utils/common\";\n\nexport const side = (node) => {\n const attrs = preprocessAttr(node)._default || \"left\";\n return toNode(\"div\", { class: \"bb-side\", \"data-side\": attrs }, node.content);\n};\n","import { toNode } from \"../utils/common\";\n\n/**\n * @file Adds subscript to BBCode\n * @example [sub]content[/sub]\n */\n\nconst sub = (node) => {\n return toNode(\"sub\", {}, node.content);\n};\n\nexport { sub };\n","import { toNode } from \"../utils/common\";\n\n/**\n * @file Adds superscript to bbcode\n * @example [sup]content[/sup]\n */\n\nconst sup = (node) => {\n return toNode(\"sup\", {}, node.content);\n};\n\nexport { sup };\n","import { isTagNode } from \"@bbob/plugin-helper\";\nimport { generateGUID, preprocessAttr, toNode, toRawTag } from \"../utils/common\";\n\n/**\n * @file Adds [tabs][tab] to bbcode\n * @example [tabs][tab=name 1]content[/tab][tab=name 2]content[/tab][/tabs]\n */\nexport const tabs = (node, options) => {\n const tabsList = node.content.filter(\n (contentNode) => isTagNode(contentNode) && contentNode.tag === \"tab\"\n );\n const groupId = generateGUID();\n tabsList.forEach((tabNode) => {\n tabNode.isValid = true;\n tabNode.groupId = groupId;\n });\n\n if (!tabsList.length) {\n return toRawTag(node, options.data.raw);\n }\n\n tabsList[0].open = true;\n return toNode(\n \"div\",\n {\n class: \"bb-tabs\",\n },\n tabsList\n );\n};\n\n/**\n * [tab=name]content[/tab]\n * [tab name=\"name\" style=\"style\"]content[/tab]\n */\nexport const tab = (node, options) => {\n if (!node.isValid) {\n return toRawTag(node, options.data.raw);\n }\n\n const attrs = preprocessAttr(node, options.data.raw);\n const name = attrs?._default || attrs?.name || \"Tab\";\n const tabId = `tab-${name.replace(/\\W/g, \"_\")}-${generateGUID()}`;\n return [\n toNode(\"input\", {\n type: \"radio\",\n id: tabId,\n name: \"tab-group-\" + node.groupId,\n class: \"bb-tab\",\n checked: node.open,\n }),\n toNode(\n \"label\",\n {\n class: \"bb-tab-label\",\n for: tabId,\n style: attrs.style,\n },\n name\n ),\n toNode(\n \"div\",\n {\n class: \"bb-tab-content\",\n },\n node.content\n ),\n ];\n};\n","/**\n * @file discourse-core-replacement.js\n * This is a dedicated file for replacing the standard Discourse BBCode tags in core.\n * In the markdown-it engine, discourse has added these bbcode tags in the inline parser.\n * However this means that if the parser detects a block level tag inside an inline tag,\n * it will not parse the inline tag.\n *\n * This file is meant to fix such scenarios by doing the parsing of bbcode tags for it.\n *\n * @example\n * [b][h]bold[/h][/b] // this should properly parse the bold tag inside the h tag\n *\n * https://github.com/discourse/discourse/blob/d7ece61252d7671a1f124483836279b99852c08c/app/assets/javascripts/discourse-markdown-it/src/features/bbcode-inline.js\n */\nimport { toNode } from \"../utils/common\";\n\nexport const bold = (node) => {\n return toNode(\"span\", { class: \"bbcode-b\" }, node.content);\n};\n\nexport const italic = (node) => {\n if (node.gen) {\n // node is actually a generated node \"i\" made by another tag\n // don't process it\n return node;\n }\n return toNode(\"span\", { class: \"bbcode-i\" }, node.content);\n};\n\nexport const underline = (node) => {\n return toNode(\"span\", { class: \"bbcode-u\" }, node.content);\n};\n\nexport const strike = (node) => {\n return toNode(\"span\", { class: \"bbcode-s\" }, node.content);\n};\n","import {\n MD_BROKEN_BLOCKQUOTE,\n MD_BROKEN_ORDERED_LIST,\n MD_BROKEN_UNORDERED_LIST,\n MD_NEWLINE_INJECT,\n MD_NEWLINE_INJECT_COMMENT,\n MD_NEWLINE_PRE_INJECT,\n} from \"./common\";\n\n/**\n * Post Processing designed to fix issues with Markdown and BBCode that the parser can't fix.\n *\n * Separate from markdown-it post processing as it'll be able to manipulate the full string.\n * @param {string} raw string from processing through both BBCode and Markdown\n * @returns post processed string\n */\nfunction removeNewlineInjects(raw) {\n const processed = raw\n .replaceAll(MD_NEWLINE_INJECT, \"\")\n .replaceAll(MD_NEWLINE_PRE_INJECT, \"\")\n .replaceAll(\"\\n\" + MD_NEWLINE_INJECT_COMMENT, \"\")\n .replaceAll(MD_NEWLINE_INJECT_COMMENT + \"\\n\", \"\")\n .replaceAll(MD_NEWLINE_INJECT_COMMENT, \"\"); // Remove all instances of the injected newline\n return processed;\n}\n\nfunction cleanMultilineMDBlocks(raw) {\n const processed = raw\n .replaceAll(MD_BROKEN_ORDERED_LIST, \"\")\n .replaceAll(MD_BROKEN_UNORDERED_LIST, \"\")\n .replaceAll(MD_BROKEN_BLOCKQUOTE, \"\");\n return processed;\n}\n\n/**\n * Injects hoisted code blocks back into the raw string\n * @param {string} raw input to inject hoisted code blocks into\n * @param {any} data contains hoist map\n * @returns string with hoisted code blocks injected\n */\nfunction renderHoistedCodeBlocks(raw, data) {\n const hoistMap = data.hoistMap;\n for (const [uuid, content] of Object.entries(hoistMap)) {\n // These examples bypass Markdown's text renderer while hoisted. Escape\n // them when restoring them, so HTML examples stay text inside code/plain.\n const escaped = content\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\");\n raw = raw.replaceAll(uuid, escaped);\n }\n return raw;\n}\n\n/**\n * Setups the class style tag template for the post\n * @param {string} raw\n * @param {{styles: string[]}} data - contains styles array\n * @returns string\n */\nfunction createClassStyleTagTemplate(raw, data) {\n if (data.styles.length === 0) {\n return raw;\n }\n const template = '\";\n return template + raw;\n}\n\n/**\n * Setups the script tag template for the post\n * @param {string} raw\n * @param {{\n * bbscripts: {\n * id: string,\n * class: string,\n * on: string,\n * version: string,\n * content: string\n * }[]}} data - contains scripts array\n * @returns string\n */\nfunction createScriptTagTemplate(raw, data) {\n if (data.bbscripts.length === 0) {\n return raw;\n }\n const templates = data.bbscripts.map(\n (s) =>\n ``\n );\n return templates.join(\"\") + raw;\n}\n\n/**\n * Performs post processing on the raw string to address any necessary functionality that BBob/MD can't handle with a plugin (i.e. hoisting).\n * @param {string} raw processed input from after bbob and md\n * @param {any} data from bbob data\n * @returns final processed string\n */\nexport function postprocess(raw, data) {\n let final = raw;\n const postprocessors = [\n removeNewlineInjects,\n createClassStyleTagTemplate,\n createScriptTagTemplate,\n cleanMultilineMDBlocks,\n renderHoistedCodeBlocks,\n ];\n for (const postprocessor of postprocessors) {\n final = postprocessor(final, data);\n }\n return final;\n}\n","import { ESCAPABLES_REGEX, generateGUID, MD_TABLE_REGEX, regexIndexOf } from \"./common\";\n\n/**\n * Find all code blocks and hoist them out of the content and into a map for later insertion\n * @param {string} raw input to preprocess\n * @returns processed string and hoist map\n */\nfunction fenceCodeBlockPreprocess(content, data) {\n /** @type {Object.} */\n const hoistMap = {};\n let index = 0;\n\n const addHoistAndReturnNewStartPoint = (cutOffStart, cutOffEnd, expected, trim = false) => {\n const uuid = generateGUID();\n if (cutOffEnd !== -1) {\n hoistMap[uuid] = content.substring(cutOffStart, cutOffEnd);\n content = content.substring(0, cutOffStart) + uuid + content.substring(cutOffEnd);\n } else {\n hoistMap[uuid] = content.substring(cutOffStart);\n content = content.substring(0, cutOffStart) + uuid + expected;\n }\n if (trim) {\n if (hoistMap[uuid].startsWith(\"\\n\")) {\n hoistMap[uuid] = hoistMap[uuid].substring(1);\n }\n if (hoistMap[uuid].endsWith(\"\\n\")) {\n hoistMap[uuid] = hoistMap[uuid].substring(0, hoistMap[uuid].length - 1);\n }\n }\n return cutOffStart + uuid.length + expected.length;\n };\n\n while ((index = regexIndexOf(content, ESCAPABLES_REGEX, index)) !== -1) {\n const match = ESCAPABLES_REGEX.exec(content.substring(index));\n if (match.groups?.fence) {\n const fence = match.groups.fence;\n const fenceInfo = match.groups.fenceInfo;\n if (content[index] === \"\\n\") {\n // Check if the fence is not at the start of the content\n index += 1;\n }\n const closingFenceRegex = new RegExp(\"\\n\" + fence + \"(\\n|$)\"); // Find the next fence. By commonmark spec, it should be the same fence length and type\n const nextIndex = regexIndexOf(content, closingFenceRegex, index + fence.length);\n\n const uuid = generateGUID();\n if (nextIndex !== -1) {\n hoistMap[uuid] = content.substring(index + fence.length + fenceInfo.length, nextIndex);\n } else {\n hoistMap[uuid] = content.substring(index + fence.length + fenceInfo.length);\n }\n // inject bbcode tag before and after the code block. This is to prevent BBob plugin from injecting newlines\n const replacement = `[saveNL]\\n${fence}${fenceInfo}${uuid}\\n${fence}\\n[/saveNL]`;\n content =\n content.substring(0, index) +\n replacement +\n (nextIndex !== -1 ? content.substring(nextIndex + 1 + fence.length) : \"\");\n index = index + replacement.length;\n } else if (match.groups?.bbcode) {\n const bbcode = match.groups.bbcode;\n const bbcodeTag = match.groups.bbcodeTag.toLowerCase(); // coerce to lowercase for caseinsensitive matching\n const closingTag = `[/${bbcodeTag}]`;\n const nextIndex = content.toLowerCase().indexOf(closingTag, index + 1);\n index = addHoistAndReturnNewStartPoint(index + bbcode.length, nextIndex, closingTag, true);\n } else if (match.groups.backtick) {\n const backtick = match.groups.backtick; // contains whole content\n const tickStart = match.groups.tickStart;\n const tickEnd = match.groups.tickEnd;\n index = addHoistAndReturnNewStartPoint(\n index + tickStart.length,\n index + backtick.length - tickEnd.length,\n tickEnd,\n );\n }\n }\n\n data.hoistMap = hoistMap;\n return [content, data];\n}\n\n/**\n * Find all markdown table blocks and mark them to ignore newlines\n * @param {string} raw input to preprocess\n * @returns processed string\n */\nfunction mdTableBlockPreprocess(content, data) {\n let index = 0;\n while ((index = regexIndexOf(content, MD_TABLE_REGEX, index)) !== -1) {\n const match = MD_TABLE_REGEX.exec(content.substring(index));\n const table = match[0];\n const replacement = `[saveNL]\\n${table}\\n[/saveNL]`;\n content = content.substring(0, index) + replacement + content.substring(index + table.length);\n index = index + replacement.length;\n }\n return [content, data];\n}\n\n/**\n * Preprocesses input to be formatted for bbob to intake. Handles any necessary functionality that BBob can't handle with a plugin (i.e. hoisting).\n * @param {string} raw input to preprocess\n * @returns formatted input for bbob to intake\n */\nexport function preprocessRaw(raw) {\n let data = {};\n const preprocessors = [fenceCodeBlockPreprocess, mdTableBlockPreprocess];\n for (const preprocessor of preprocessors) {\n [raw, data] = preprocessor(raw, data);\n }\n return [raw, data];\n}\n","import { createLexer, createTokenOfType } from \"@bbob/parser/lexer\";\nimport { TYPE_TAG, TYPE_WORD } from \"@bbob/parser/Token\";\n\n// BBob 4.2 only flushes one unclosed node at EOF and blindly pops a node for\n// any closing tag. Long, historically valid XenForo layouts can therefore\n// disappear entirely. Use its tokenizer extension to close missing children\n// before their ancestor, and finish every remaining wrapper at EOF.\nexport function createLegacyTokenizer(input, options) {\n const pending = [];\n const onToken = options.onToken;\n const close = (name, position) => {\n onToken(createTokenOfType(TYPE_TAG, `/${name}`, 0, 0, position, position));\n };\n\n const lexer = createLexer(input, {\n ...options,\n onToken(token) {\n if (token.isTag() && token.isStart()) {\n const name = token.getValue().toLowerCase();\n if (lexer.isTokenNested(name)) {\n pending.push(name);\n }\n } else if (token.isTag() && token.isEnd()) {\n const name = token.getValue().slice(1).toLowerCase();\n const matching = pending.lastIndexOf(name);\n if (matching === -1) {\n // An extra closing tag is text; it must not close an unrelated node.\n onToken(createTokenOfType(TYPE_WORD, token.toString()));\n return;\n }\n while (pending.length - 1 > matching) {\n close(pending.pop(), token.getStart());\n }\n pending.pop();\n }\n onToken(token);\n },\n });\n\n const tokenize = lexer.tokenize;\n lexer.tokenize = () => {\n const tokens = tokenize();\n while (pending.length) {\n close(pending.pop(), input.length);\n }\n return tokens;\n };\n return lexer;\n}\n","import bbob from \"@bbob/core\";\nimport { render } from \"@bbob/html\";\nimport { lineBreakPlugin } from \"./plugins/lineBreak\";\nimport { preserveWhitespace } from \"./plugins/preserveWhitespace\";\nimport { removeEmptyLinePlugin } from \"./plugins/removeEmptyLinesInAttr\";\nimport { availableTags, preset, preventParsing } from \"./preset\";\nimport { postprocess } from \"./utils/postprocess\";\nimport { preprocessRaw } from \"./utils/preprocess\";\nimport { createLegacyTokenizer } from \"./utils/tokenizer\";\n\nconst options = {\n onlyAllowTags: [...availableTags],\n caseFreeTags: true,\n contextFreeTags: preventParsing, // prevent parsing of children\n enableEscapeTags: true,\n onError: (err) => {\n if (options.previewing) {\n // eslint-disable-next-line no-console\n console.warn(err.message, err.lineNumber, err.columnNumber);\n }\n },\n};\nconst presetTags = preset();\nconst customTagPattern = new RegExp(\n `\\\\[(?:${availableTags.filter((tag) => tag !== \"savenl\").join(\"|\")})(?=[\\\\s=\\\\]])`,\n \"i\"\n);\n\nexport function containsBBCode(code) {\n // Hoist native fenced/inline code before deciding whether this post needs\n // legacy whitespace rules. A BBCode example is still ordinary Markdown.\n // Indented code can only start at the document start or after a blank line;\n // indentation continuing an ordinary paragraph is not a Markdown code block.\n // This is detection only: indenting nested tags in legacy layouts must not\n // turn those layouts into code when actual BBCode exists outside examples.\n const withoutIndentedCode = code\n .replace(/\\r\\n?/g, \"\\n\")\n .replace(/(^|\\n[ \\t]*\\n)(?:(?: {4}| {0,3}\\t)[^\\n]*(?:\\n|$)|[ \\t]*\\n)+/g, \"$1\");\n const [preprocessed] = preprocessRaw(withoutIndentedCode);\n return customTagPattern.test(preprocessed);\n}\n\nexport const RpNBBCode = (code, opts) => {\n const plugins = [presetTags];\n if (opts.preserveWhitespace) {\n plugins.push(preserveWhitespace());\n }\n plugins.push(lineBreakPlugin(), removeEmptyLinePlugin);\n const [preprocessed, preprocessedData] = preprocessRaw(code);\n return bbob(plugins).process(preprocessed, {\n render,\n createTokenizer: createLegacyTokenizer,\n ...options,\n data: {\n ...preprocessedData,\n raw: preprocessed,\n previewing: opts.previewing,\n fonts: new Set(),\n styles: [],\n bbscripts: [],\n },\n });\n};\n\nexport { availableTags, postprocess };\n","let C1 = 'C1';\nlet C2 = 'C2';\nif (process.env.NODE_ENV !== 'production') {\n C1 = '\"parser\" is not a function, please pass to \"process(input, { parser })\" right function';\n C2 = '\"render\" function not defined, please pass to \"process(input, { render })\"';\n}\nexport { C1, C2 };\n"],"names":["N","TAB","EQ","QUOTEMARK","SPACE","OPEN_BRAKET","CLOSE_BRAKET","SLASH","BACKSLASH","isTagNode","el","isStringNode","isEOL","keysReduce","obj","reduce","def","Object","keys","acc","key","getNodeLength","node","Array","isArray","content","count","contentNode","String","length","escapeAttrValue","value","replace","attrValue","name","JSON","stringify","attrsToString","values","arr","join","getTagAttrs","tag","params","uniqAttr","res","tagAttr","attrs","TagNode","attr","this","append","push","appendToNode","setStart","start","setEnd","end","toTagStart","openTag","closeTag","toTagEnd","toTagNode","newNode","toLowerCase","toString","r","renderContent","tagStart","create","isOf","type","constructor","TOKEN_TYPE_ID","getTokenValue","token","isTagEnd","charCodeAt","Token","isEmpty","isNaN","isText","isTag","isAttrName","isAttrValue","isStart","isEnd","getName","slice","getTagName","getValue","getLine","getColumn","getStart","getEnd","text","tokenToText","row","col","CharGrabber","skip","num","silent","c","pos","o","onSkip","hasNext","len","getCurr","s","getPos","getLength","getRest","substring","getNext","nextPos","getPrev","prevPos","isLast","includes","val","indexOf","grabWhile","condition","grabN","substrUntilChar","char","idx","source","options","createCharGrabber","createTokenOfType","cl","p","e","WHITESPACES","SPECIAL_CHARS","isWhiteSpace","isEscapeChar","unq","str","charToRemove","charAt","trimChar","createLexer","buffer","prevCol","tokenIndex","stateMode","tagMode","contextFreeTag","tokens","Math","floor","escapeTags","enableEscapeTags","contextFreeTags","filter","Boolean","map","caseFreeTags","nestedMap","Map","onToken","RESERVED_CHARS","NOT_CHAR_TOKENS","isCharToken","checkContextFreeMode","isClosingTag","chars","emitToken","startPos","endPos","nextTagState","tagChars","isSingleValueTag","masterStartPos","validAttrName","isValue","stateSpecial","validAttrValue","isQM","prevChar","nextChar","isPrevSLASH","isNextEQ","isWS","isNextWS","isSpecialChar","stateTag","currChar","substr","hasInvalidChars","isNoAttrsInTag","stateAttrs","tagStr","tagGrabber","hasSpace","stateWord","fullTagLen","fullTagName","isEscapableChar","isChar","tokenize","isTokenNested","tokenValue","has","get","status","set","NodeList","last","n","flush","pop","toArray","createList","parse","input","opts","onlyAllowTags","tokenizer","nodes","nestedNodes","tagNodes","tagNodesAttrName","nestedTagsMap","Set","isTagNested","tagName","flushTagNodes","getNodes","lastNestedNode","appendNodeAsString","isNested","forEach","item","appendNodes","handleTagStart","tagNode","from","to","add","handleTag","onError","line","column","lineNumber","columnNumber","handleTagEnd","lexer","createTokenizer","activeTagNode","attrName","handleNode","isObj","iterate","t","cb","tree","same","expected","actual","every","exp","some","call","act","ao","eo","createTree","extendedTree","messages","walk","match","expr","expression","renderNode","stripTags","render","toNode","gen","preprocessAttr","raw","nodeRaw","defaultOption","exec","trim","startsWith","endsWith","_default","vals","toOriginalStartTag","toOriginalEndTag","toRawTag","regexIndexOf","string","regex","startpos","search","MD_NEWLINE_INJECT","MD_NEWLINE_PRE_INJECT","MD_NEWLINE_INJECT_COMMENT","URL_REGEX_SINGLE_LINE","RegExp","ESCAPABLES_REGEX","MD_TABLE_REGEX","generateGUID","d","Date","getTime","window","performance","now","random","isString","disableLineBreakConversion","preserveHeadingText","_","indent","hashes","reduceWordsToLines","unshift","child","splice","isWhitespaceSensitive","test","words","rightIdx","findLastIndex","w","i","numSpaces","fromCharCode","repeat","CONSECUTIVE_NEWLINE_REGEX","removeEmptyLinePlugin","process","tags","core","tagCallback","SLIDE_TITLE_OPEN","Symbol","SLIDE_TITLE_CLOSE","SLIDE_CLOSE","SLIDE_REGEX","markerToString","marker","accordionTags","accordion","groupId","markedContent","contentArr","newArr","shift","foundIndex","preContent","postContent","groups","slideTitleOpen","slideTitleClose","slideClose","generateSlideMarkersFromContent","generatedSlides","currentSlide","prevMarker","customTitle","generateSlidesFromMarkers","filteredContent","isValid","data","customSettings","split","lastValidAlignment","align","width","find","classes","style","class","slide","title","isOpen","open","titleAlign","left","right","center","possibleOptions","alignment","anchor","a","id","goto","href","WEB_FONTS","VALID_FONT_STYLES","thin","extralight","light","regular","medium","semibold","bold","extrabold","black","REGISTERED_AXIS","AXES_REGEX","emailHeader","emailFooter","rowcolumn","columnAttrs","columnStyle","EVENTS","TABLE_STYLE","ROW_STYLES","ROW_TAGS","CELL_TAGS","hasUnexpectedContent","allowedTags","option","cell","isBBCodeTableCell","span","Number","colspan","tableTags","table","rows","isBBCodeTableRow","tr","cells","tableCell","td","th","tf","ACCEPTED_OPTIONS","textmessage","messageList","messageNode","recipient","message","animation","previewing","commonGUID","commonId","keyframes","ident","cleanContent","replaceAll","formatted","styles","bg","color","block","defaultOp","blockAttr","blockOption","blockquote","author","border","br","centerblock","percentageInput","check","nameAttr","classSuffix","className","selector","mediaQuery","state","minWidth","maxWidth","code","inputColor","comment","div","classAttrs","classNames","divide","fieldset","fa","font","fontFamily","family","axes","ital","wght","matches","italic","weight","named_weight","fromEntries","entries","axesParser","url","sort","googleFontApiBuild","fonts","custom","fontVar","h","h1","h2","h3","h4","h5","h6","heightrestrict","heightInput","heightValue","parsedHeight","parseHeight","highlight","icode","imagefloat","inlinespoiler","justify","keyframe","list","items","appendItem","trimStart","trimEnd","parts","part","mail","attributes","mailAttr","mailOption","person","subject","newspaper","nobr","note","ooc","pindent","plain","print","printAttr","printOption","progress","percentageInt","quote","thinprogress","savenl","sh","script","onEvent","on","scriptSetup","version","bbscripts","scroll","side","size","fontSize","fontValue","valid","parsedSize","sizeRanges","unit","parseFontSize","outputAttr","spoiler","providedTitle","sub","sup","tab","tabId","checked","for","tabs","tabsList","tabNode","b","u","availableTags","preset","createPreset","defTags","processor","presetFactory","presetExecutor","assign","extend","callback","removeNewlineInjects","cleanMultilineMDBlocks","renderHoistedCodeBlocks","hoistMap","uuid","escaped","createClassStyleTagTemplate","createScriptTagTemplate","fenceCodeBlockPreprocess","index","addHoistAndReturnNewStartPoint","cutOffStart","cutOffEnd","fence","fenceInfo","closingFenceRegex","nextIndex","replacement","bbcode","closingTag","bbcodeTag","backtick","tickStart","tickEnd","mdTableBlockPreprocess","preprocessRaw","preprocessors","preprocessor","createLegacyTokenizer","pending","close","position","matching","lastIndexOf","err","console","warn","presetTags","customTagPattern","plugins","preserveWhitespace","preprocessed","preprocessedData","plugs","mockRender","skipParse","parser","parseFn","renderFn","Error","plugin","newTree","html","bbob","withoutIndentedCode","final","postprocessors","postprocessor"],"mappings":"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","x_google_ignoreList":[0,1,2,3,4,5,6,7,8,9,14,68]} \ No newline at end of file diff --git a/assets/javascripts/discourse/controllers/admin-plugins-bbcode.js b/assets/javascripts/discourse/controllers/admin-plugins-bbcode.js index 87c13e5..9e840ef 100644 --- a/assets/javascripts/discourse/controllers/admin-plugins-bbcode.js +++ b/assets/javascripts/discourse/controllers/admin-plugins-bbcode.js @@ -13,7 +13,7 @@ export default class AdminPluginsBBCodeController extends Controller { this.resetEnabled = false; this.success = false; this.error = false; - ajax("/BbCode/admin/refresh") + ajax("/BbCode/admin/refresh", { type: "POST" }) .then(() => { // success this.resetEnabled = true; diff --git a/assets/javascripts/discourse/initializers/accordion.js b/assets/javascripts/discourse/initializers/accordion.js index 303f0cd..5af2fec 100644 --- a/assets/javascripts/discourse/initializers/accordion.js +++ b/assets/javascripts/discourse/initializers/accordion.js @@ -1,192 +1,44 @@ -/** - * @file Initializes any accordion tag with proper js/event handling - */ import { withPluginApi } from "discourse/lib/plugin-api"; -/** - * Adds the inline js for accordion inside a given post - * @param {HTMLElement} post the post itself - */ -function addAccordionCode(post) { - post.querySelectorAll("div.bb-accordion").forEach((el) => { - // eslint-disable-next-line no-new - new Accordion(el); - }); -} - -/** - * @typedef Slide - * @type {object} - * @property {HTMLDetailsElement} details - * @property {HTMLElement} summary - * @property {HTMLElement} content - * @property {Animation | null} animation - * @property {boolean} isClosing - * @property {boolean} isExpanding - */ - -class Accordion { - /** @type {Element} */ - accordion; - /** @type {Slide[]} */ - slides = []; - - /** - * Constructs the WAAPI powered accordion - * @param {Element} el - */ - constructor(el) { - this.accordion = el; - const details = this.accordion.querySelectorAll("details.bb-slide"); - details.forEach((detail) => { - /** @type {Slide} */ - const slide = { - details: detail, - summary: detail.querySelector("summary.bb-slide-title"), - content: detail.querySelector(".bb-slide-content"), - animation: null, // Store the animation object (so we can cancel it if needed) - isClosing: false, - isExpanding: false, - }; - slide.summary?.addEventListener("click", (ev) => this.onClick(ev, slide)); - this.slides.push(slide); - }); - } +const decoratedSlides = new WeakSet(); - /** - * @param {MouseEvent} ev - * @param {Slide} slide - */ - onClick(ev, slide) { - // Stop default behaviour from the browser - ev.preventDefault(); - // Add an overflow on the
      to avoid content overflowing - slide.details.style.overflow = "hidden"; - // Check if the element is being closed or is already closed - if (slide.isClosing || !slide.details.open) { - this.open(slide); - // Check if the element is being openned or is already open - } else if (slide.isExpanding || slide.details.open) { - this.shrink(slide); +// Native details elements handle focus, keyboard input and disclosure state. +// Only the accordion's exclusive-open behavior needs a decorator. +export function addAccordionCode(post) { + post.querySelectorAll("details.bb-slide").forEach((slide) => { + if (decoratedSlides.has(slide)) { + return; } - } - /** - * @param {Slide} slide - */ - shrink(slide) { - // Set the element as "being closed" - slide.isClosing = true; - slide.details.style.overflow = "hidden"; - - // Store the current height of the element - const startHeight = `${slide.details.offsetHeight}px`; - // Calculate the height of the summary - const endHeight = `${slide.summary.offsetHeight}px`; - - // If there is already an animation running - if (slide.animation) { - // Cancel the current animation - slide.animation.cancel(); + const accordion = slide.closest(".bb-accordion"); + if (!accordion) { + return; } - // Start a WAAPI animation - slide.animation = slide.details.animate( - { - // Set the keyframes from the startHeight to endHeight - height: [startHeight, endHeight], - }, - { - duration: 400, - easing: "linear", + decoratedSlides.add(slide); + slide.addEventListener("toggle", () => { + if (!slide.open) { + return; } - ); - - // When the animation is complete, call onAnimationFinish() - slide.animation.onfinish = () => this.onAnimationFinish(false, slide); - // If the animation is cancelled, isClosing variable is set to false - slide.animation.oncancel = () => (slide.isClosing = false); - } - - /** - * @param {Slide} slide - */ - open(slide) { - // Apply a fixed height on the element - slide.details.style.height = `${slide.details.offsetHeight}px`; - // Force the [open] attribute on the details element - slide.details.open = true; - // Wait for the next frame to call the expand function - const otherSlides = this.slides.filter((x) => x !== slide); - for (const otherSlide of otherSlides) { - this.shrink(otherSlide); - } - window.requestAnimationFrame(() => this.expand(slide)); - } - - /** - * @param {Slide} slide - */ - expand(slide) { - // Set the element as "being expanding" - slide.isExpanding = true; - // Get the current fixed height of the element - const startHeight = `${slide.details.offsetHeight}px`; - // Calculate the open height of the element (summary height + content height) - const endHeight = `${slide.summary.offsetHeight + slide.content.offsetHeight}px`; - // If there is already an animation running - if (slide.animation) { - // Cancel the current animation - slide.animation.cancel(); - } - - // Start a WAAPI animation - slide.animation = slide.details.animate( - { - // Set the keyframes from the startHeight to endHeight - height: [startHeight, endHeight], - }, - { - duration: 400, - easing: "linear", - } - ); - // When the animation is complete, call onAnimationFinish() - slide.animation.onfinish = () => this.onAnimationFinish(true, slide); - // If the animation is cancelled, isExpanding variable is set to false - slide.animation.oncancel = () => (slide.isExpanding = false); - } - - /** - * @param {boolean} open - * @param {Slide} slide - */ - onAnimationFinish(open, slide) { - // Set the open attribute based on the parameter - slide.details.open = open; - // Clear the stored animation - slide.animation = null; - // Reset isClosing & isExpanding - slide.isClosing = false; - slide.isExpanding = false; - // Remove the overflow hidden and the fixed height - slide.details.style.height = slide.details.style.overflow = ""; - } -} - -/** - * The initial called function. - * Any calls to the PluginAPI should be done in here - * @param api - */ -function initializeAccordion(api) { - api.decorateCookedElement(addAccordionCode, { id: "add accordions" }); + accordion.querySelectorAll("details.bb-slide[open]").forEach((other) => { + if (other !== slide && other.closest(".bb-accordion") === accordion) { + other.open = false; + } + }); + }); + }); } export default { - name: "accordion", + name: "bbcode-accordion", initialize() { - withPluginApi("0.11.1", initializeAccordion); + withPluginApi((api) => { + if (!api.container.lookup("service:site-settings").bbcode_enabled) { + return; + } + + api.decorateCookedElement(addAccordionCode); + }); }, }; diff --git a/assets/javascripts/discourse/initializers/bbcode-parser-init.js b/assets/javascripts/discourse/initializers/bbcode-parser-init.js deleted file mode 100644 index f9a67ff..0000000 --- a/assets/javascripts/discourse/initializers/bbcode-parser-init.js +++ /dev/null @@ -1,15 +0,0 @@ -import loadscript from "discourse/lib/load-script"; - -export default { - name: "bbcode-parser-init", - initialize() { - if (window.bbcodeParser) { - return; - } else { - loadscript("/assets/bundled/bbcode-parser.min.js").then(() => { - // eslint-disable-next-line no-console - console.info("BBCode parser loaded for preview."); - }); - } - }, -}; diff --git a/assets/javascripts/discourse/initializers/bbscript.js b/assets/javascripts/discourse/initializers/bbscript.js index eb26f35..1dcd6ff 100644 --- a/assets/javascripts/discourse/initializers/bbscript.js +++ b/assets/javascripts/discourse/initializers/bbscript.js @@ -1,112 +1,108 @@ -import { debounce } from "@ember/runloop"; import loadscript from "discourse/lib/load-script"; import { withPluginApi } from "discourse/lib/plugin-api"; /* global bbscriptParser */ -/** - * @type {Array<{callback: function, on: string}>} - */ -let attachedPreviewBBScripts = []; -/** @type {WeakMap} */ -const initBBScripts = new WeakMap(); - -const PARENT_PREVIEW_WRAPPER_CLASS = "d-editor-preview"; +const decoratedPosts = new WeakMap(); +let parserPromise; -const documentObserver = new IntersectionObserver( - (entries, observer) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - const post = entry.target; - const callback = initBBScripts.get(post); - callback?.forEach((c) => c()); - initBBScripts.delete(post); - observer.unobserve(post); - } - }); - }, - { - threshold: 0, - rootMargin: "10px 0px 0px 0px", +function decorateBBScript(post) { + decoratedPosts.get(post)?.(); + const templates = [...post.querySelectorAll("template[data-bbcode-plus='script']")]; + if (!templates.length) { + return; } -); -/** - * Check if the post is a preview. If it is a preview, debounce the function - * @param {HTMLElement} post - */ -function checkIsPreview(post) { - let isPreview = post.classList.contains(PARENT_PREVIEW_WRAPPER_CLASS); - if (isPreview) { - // prevent multiple calls to addBBScriptLogic - // preview mode is constantly updating based on user input - debounce(this, addBBScriptLogic, post, true, 1000); - } else { - addBBScriptLogic(post, false); - } -} + let disposed = false; + let observer; + const events = new AbortController(); + const cleanup = () => { + disposed = true; + observer?.disconnect(); + events.abort(); + if (decoratedPosts.get(post) === cleanup) { + decoratedPosts.delete(post); + } + }; + decoratedPosts.set(post, cleanup); -/** - * Adds the bbscript functions to the post - * @param {HTMLElement} post the post itself - */ -function addBBScriptLogic(post, isPreview = false) { - if (isPreview) { - attachedPreviewBBScripts.forEach(({ callback, on }) => { - post.removeEventListener(on, callback, true); - }); - attachedPreviewBBScripts = []; - delete bbscriptParser.bbscriptData.preview; - } + parserPromise ||= loadscript("/plugins/bbcode/javascripts/bbscript-parser.min.js"); + parserPromise + .then(() => { + if (disposed) { + return; + } - post.querySelectorAll("template[data-bbcode-plus='script']").forEach((el) => { - const callerId = el.getAttribute("data-bbscript-id") || ""; - const callerClass = el.getAttribute("data-bbscript-class") || ""; - /** @type {string} */ - const content = el.content.textContent || ""; - let version = el.getAttribute("data-bbscript-ver") || ""; - const on = el.getAttribute("data-bbscript-on") || "init"; // valid on events: init, click, mouseover, mouseout, etc. - let astTree; - if (version === "") { - // unknown version. check for unique () style of bbscript2 - version = content.split("\n").some((line) => line.trim().startsWith("(")) ? "2" : "1"; - } - if (version === "2") { - const parsed = bbscriptParser.bbscript2Parser.parse(content); - astTree = parsed.ast; - if (parsed.formattedErrors.length) { - // eslint-disable-next-line no-console - console.warn(parsed.formattedErrors); + const isPreview = !!post.closest(".d-editor-preview"); + if (isPreview) { + delete bbscriptParser.bbscriptData.preview; } - } else { - astTree = bbscriptParser.bbscriptProcessorV1.parse(content); - } - if (on === "init") { - let target; - if (callerClass) { - target = document.querySelectorAll("." + callerClass + "__" + callerId) || undefined; + const initializers = []; + for (const el of templates) { + const callerId = el.getAttribute("data-bbscript-id") || ""; + const callerClass = el.getAttribute("data-bbscript-class") || ""; + const content = el.content.textContent || ""; + const on = el.getAttribute("data-bbscript-on") || "init"; + let version = el.getAttribute("data-bbscript-ver") || ""; + if (!version) { + version = content.split("\n").some((line) => line.trim().startsWith("(")) ? "2" : "1"; + } + let astTree; + if (version === "2") { + const parsed = bbscriptParser.bbscript2Parser.parse(content); + astTree = parsed.ast; + if (parsed.formattedErrors.length) { + // eslint-disable-next-line no-console + console.warn(parsed.formattedErrors); + } + } else { + astTree = bbscriptParser.bbscriptProcessorV1.parse(content); + } + + const selector = callerClass && `.${CSS.escape(callerClass + "__" + callerId)}`; + if (on === "init") { + initializers.push(() => { + const target = selector ? post.querySelectorAll(selector) : undefined; + triggerBBScript(callerId, callerClass, astTree, version, target); + }); + } else if (selector) { + post.addEventListener( + on, + (event) => { + const target = event.target?.closest?.(selector); + if (target && post.contains(target)) { + triggerBBScript(callerId, callerClass, astTree, version, target); + } + }, + { capture: true, signal: events.signal } + ); + } } - // only fire when the post is visible - if (!initBBScripts.has(post)) { - initBBScripts.set(post, []); + + if (!initializers.length) { + return; } - initBBScripts.get(post).push(() => { - triggerBBScript(callerId, callerClass, astTree, version, target); - }); - documentObserver.observe(post); - } else { - const callback = (ev) => { - const target = ev.target?.closest("." + callerClass + "__" + callerId); - if (target) { - triggerBBScript(callerId, callerClass, astTree, version, target); - } - }; - // event delegation - post.addEventListener(on, callback, true); if (isPreview) { - attachedPreviewBBScripts.push({ callback, on }); + initializers.forEach((initialize) => initialize()); + } else { + observer = new IntersectionObserver( + (entries) => { + if (entries.some((entry) => entry.isIntersecting)) { + observer.disconnect(); + initializers.forEach((initialize) => initialize()); + } + }, + { rootMargin: "10px 0px 0px 0px" } + ); + observer.observe(post); } - } - }); + }) + .catch((error) => { + cleanup(); + // eslint-disable-next-line no-console + console.warn("Could not initialize BBScript", error); + }); + + return cleanup; } /** @@ -134,26 +130,14 @@ const triggerBBScript = (callerId, callerClass, astTree, version, target) => { function initializeBBScript(api) { const siteSettings = api.container.lookup("service:site-settings"); - if (!siteSettings.enable_bbscript) { - return; + if (siteSettings.bbcode_enabled && siteSettings.enable_bbscript) { + api.decorateCookedElement(decorateBBScript); } - - api.decorateCookedElement( - (post) => { - loadscript("/plugins/bbcode/javascripts/bbscript-parser.min.js").then(() => { - checkIsPreview(post); - }); - }, - { - id: "add bbscript", - afterAdopt: true, - } - ); } export default { name: "bbscript", initialize() { - withPluginApi("0.11.1", initializeBBScript); + withPluginApi(initializeBBScript); }, }; diff --git a/assets/javascripts/discourse/initializers/class.js b/assets/javascripts/discourse/initializers/class.js index 783a79a..d93c50f 100644 --- a/assets/javascripts/discourse/initializers/class.js +++ b/assets/javascripts/discourse/initializers/class.js @@ -2,6 +2,7 @@ * @file Initializes and adds any custom user styles to the post */ import { withPluginApi } from "discourse/lib/plugin-api"; +import { decorateBBCodeScrollers } from "../lib/bbcode-scroll-containers"; /** * Adds the inline styles for the style tag inside a given post @@ -10,7 +11,7 @@ import { withPluginApi } from "discourse/lib/plugin-api"; function addClassStyleCode(post) { // cleans up existing styles post.querySelectorAll("style[data-rendered-class]").forEach((el) => { - post.removeChild(el); + el.remove(); }); post.querySelectorAll("template[data-bbcode-plus='class']").forEach((el) => { @@ -28,9 +29,15 @@ function addClassStyleCode(post) { } post.prepend(style); }); + + return decorateBBCodeScrollers(post); } function initializeClassStyle(api) { + if (!api.container.lookup("service:site-settings").bbcode_enabled) { + return; + } + api.decorateCookedElement(addClassStyleCode, { id: "add class style code" }); } diff --git a/assets/javascripts/discourse/initializers/fontawesome.js b/assets/javascripts/discourse/initializers/fontawesome.js index 422059b..731c585 100644 --- a/assets/javascripts/discourse/initializers/fontawesome.js +++ b/assets/javascripts/discourse/initializers/fontawesome.js @@ -5,7 +5,7 @@ const postsMissingFontAwesome = []; function initializeFontAwesome(api) { const siteSettings = api.container.lookup("service:site-settings"); - if (!siteSettings.fontawesome_kit_url) { + if (!siteSettings.bbcode_enabled || !siteSettings.fontawesome_kit_url) { return; } window.FontAwesomeConfig = { diff --git a/assets/javascripts/discourse/initializers/google-font.js b/assets/javascripts/discourse/initializers/google-font.js index 24203b7..538f321 100644 --- a/assets/javascripts/discourse/initializers/google-font.js +++ b/assets/javascripts/discourse/initializers/google-font.js @@ -10,7 +10,7 @@ import { withPluginApi } from "discourse/lib/plugin-api"; function addGoogleFont(post) { // Cleans up post if we're editing const priorLinks = post.querySelectorAll("link[data-rendered-gfont]"); - priorLinks.forEach((oldLink) => post.removeChild(oldLink)); + priorLinks.forEach((oldLink) => oldLink.remove()); const elements = post.querySelectorAll("[data-font]"); if (!elements.length) { @@ -20,7 +20,7 @@ function addGoogleFont(post) { const gFonts = []; Array.from(elements).map((e) => { const data = e.getAttribute("data-font"); - if (!gFonts.includes(data) && data.startsWith("https://fonts.googleapis.com")) { + if (!gFonts.includes(data) && data.startsWith("https://fonts.googleapis.com/css2?")) { frag.appendChild(linkBuilder(data)); gFonts.push(data); } @@ -48,6 +48,10 @@ function linkBuilder(data) { * @param api */ function initializeFont(api) { + if (!api.container.lookup("service:site-settings").bbcode_enabled) { + return; + } + api.decorateCookedElement((elem) => addGoogleFont(elem), { id: "add google font", }); diff --git a/assets/javascripts/discourse/initializers/highlight.js b/assets/javascripts/discourse/initializers/highlight.js index a3c4285..c22c986 100644 --- a/assets/javascripts/discourse/initializers/highlight.js +++ b/assets/javascripts/discourse/initializers/highlight.js @@ -340,6 +340,10 @@ function markdownHighlight(hljs) { * @param api */ function registerHighlightJs(api) { + if (!api.container.lookup("service:site-settings").bbcode_enabled) { + return; + } + api.registerHighlightJSLanguage("bbcode", bbcodeHighlight); api.registerHighlightJSLanguage("markdown-bbcode", markdownHighlight); } diff --git a/assets/javascripts/discourse/initializers/inlinespoiler.js b/assets/javascripts/discourse/initializers/inlinespoiler.js index ed45a07..18afdea 100644 --- a/assets/javascripts/discourse/initializers/inlinespoiler.js +++ b/assets/javascripts/discourse/initializers/inlinespoiler.js @@ -1,41 +1,61 @@ -/** - * @file Initializes any inline spoiler tag with proper js/event handling - */ import { withPluginApi } from "discourse/lib/plugin-api"; -/** - * Adds the inline js for inline spoilers inside a given post - * @param {HTMLElement} post the post itself - */ -function addInlineSpoilerCode(post) { - post.querySelectorAll(".bb-inline-spoiler").forEach((el) => { - el.addEventListener("click", toggleInlineSpoiler); - }); -} +const decoratedSpoilers = new WeakSet(); function toggleInlineSpoiler(event) { - const inlinespoiler = event.target; - if (inlinespoiler.attributes.getNamedItem("data-displayed") === null) { - inlinespoiler.setAttribute("data-displayed", true); + if (event.type === "keydown" && !["Enter", " "].includes(event.key)) { + return; + } + + const spoiler = event.currentTarget; + if ( + event.target !== spoiler && + spoiler.getAttribute("data-displayed") === "true" && + event.target.closest("a, button, input, select, textarea") + ) { + // Let revealed links and controls reach Discourse's delegated handlers. + // Enclosing revealed spoilers also take this branch without toggling. + return; + } + + event.preventDefault(); + event.stopPropagation(); + const displayed = spoiler.getAttribute("data-displayed") !== "true"; + if (displayed) { + spoiler.setAttribute("data-displayed", "true"); } else { - inlinespoiler.removeAttribute("data-displayed"); + spoiler.removeAttribute("data-displayed"); } + spoiler.setAttribute("aria-expanded", String(displayed)); } -/** - * The initial called function. - * Any calls to the PluginAPI should be done in here - * @param api - */ -function initializeSpoiler(api) { - api.decorateCookedElement(addInlineSpoilerCode, { - id: "add inline spoilers", +export function addInlineSpoilerCode(post) { + post.querySelectorAll(".bb-inline-spoiler").forEach((spoiler) => { + if (decoratedSpoilers.has(spoiler)) { + return; + } + + decoratedSpoilers.add(spoiler); + spoiler.setAttribute("tabindex", "0"); + spoiler.setAttribute("role", "button"); + spoiler.setAttribute( + "aria-expanded", + String(spoiler.getAttribute("data-displayed") === "true") + ); + spoiler.addEventListener("click", toggleInlineSpoiler); + spoiler.addEventListener("keydown", toggleInlineSpoiler); }); } export default { - name: "inlinespoiler", + name: "bbcode-inline-spoiler", initialize() { - withPluginApi("0.11.1", initializeSpoiler); + withPluginApi((api) => { + if (!api.container.lookup("service:site-settings").bbcode_enabled) { + return; + } + + api.decorateCookedElement(addInlineSpoilerCode); + }); }, }; diff --git a/assets/javascripts/discourse/initializers/spoiler.js b/assets/javascripts/discourse/initializers/spoiler.js deleted file mode 100644 index 4989e67..0000000 --- a/assets/javascripts/discourse/initializers/spoiler.js +++ /dev/null @@ -1,150 +0,0 @@ -/** - * @file Initializes any spoiler tag with proper js/event handling - */ -import { withPluginApi } from "discourse/lib/plugin-api"; - -/** - * Adds the inline js for spoilers inside a given post - * @param {HTMLElement} post the post itself - */ -function addSpoilerCode(post) { - post.querySelectorAll("details.bb-spoiler").forEach((el) => { - // eslint-disable-next-line no-new - new Spoiler(el); - }); -} - -class Spoiler { - constructor(el) { - // Store the
      element - this.el = el; - // Store the element - this.summary = el.querySelector("summary"); - // Store the
      element - this.content = el.querySelector(".bb-spoiler-content"); - - // Store the animation object (so we can cancel it if needed) - this.animation = null; - // Store if the element is closing - this.isClosing = false; - // Store if the element is expanding - this.isExpanding = false; - // Detect user clicks on the summary element - this.summary.addEventListener("click", (e) => this.onClick(e)); - } - - onClick(e) { - // Stop default behaviour from the browser - e.preventDefault(); - // Add an overflow on the
      to avoid content overflowing - this.el.style.overflow = "hidden"; - // Check if the element is being closed or is already closed - if (this.isClosing || !this.el.open) { - this.open(); - // Check if the element is being openned or is already open - } else if (this.isExpanding || this.el.open) { - this.shrink(); - } - } - - shrink() { - // Set the element as "being closed" - this.isClosing = true; - - // Store the current height of the element - const startHeight = `${this.el.offsetHeight}px`; - // Calculate the height of the summary - const endHeight = `${this.summary.offsetHeight}px`; - - // If there is already an animation running - if (this.animation) { - // Cancel the current animation - this.animation.cancel(); - } - - // Start a WAAPI animation - this.animation = this.el.animate( - { - // Set the keyframes from the startHeight to endHeight - height: [startHeight, endHeight], - }, - { - duration: 200, - easing: "ease-in-out", - } - ); - - // When the animation is complete, call onAnimationFinish() - this.animation.onfinish = () => this.onAnimationFinish(false); - // If the animation is cancelled, isClosing variable is set to false - this.animation.oncancel = () => (this.isClosing = false); - } - - open() { - // Apply a fixed height on the element - this.el.style.height = `${this.el.offsetHeight}px`; - // Force the [open] attribute on the details element - this.el.open = true; - // Wait for the next frame to call the expand function - window.requestAnimationFrame(() => this.expand()); - } - - expand() { - // Set the element as "being expanding" - this.isExpanding = true; - // Get the current fixed height of the element - const startHeight = `${this.el.offsetHeight}px`; - // Calculate the open height of the element (summary height + content height) - const endHeight = `${this.summary.offsetHeight + this.content.offsetHeight}px`; - - // If there is already an animation running - if (this.animation) { - // Cancel the current animation - this.animation.cancel(); - } - - // Start a WAAPI animation - this.animation = this.el.animate( - { - // Set the keyframes from the startHeight to endHeight - height: [startHeight, endHeight], - }, - { - duration: 200, - easing: "ease-in-out", - } - ); - // When the animation is complete, call onAnimationFinish() - this.animation.onfinish = () => this.onAnimationFinish(true); - // If the animation is cancelled, isExpanding variable is set to false - this.animation.oncancel = () => (this.isExpanding = false); - } - - onAnimationFinish(open) { - // Set the open attribute based on the parameter - this.el.open = open; - // Clear the stored animation - this.animation = null; - // Reset isClosing & isExpanding - this.isClosing = false; - this.isExpanding = false; - // Remove the overflow hidden and the fixed height - this.el.style.height = this.el.style.overflow = ""; - } -} - -/** - * The initial called function. - * Any calls to the PluginAPI should be done in here - * @param api - */ -function initializeSpoiler(api) { - api.decorateCookedElement(addSpoilerCode, { id: "add spoilers" }); -} - -export default { - name: "spoiler", - initialize() { - withPluginApi("0.11.1", initializeSpoiler); - }, -}; diff --git a/assets/javascripts/discourse/lib/bbcode-scroll-containers.js b/assets/javascripts/discourse/lib/bbcode-scroll-containers.js new file mode 100644 index 0000000..8778035 --- /dev/null +++ b/assets/javascripts/discourse/lib/bbcode-scroll-containers.js @@ -0,0 +1,67 @@ +import { cancel, schedule } from "@ember/runloop"; + +const positionedScrollers = new WeakMap(); +const pendingDecorations = new WeakMap(); +const scrollableOverflow = new Set(["auto", "scroll"]); + +export function decorateBBCodeScrollers(post) { + pendingDecorations.get(post)?.(); + if (post.isConnected) { + containBBCodeScrollers(post); + return; + } + + // Discourse decorates an inert document before adopting its content into + // the page. Wait for insertion before reading author CSS or layout styles. + let timer; + const cleanup = () => { + cancel(timer); + if (pendingDecorations.get(post) === cleanup) { + pendingDecorations.delete(post); + } + }; + pendingDecorations.set(post, cleanup); + timer = schedule("afterRender", () => { + pendingDecorations.delete(post); + containBBCodeScrollers(post); + }); + return cleanup; +} + +// Absolutely positioned artwork must belong to its author's scroll container, +// otherwise it can escape that container and widen the entire topic page. +export function containBBCodeScrollers(post) { + const view = post.ownerDocument.defaultView; + if (!post.isConnected || !view) { + return; + } + + post.querySelectorAll('div[data-bbcode-div="true"]').forEach((element) => { + const previous = positionedScrollers.get(element); + if ( + previous && + element.style.position === "relative" && + !element.style.getPropertyPriority("position") + ) { + // Re-evaluate after class styles change during preview decoration. + // Restore only our own override, never a new author-supplied position. + if (previous.value) { + element.style.setProperty("position", previous.value, previous.priority); + } else { + element.style.removeProperty("position"); + } + } + positionedScrollers.delete(element); + + const computed = view.getComputedStyle(element); + if (computed.position !== "static" || !scrollableOverflow.has(computed.overflowX)) { + return; + } + + positionedScrollers.set(element, { + value: element.style.getPropertyValue("position"), + priority: element.style.getPropertyPriority("position"), + }); + element.style.setProperty("position", "relative"); + }); +} diff --git a/assets/javascripts/discourse/templates/admin/plugins-bbcode.gjs b/assets/javascripts/discourse/templates/admin/plugins-bbcode.gjs new file mode 100644 index 0000000..8927ea2 --- /dev/null +++ b/assets/javascripts/discourse/templates/admin/plugins-bbcode.gjs @@ -0,0 +1,27 @@ +import DButton from "discourse/ui-kit/d-button"; +import { i18n } from "discourse-i18n"; + + diff --git a/assets/javascripts/discourse/templates/admin/plugins-bbcode.hbs b/assets/javascripts/discourse/templates/admin/plugins-bbcode.hbs deleted file mode 100644 index 792911f..0000000 --- a/assets/javascripts/discourse/templates/admin/plugins-bbcode.hbs +++ /dev/null @@ -1,22 +0,0 @@ -

      {{i18n "bbcode.title"}}

      -

      {{i18n "bbcode.resetDesc"}}

      - -
      - {{#if this.resetEnabled}} - - {{#if this.success}} - {{i18n "bbcode.success"}} - {{/if}} - {{#if this.error}} - {{i18n "bbcode.error"}} - {{/if}} - {{else}} -
      - {{i18n "bbcode.reseting"}} - {{/if}} -
      \ No newline at end of file diff --git a/assets/javascripts/lib/discourse-markdown/bbcode-plugin.js b/assets/javascripts/lib/discourse-markdown/bbcode-plugin.js index 17ba3d0..5969d2d 100644 --- a/assets/javascripts/lib/discourse-markdown/bbcode-plugin.js +++ b/assets/javascripts/lib/discourse-markdown/bbcode-plugin.js @@ -1,116 +1,72 @@ -/** - * Processes inputted BBCode string using custom configured 3rd party library (see /bbcode-src) - * @param {string} raw content to preprocess into HTML - * @returns processed HTML string to pass into markdown-it - */ -function preprocessor(raw, opts, previewing = false) { - // eslint-disable-next-line no-undef - if (!bbcodeParser) { - // parser doesn't exist. Something horrible has happened and somehow the parser wasn't imported/initialized - // give up and send it straight back. - // eslint-disable-next-line no-console - console.warn( - "Attempted to get the bbcode parser: does not exist. Defaulting to standard markdown-it.", - "\ncalled on: \n", - raw - ); - return [raw, {}]; - } - const parser = globalThis.bbcodeParser.RpNBBCode; - opts.previewing = previewing; - - const processed = parser(raw, opts); - return [processed.html, processed.tree.options.data]; -} - -/** - * Processes the output of both the markdown-it and the bbcode parser, concatenating additional content if necessary - * @param {string} raw processed string - * @param {boolean} previewing flag - * @param {any} data from preprocessor - * @returns processed string - */ -function postprocessor(raw, previewing = false, data = {}) { - // eslint-disable-next-line no-undef - if (!bbcodeParser) { - // parser doesn't exist. Something horrible has happened and somehow the parser wasn't imported/initialized - // give up and send it straight back. - // eslint-disable-next-line no-console - console.warn( - "Attempted to get the bbcode parser: does not exist. Defaulting to standard markdown-it.", - "\ncalled on: \n", - raw - ); - return raw; - } - // preview auto clear doesn't check against the live dom, so if a onebox is at the end of the post, - // it won't be cleared and could cause a fatal error - const append = previewing ? '
      ' : ""; - return globalThis.bbcodeParser.postprocess(raw, data) + append; -} - +// Keep the BBCode parser inside Discourse's registered Markdown pipeline. +// Its output still passes through the normal sanitizer on both server and client. export function setup(helper) { - if (!helper.markdownIt) { - return; - } + helper.registerOptions((options, siteSettings) => { + options.features["bbcode-plugin"] = !!siteSettings.bbcode_enabled; + options.bbcodePreserveWhitespace = + siteSettings.preserve_whitespace && !siteSettings.discourse_normalize_whitespace; + }); - helper.registerOptions((opts, siteSettings) => { - opts.features["bbcode-parser"] = siteSettings.bbcode_enabled; - if (opts.engine || !siteSettings.bbcode_enabled) { - return; + helper.registerPlugin((md) => { + const parser = globalThis.bbcodeParser; + if (!parser) { + throw new Error("The registered BBCode parser asset is missing"); } - //Add check site settings for options to send to RpNBBCode - let preprocessor_options = { - preserveWhitespace: - siteSettings.preserve_whitespace && !siteSettings.discourse_normalize_whitespace, - }; - Object.defineProperty(opts, "engine", { - configurable: true, - set(engine) { - const md = engine.render; - engine.set({ breaks: false }); // disable breaks. Let BBob handle line breaks. + const parsedTokens = new WeakMap(); + + md.core.ruler.before("normalize", "rpn-bbcode", (state) => { + // Plain Markdown keeps native paragraphs, whitespace and code blocks. + // Restricted cooks (including chat) do not enable this feature. + state.env.rpnBBCode = false; + if (!parser.containsBBCode(state.src)) { + return; + } - engine.render = function (raw) { - if (engine.options?.discourse?.featuresOverride !== undefined) { - // if featuresOverride is set, we're in a chat message and should not preprocess - return md.apply(this, [raw]); - } - const [preprocessed, data] = preprocessor( - raw, - preprocessor_options, - engine.options?.discourse?.previewing - ); - const processed = md.apply(this, [preprocessed]); - const postprocessed = postprocessor( - processed, - engine.options?.discourse?.previewing, - data - ); - return postprocessed; - }; - Object.defineProperty(opts, "engine", { - configurable: true, - enumerable: true, - writable: true, - value: engine, - }); - }, + const processed = parser.RpNBBCode(state.src, { + preserveWhitespace: md.options.discourse.bbcodePreserveWhitespace, + previewing: md.options.discourse.previewing, + }); + state.src = processed.html; + state.env.rpnBBCode = true; + parsedTokens.set(state.tokens, processed.tree.options.data); }); - }); - helper.registerPlugin((md) => { - // disable paragraph rendering - md.renderer.rules.paragraph_open = function () { - return ""; - }; - md.renderer.rules.paragraph_close = function () { - return ""; - }; + // Legacy layouts indent nested tags for readability. After conversion the + // generated HTML must not become an indented Markdown code block. Capture + // the native rule through Ruler's public API while registering the plugin, + // then keep it enabled for every ordinary Markdown cook. + const blockRules = md.block.ruler.getRules(""); + md.block.ruler.disable("code", true); + const withoutCode = new Set(md.block.ruler.getRules("")); + const nativeCode = blockRules.find((rule) => !withoutCode.has(rule)); + if (nativeCode) { + md.block.ruler.at("code", (state, ...args) => { + return !state.env.rpnBBCode && nativeCode(state, ...args); + }); + md.block.ruler.enable("code"); + } + + for (const name of ["paragraph_open", "paragraph_close", "softbreak"]) { + const renderRule = md.renderer.rules[name]; + md.renderer.rules[name] = (tokens, index, options, env, renderer) => { + if (env.rpnBBCode) { + return name === "softbreak" ? "\n" : ""; + } + return renderRule + ? renderRule(tokens, index, options, env, renderer) + : renderer.renderToken(tokens, index, options); + }; + } - // this rule is where an indent (space/indent) is converted to a code block - // rarely used in the wild, but it's a common source of confusion - md.disable("code"); + // A renderer extension preserves the parser's hoisted code, styles and + // scripts without intercepting Discourse's engine initialization. + const render = md.renderer.render; + md.renderer.render = function (tokens, options, env) { + const html = render.call(this, tokens, options, env); + const data = parsedTokens.get(tokens); + return data ? parser.postprocess(html, data) : html; + }; }); helper.allowList([ @@ -138,6 +94,7 @@ export function setup(helper) { "div.bb-email-subject", "div.bb-float-left", "div.bb-float-right", + "div.bb-fieldset", "div.bb-height-restrict", "div.bb-img", "div.bb-justify", @@ -154,6 +111,7 @@ export function setup(helper) { "div.bb-progress", "div.bb-progress-bar", "div.bb-progress-other", + "div.bb-progress-bar-other", "div.bb-progress-text", "div.bb-progress-thin", "div.bb-ooc", @@ -164,6 +122,8 @@ export function setup(helper) { "div.bb-slide-content", "div.bb-spoiler-content", "div.bb-tabs", + "table.bb-table", + "td.bb-table-footer", "input.bb-tab", "label.bb-tab-label", "div.bb-tab-content", @@ -174,6 +134,7 @@ export function setup(helper) { "div.bb-message-content", "div.bb-message-them", "div.bb-message-me", + "div[data-bbcode-div=true]", "div[style=*]", "fieldset.bb-fieldset", "legend.bb-fieldset-legend", @@ -185,6 +146,10 @@ export function setup(helper) { "span.bb-highlight", "span.bb-inline-spoiler", "span.bb-pindent", + "span.bbcode-b", + "span.bbcode-i", + "span.bbcode-u", + "span.bbcode-s", "span.hidden", "span[style=*]", "summary", @@ -204,6 +169,24 @@ export function setup(helper) { return true; } + if (tag === "div" && name === "data-span") { + return /^column-width-span\d+$/.test(value); + } + + if (tag === "table" && name === "data-bb-table-style") { + return /^(?:(?:none|dotted|dark)(?:-zebra2?)?|zebra2?)$/.test(value); + } + if (tag === "tr" && name === "data-bb-table-row") { + return ["blue", "gray"].includes(value); + } + if (["td", "th"].includes(tag) && name === "colspan") { + return /^\d{1,4}$/.test(value) && Number(value) > 0 && Number(value) <= 1000; + } + + if (tag === "span" && name === "data-font") { + return value.startsWith("https://fonts.googleapis.com/css2?"); + } + // custom attr allowlist for tabs if (tag === "input" && name === "type" && value === "radio") { return true; diff --git a/assets/stylesheets/common/index.scss b/assets/stylesheets/common/index.scss index cc73ae1..6dca733 100644 --- a/assets/stylesheets/common/index.scss +++ b/assets/stylesheets/common/index.scss @@ -1,31 +1,32 @@ -@import "accordion.scss"; -@import "alignment.scss"; -@import "block.scss"; -@import "blockquote.scss"; -@import "border-bg.scss"; -@import "check.scss"; -@import "divide.scss"; -@import "fieldset.scss"; -@import "heightrestrict.scss"; -@import "highlight.scss"; -@import "img.scss"; -@import "imagefloat.scss"; -@import "justify.scss"; -@import "mail.scss"; -@import "markdown-shims.scss"; -@import "newspaper.scss"; -@import "note.scss"; -@import "ooc.scss"; -@import "pindent.scss"; -@import "print.scss"; -@import "progress.scss"; -@import "rowcolumn.scss"; -@import "scroll.scss"; -@import "side.scss"; -@import "size.scss"; -@import "spoiler.scss"; -@import "tabs.scss"; -@import "textmessage.scss"; +@import "accordion"; +@import "alignment"; +@import "block"; +@import "blockquote"; +@import "border-bg"; +@import "check"; +@import "divide"; +@import "fieldset"; +@import "heightrestrict"; +@import "highlight"; +@import "img"; +@import "imagefloat"; +@import "justify"; +@import "mail"; +@import "markdown-shims"; +@import "newspaper"; +@import "note"; +@import "ooc"; +@import "pindent"; +@import "print"; +@import "progress"; +@import "rowcolumn"; +@import "scroll"; +@import "side"; +@import "size"; +@import "spoiler"; +@import "tabs"; +@import "table"; +@import "textmessage"; .d-editor-preview { position: relative; diff --git a/assets/stylesheets/common/rowcolumn.scss b/assets/stylesheets/common/rowcolumn.scss index 373c503..9a11e3b 100644 --- a/assets/stylesheets/common/rowcolumn.scss +++ b/assets/stylesheets/common/rowcolumn.scss @@ -14,46 +14,44 @@ margin: 0 0.5%; padding: 0 0.5%; float: left; + &[data-span="column-width-span1"] { width: 10.5%; } + &[data-span="column-width-span2"] { width: 23%; } + &[data-span="column-width-span3"] { width: 35.5%; } + &[data-span="column-width-span4"] { width: 48%; } + &[data-span="column-width-span5"] { width: 60.5%; } + &[data-span="column-width-span6"] { width: 73%; } + &[data-span="column-width-span7"] { width: 85.5%; } + &[data-span="column-width-span8"] { width: 98%; } } -@media screen and (max-width: 900px) { +@media screen and (width <= 900px) { .bb-row { - &[data-span="column-width-span1"], - &[data-span="column-width-span2"], - &[data-span="column-width-span3"], - &[data-span="column-width-span4"], - &[data-span="column-width-span5"], - &[data-span="column-width-span6"], - &[data-span="column-width-span7"], - &[data-span="column-width-span8"] { - width: 100%; - } - .bb-column { + width: 100%; margin: 0; padding: 0; float: none; diff --git a/assets/stylesheets/common/spoiler.scss b/assets/stylesheets/common/spoiler.scss index ef693b3..e645eaf 100644 --- a/assets/stylesheets/common/spoiler.scss +++ b/assets/stylesheets/common/spoiler.scss @@ -6,11 +6,7 @@ details.bb-spoiler { summary { text-decoration: none; cursor: pointer; - border: 1px solid transparent; - border-top-color: transparent; - border-right-color: transparent; - border-bottom-color: transparent; - border-left-color: transparent; + border: 1px solid var(--tertiary-low); font-size: 12px; border-radius: 4px; padding-top: 7px; @@ -22,11 +18,11 @@ details.bb-spoiler { background: var(--tertiary); overflow: hidden; white-space: nowrap; - word-wrap: normal; + overflow-wrap: normal; text-overflow: ellipsis; max-width: 100%; display: inline-block; - border-color: var(--tertiary-low); + &::before { display: none; } @@ -40,12 +36,23 @@ details.bb-spoiler { border-left: 5px solid var(--tertiary); background-color: var(--secondary-very-high); } + + // Native details elements handle mouse, touch and keyboard interaction. + &[open] > div.bb-spoiler-content { + display: block; + } } span.bb-inline-spoiler { display: inline; - background-color: currentColor; + background-color: currentcolor; cursor: pointer; + + &:focus-visible { + outline: 2px solid var(--tertiary); + outline-offset: 2px; + } + &[data-displayed="true"] { background-color: var(--tertiary-hover); } diff --git a/assets/stylesheets/common/table.scss b/assets/stylesheets/common/table.scss new file mode 100644 index 0000000..2282e19 --- /dev/null +++ b/assets/stylesheets/common/table.scss @@ -0,0 +1,52 @@ +table.bb-table { + width: 100%; + border-collapse: collapse; + + th, + td { + border: 1px solid var(--tertiary); + font-size: 85%; + padding: 3px 6px; + } + + th { + color: var(--tertiary); + } + + tr[data-bb-table-row] > td { + background: var(--tertiary-low); + } + + &[data-bb-table-style$="zebra"] > tbody > tr:nth-child(even), + &[data-bb-table-style$="zebra2"] > tbody > tr:nth-child(4n + 1), + &[data-bb-table-style$="zebra2"] > tbody > tr:nth-child(4n + 2) { + background: var(--tertiary-low); + } + + &[data-bb-table-style^="dotted"] { + th, + td { + border-style: dotted; + } + } + + &[data-bb-table-style^="none"] { + th, + td { + border-style: none; + } + } + + &[data-bb-table-style^="dark"] { + th, + td { + border-color: var(--primary-medium); + } + } + + td.bb-table-footer { + text-align: center; + color: var(--tertiary); + font-style: italic; + } +} diff --git a/assets/stylesheets/common/tabs.scss b/assets/stylesheets/common/tabs.scss index ff7eb90..4f0799a 100644 --- a/assets/stylesheets/common/tabs.scss +++ b/assets/stylesheets/common/tabs.scss @@ -1,27 +1,35 @@ div.bb-tabs { - padding: 10px; + // Legacy layouts paint their own controls over the tab labels. Keep the + // XenForo title row's 20px inset and dimensions so those controls still + // line up with the real, keyboard-accessible radio labels underneath. + box-sizing: border-box; + padding: 0 0 0 20px; display: flex; flex-flow: row wrap; - justify-content: start; + align-items: flex-start; + justify-content: flex-start; - label.bb-tab-label { + > label.bb-tab-label { cursor: pointer; order: 0; - margin: 5px; + flex: 0 0 auto; + + // 22px content + 2px border + 6px spacing matches the old 30px title row. + margin: 0 10px 6px 0; box-sizing: content-box; display: block; + font-size: 13px; font-weight: bold; height: auto; + min-height: 22px; + max-width: calc(100% - 56px); line-height: 22px; padding: 0 22px; text-transform: uppercase; + white-space: normal; overflow-wrap: anywhere; border: 1px solid var(--tertiary); - &:first-of-type { - margin-left: 10px; - } - &:hover, &:focus { color: var(--tertiary-hover); @@ -29,31 +37,35 @@ div.bb-tabs { } } - input[type="radio"] { + > input[type="radio"] { position: absolute; left: -200vw; + &:focus-visible + label.bb-tab-label { outline: 2px solid var(--tertiary); } } - .bb-tab-content { - flex: 1 0 1; - width: 100%; + > .bb-tab-content { + // The original panel starts 10px from the wrapper edge, while the title + // row starts at 20px. Account for the title inset without widening cards. + box-sizing: border-box; + flex: 0 0 calc(100% + 10px); + width: calc(100% + 10px); order: 99; - margin-top: 10px; - justify-self: stretch; + margin: 10px 0 0 -10px; border-top: 3px solid var(--tertiary); border-bottom: 1px solid var(--tertiary); padding: 10px; + overflow: hidden; display: none; } - input[type="radio"]:checked + label.bb-tab-label + .bb-tab-content { + > input[type="radio"]:checked + label.bb-tab-label + .bb-tab-content { display: block; } - input[type="radio"]:checked + label.bb-tab-label { + > input[type="radio"]:checked + label.bb-tab-label { color: var(--tertiary); } } diff --git a/bbcode-src/index.js b/bbcode-src/index.js index 69a47b0..8084027 100644 --- a/bbcode-src/index.js +++ b/bbcode-src/index.js @@ -6,6 +6,7 @@ import { removeEmptyLinePlugin } from "./plugins/removeEmptyLinesInAttr"; import { availableTags, preset, preventParsing } from "./preset"; import { postprocess } from "./utils/postprocess"; import { preprocessRaw } from "./utils/preprocess"; +import { createLegacyTokenizer } from "./utils/tokenizer"; const options = { onlyAllowTags: [...availableTags], @@ -20,6 +21,24 @@ const options = { }, }; const presetTags = preset(); +const customTagPattern = new RegExp( + `\\[(?:${availableTags.filter((tag) => tag !== "savenl").join("|")})(?=[\\s=\\]])`, + "i" +); + +export function containsBBCode(code) { + // Hoist native fenced/inline code before deciding whether this post needs + // legacy whitespace rules. A BBCode example is still ordinary Markdown. + // Indented code can only start at the document start or after a blank line; + // indentation continuing an ordinary paragraph is not a Markdown code block. + // This is detection only: indenting nested tags in legacy layouts must not + // turn those layouts into code when actual BBCode exists outside examples. + const withoutIndentedCode = code + .replace(/\r\n?/g, "\n") + .replace(/(^|\n[ \t]*\n)(?:(?: {4}| {0,3}\t)[^\n]*(?:\n|$)|[ \t]*\n)+/g, "$1"); + const [preprocessed] = preprocessRaw(withoutIndentedCode); + return customTagPattern.test(preprocessed); +} export const RpNBBCode = (code, opts) => { const plugins = [presetTags]; @@ -30,6 +49,7 @@ export const RpNBBCode = (code, opts) => { const [preprocessed, preprocessedData] = preprocessRaw(code); return bbob(plugins).process(preprocessed, { render, + createTokenizer: createLegacyTokenizer, ...options, data: { ...preprocessedData, @@ -42,4 +62,4 @@ export const RpNBBCode = (code, opts) => { }); }; -export { postprocess }; +export { availableTags, postprocess }; diff --git a/bbcode-src/plugins/lineBreak.js b/bbcode-src/plugins/lineBreak.js index f0d0e2b..0ae0ad8 100644 --- a/bbcode-src/plugins/lineBreak.js +++ b/bbcode-src/plugins/lineBreak.js @@ -23,8 +23,18 @@ const isString = (value) => typeof value === "string"; * @param t tree of nodes to be processed * @returns modified tree */ -const walk = (t, disableLineBreakConversion = false) => { - const tree = t; +const walk = (t, disableLineBreakConversion = false, preserveHeadingText = false) => { + let tree = t; + + if (isString(tree) && preserveHeadingText) { + // Text such as "# character sheet" is a small literal label in legacy + // layouts. Encode its first hash before Markdown's block rules run; inline + // entity parsing restores the original text without creating a heading. + // This never rewrites attributes, code examples or Markdown outside layouts. + tree = tree.replace(/^([ \t]*)(#{1,6})(?=[ \t\r\n]|$)/gm, (_, indent, hashes) => { + return `${indent}#${hashes.slice(1)}`; + }); + } if (Array.isArray(tree)) { reduceWordsToLines(tree); @@ -34,7 +44,7 @@ const walk = (t, disableLineBreakConversion = false) => { tree.push(MD_NEWLINE_INJECT); } for (let idx = 0; idx < tree.length; idx++) { - const child = walk(tree[idx], disableLineBreakConversion); + const child = walk(tree[idx], disableLineBreakConversion, preserveHeadingText); if (Array.isArray(child)) { tree.splice(idx, 1, ...child); idx += child.length - 1; @@ -51,7 +61,10 @@ const walk = (t, disableLineBreakConversion = false) => { if (tree.disableLineBreakConversion) { disableLineBreakConversion = true; } - walk(tree.content, disableLineBreakConversion); + // Only author-created DIVs and NOBR regions own their heading typography. + // Generated DIVs (spoilers, quotes, etc.) do not change Markdown semantics. + preserveHeadingText ||= tree.preserveHeadingText || tree.attrs?.["data-bbcode-div"] === "true"; + walk(tree.content, disableLineBreakConversion, preserveHeadingText); return tree.tag ? tree : tree.content; } else if (isString(tree) && URL_REGEX_SINGLE_LINE.test(tree.trim())) { // if the entire string is a URL, then it should be prepared for onebox. diff --git a/bbcode-src/preset.js b/bbcode-src/preset.js index fa761cc..e0f89b8 100644 --- a/bbcode-src/preset.js +++ b/bbcode-src/preset.js @@ -25,6 +25,7 @@ import { highlight } from "./tags/highlight"; import { imagefloat } from "./tags/imagefloat"; import { justify } from "./tags/justify"; import { br, nobr } from "./tags/lineBreak"; +import { list } from "./tags/list"; import { mail } from "./tags/mail"; import { newspaper } from "./tags/newspaper"; import { note } from "./tags/note"; @@ -42,6 +43,7 @@ import { size } from "./tags/size"; import { inlinespoiler, spoiler } from "./tags/spoiler"; import { sub } from "./tags/subscript"; import { sup } from "./tags/superscript"; +import { tableTags } from "./tags/table"; import { tab, tabs } from "./tags/tabs"; import { textmessage } from "./tags/textmessage"; import { thinprogress } from "./tags/thinprogress"; @@ -49,6 +51,7 @@ import { thinprogress } from "./tags/thinprogress"; const tags = { ...accordionTags, ...alignment, + ...tableTags, ...anchor, animation, bg, @@ -81,6 +84,7 @@ const tags = { inlinespoiler, justify, keyframe, + list, mail, newspaper, nobr, diff --git a/bbcode-src/tags/div.js b/bbcode-src/tags/div.js index eeb657a..69b71b8 100644 --- a/bbcode-src/tags/div.js +++ b/bbcode-src/tags/div.js @@ -18,9 +18,10 @@ export const div = (node, options) => { return toNode( "div", { + "data-bbcode-div": "true", style, }, - node.content, + node.content ); } @@ -39,9 +40,10 @@ export const div = (node, options) => { return toNode( "div", { + "data-bbcode-div": "true", class: classNames, style, }, - node.content, + node.content ); }; diff --git a/bbcode-src/tags/lineBreak.js b/bbcode-src/tags/lineBreak.js index f619679..36ebbc0 100644 --- a/bbcode-src/tags/lineBreak.js +++ b/bbcode-src/tags/lineBreak.js @@ -20,5 +20,9 @@ export const br = () => { * ``` */ export const nobr = (node) => { - return { disableLineBreakConversion: true, content: node.content }; + return { + disableLineBreakConversion: true, + preserveHeadingText: true, + content: node.content, + }; }; diff --git a/bbcode-src/tags/list.js b/bbcode-src/tags/list.js new file mode 100644 index 0000000..5e887f6 --- /dev/null +++ b/bbcode-src/tags/list.js @@ -0,0 +1,56 @@ +import { isStringNode } from "@bbob/plugin-helper"; +import { preprocessAttr, toNode } from "../utils/common"; + +// XenForo's [*] is an item separator, not a paired BBCode tag. Split only +// strings here so nested lists and formatting nodes stay inside their item. +export const list = (node, options) => { + const items = []; + let content = []; + + function appendItem() { + while (content.length && isStringNode(content[0]) && !content[0].trim()) { + content.shift(); + } + while ( + content.length && + isStringNode(content[content.length - 1]) && + !content[content.length - 1].trim() + ) { + content.pop(); + } + if (content.length) { + if (isStringNode(content[0])) { + content[0] = content[0].trimStart(); + } + const last = content.length - 1; + if (isStringNode(content[last])) { + content[last] = content[last].trimEnd(); + } + items.push(toNode("li", {}, content)); + } + content = []; + } + + for (const child of node.content) { + if (!isStringNode(child)) { + content.push(child); + continue; + } + const parts = child.split("[*]"); + content.push(parts[0]); + for (const part of parts.slice(1)) { + appendItem(); + content.push(part); + } + } + appendItem(); + + let style = String(preprocessAttr(node, options.data.raw)._default || "").trim(); + if ( + (style.startsWith("'") && style.endsWith("'")) || + (style.startsWith('"') && style.endsWith('"')) + ) { + style = style.slice(1, -1); + } + return toNode(style === "1" ? "ol" : "ul", {}, items); +}; diff --git a/bbcode-src/tags/table.js b/bbcode-src/tags/table.js new file mode 100644 index 0000000..1710000 --- /dev/null +++ b/bbcode-src/tags/table.js @@ -0,0 +1,89 @@ +import { isTagNode } from "@bbob/plugin-helper"; +import { preprocessAttr, toNode, toRawTag } from "../utils/common"; + +// These are the variants supported by RpNation's XenForo table stylesheet. +const TABLE_STYLE = /^(?:(?:none|dotted|dark)(?:-zebra2?)?|zebra2?)$/; +const ROW_STYLES = new Set(["blue", "gray", "grey"]); +const ROW_TAGS = new Set(["tr"]); +const CELL_TAGS = new Set(["td", "th", "tf"]); + +function hasUnexpectedContent(content, allowedTags) { + return content.some((child) => { + if (typeof child === "string") { + return child.trim().length > 0; + } + return !isTagNode(child) || !allowedTags.has(child.tag.toLowerCase()); + }); +} + +function option(node, options) { + return String(preprocessAttr(node, options.data.raw)._default || "") + .trim() + .toLowerCase(); +} + +function table(node, options) { + if (node.gen) { + return node; + } + + const rows = node.content.filter((child) => isTagNode(child) && child.tag.toLowerCase() === "tr"); + if ( + !rows.length || + hasUnexpectedContent(node.content, ROW_TAGS) || + rows.some((row) => hasUnexpectedContent(row.content, CELL_TAGS)) + ) { + // Invalid legacy tables can contain meaningful text outside their cells. + // Keep their source visible instead of silently discarding that content. + return toRawTag(node, options.data.raw); + } + rows.forEach((row) => (row.isBBCodeTableRow = true)); + + const style = option(node, options); + const attrs = { class: "bb-table" }; + if (TABLE_STYLE.test(style)) { + attrs["data-bb-table-style"] = style; + } + return toNode("table", attrs, rows); +} + +function tr(node, options) { + if (node.gen) { + return node; + } + if (!node.isBBCodeTableRow) { + return toRawTag(node, options.data.raw); + } + + const cells = node.content.filter( + (child) => isTagNode(child) && CELL_TAGS.has(child.tag.toLowerCase()) + ); + cells.forEach((tableCell) => (tableCell.isBBCodeTableCell = true)); + + const style = option(node, options); + const attrs = {}; + if (ROW_STYLES.has(style)) { + attrs["data-bb-table-row"] = style === "grey" ? "gray" : style; + } + return toNode("tr", attrs, cells); +} + +function cell(node, options) { + if (node.gen) { + return node; + } + if (!node.isBBCodeTableCell) { + return toRawTag(node, options.data.raw); + } + + const tag = node.tag.toLowerCase(); + const attrs = tag === "tf" ? { class: "bb-table-footer" } : {}; + const span = option(node, options); + // HTML colspans are positive integers, with a maximum of 1000. + if (/^\d{1,4}$/.test(span) && Number(span) > 0 && Number(span) <= 1000) { + attrs.colspan = String(Number(span)); + } + return toNode(tag === "th" ? "th" : "td", attrs, node.content); +} + +export const tableTags = { table, tr, td: cell, th: cell, tf: cell }; diff --git a/bbcode-src/utils/common.js b/bbcode-src/utils/common.js index 8ca8adc..de54298 100644 --- a/bbcode-src/utils/common.js +++ b/bbcode-src/utils/common.js @@ -34,36 +34,31 @@ const toNode = (tag, attrs, content = []) => ({ * @returns processed attributes */ const preprocessAttr = (node, raw) => { + if (raw && node.start) { + const nodeRaw = raw.substring(node.start.from, node.start.to); + const defaultOption = /^\[[^\s=\]]+\s*=/.exec(nodeRaw); + if (defaultOption) { + // A default option can contain complete CSS, including spaces, equals + // signs in URLs, and quoted font names. Read it from the original tag + // rather than reconstructing it from the tokenized keyed attributes. + let value = nodeRaw.slice(defaultOption[0].length, -1).trim(); + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + return { _default: value }; + } + } + const keys = Object.keys(node.attrs).join(" "); const vals = Object.values(node.attrs).join(" "); if (keys !== vals) { // [tag key=val] return node.attrs; } - if (!raw || !node.start) { - return { - _default: vals, - }; - } - // [tag=attr] - // node.start.from = 0 - // node.start.to = 10 - const nodeRaw = raw.substring(node.start.from, node.start.to); - if (!nodeRaw.includes("=")) { - // [tag] or [tag attr] - return node.attrs; - } - const openTagParts = nodeRaw.split("="); - if (openTagParts.length !== 2) { - return node.attrs; - } - let val = openTagParts[1].slice(0, -1).trim(); // `attr` or `"attr"` - if (val.startsWith('"') && val.endsWith('"')) { - val = val.slice(1, -1); - } - return { - _default: val, - }; + return raw && node.start ? node.attrs : { _default: vals }; }; /** @@ -132,8 +127,10 @@ const URL_REGEX = const MD_URL_REGEX = /\!?\[.*\]\((http|ftp|https|upload):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])\)/; const URL_REGEX_SINGLE_LINE = new RegExp(`^${URL_REGEX.source}|${MD_URL_REGEX.source}$`); +// Both ends of an inline-code delimiter must be whole backtick runs. In +// particular, never retry shorter openers from inside an unmatched long run. const ESCAPABLES_REGEX = - /((\n|^)(?```+|~~~+)(?.*\n))|(?\[(?i?code|plain)(=.*)?\])|(?(?`{1,2})(.*)(?\k))/im; + /((\n|^)(?```+|~~~+)(?.*\n))|(?\[(?i?code|plain)(=[^\]\r\n]*)?\])|(?(?`+)(?!`)([\s\S]*?)(?(?)(?!`))/im; const MD_TABLE_REGEX = /^(\|[^\n]+\|\r?\n)((?:\| ?:?[-]+:? ?)+\|)(\n(?:\|[^\n]+\|\r?\n?)*)?$/m; const MD_BROKEN_ORDERED_LIST = "
\n
    "; diff --git a/bbcode-src/utils/postprocess.js b/bbcode-src/utils/postprocess.js index 6270578..0f7b234 100644 --- a/bbcode-src/utils/postprocess.js +++ b/bbcode-src/utils/postprocess.js @@ -41,7 +41,13 @@ function cleanMultilineMDBlocks(raw) { function renderHoistedCodeBlocks(raw, data) { const hoistMap = data.hoistMap; for (const [uuid, content] of Object.entries(hoistMap)) { - raw = raw.replaceAll(uuid, content); + // These examples bypass Markdown's text renderer while hoisted. Escape + // them when restoring them, so HTML examples stay text inside code/plain. + const escaped = content + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">"); + raw = raw.replaceAll(uuid, escaped); } return raw; } diff --git a/bbcode-src/utils/tokenizer.js b/bbcode-src/utils/tokenizer.js new file mode 100644 index 0000000..5214b4b --- /dev/null +++ b/bbcode-src/utils/tokenizer.js @@ -0,0 +1,49 @@ +import { createLexer, createTokenOfType } from "@bbob/parser/lexer"; +import { TYPE_TAG, TYPE_WORD } from "@bbob/parser/Token"; + +// BBob 4.2 only flushes one unclosed node at EOF and blindly pops a node for +// any closing tag. Long, historically valid XenForo layouts can therefore +// disappear entirely. Use its tokenizer extension to close missing children +// before their ancestor, and finish every remaining wrapper at EOF. +export function createLegacyTokenizer(input, options) { + const pending = []; + const onToken = options.onToken; + const close = (name, position) => { + onToken(createTokenOfType(TYPE_TAG, `/${name}`, 0, 0, position, position)); + }; + + const lexer = createLexer(input, { + ...options, + onToken(token) { + if (token.isTag() && token.isStart()) { + const name = token.getValue().toLowerCase(); + if (lexer.isTokenNested(name)) { + pending.push(name); + } + } else if (token.isTag() && token.isEnd()) { + const name = token.getValue().slice(1).toLowerCase(); + const matching = pending.lastIndexOf(name); + if (matching === -1) { + // An extra closing tag is text; it must not close an unrelated node. + onToken(createTokenOfType(TYPE_WORD, token.toString())); + return; + } + while (pending.length - 1 > matching) { + close(pending.pop(), token.getStart()); + } + pending.pop(); + } + onToken(token); + }, + }); + + const tokenize = lexer.tokenize; + lexer.tokenize = () => { + const tokens = tokenize(); + while (pending.length) { + close(pending.pop(), input.length); + } + return tokens; + }; + return lexer; +} diff --git a/config/routes.rb b/config/routes.rb index 3e10780..f550a8f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,7 +2,7 @@ BbCode::Engine.routes.draw do namespace :admin, defaults: { format: :json, constraints: StaffConstraint.new } do - get "refresh" => "refresh#index" + post "refresh" => "refresh#index" end end diff --git a/package.json b/package.json index 66cdaec..28bbf14 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "dependencies": { "@bbob/core": "^4.2.0", "@bbob/html": "^4.2.0", + "@bbob/parser": "4.2.0", "@bbob/preset": "^4.2.0", "@bbob/preset-html5": "^4.2.0" }, diff --git a/plugin.rb b/plugin.rb index 6a7c7f9..1311f35 100644 --- a/plugin.rb +++ b/plugin.rb @@ -21,7 +21,7 @@ end module ::BbCode - PLUGIN_NAME = "BbCode" + PLUGIN_NAME = "bbcode" end require_relative "lib/bb_code/engine" @@ -37,15 +37,10 @@ module ::BbCode # when false, it does nothing, which allows for persistence of non-default whitespace. class ::TextCleaner # rubocop:disable Discourse/Plugins/NoMonkeyPatching module Optional_normalize_whitespace - def title_options - options = super - options[:normalize_whitespace_opt] = SiteSetting.discourse_normalize_whitespace - options - end def normalize_whitespaces(text) - options = title_options - text = super(text) if (options[:normalize_whitespace_opt]) - text + return text if SiteSetting.bbcode_enabled && !SiteSetting.discourse_normalize_whitespace + + super end end singleton_class.prepend Optional_normalize_whitespace diff --git a/spec/lib/pretty_text_spec.rb b/spec/lib/pretty_text_spec.rb new file mode 100644 index 0000000..01b5872 --- /dev/null +++ b/spec/lib/pretty_text_spec.rb @@ -0,0 +1,687 @@ +# frozen_string_literal: true + +RSpec.describe PrettyText do + before { SiteSetting.bbcode_enabled = true } + + def bbcode_fragment(raw, **options) + Nokogiri::HTML5.fragment(PrettyText.cook(raw, options)) + end + + it "cooks nested XenForo spoilers as independent, initially closed details" do + fragment = + bbcode_fragment( + "[spoiler=Outer]Before [spoiler=Inner][b]Hidden text[/b][/spoiler] After[/spoiler]", + ) + + outer = fragment.at_css("details.bb-spoiler") + inner = outer.at_css(".bb-spoiler-content details.bb-spoiler") + + expect(fragment.css("details.bb-spoiler").size).to eq(2) + expect(outer.at_css("summary").text).to eq("Spoiler: Outer") + expect(inner.at_css("summary").text).to eq("Spoiler: Inner") + expect(inner.at_css(".bb-spoiler-content .bbcode-b").text).to eq("Hidden text") + expect(outer.key?("open")).to eq(false) + expect(inner.key?("open")).to eq(false) + expect(outer.at_css(".bb-spoiler-content").text).to include("Before", "After") + end + + it "accepts mixed-case legacy spoiler tags" do + fragment = bbcode_fragment("[SPOILER=Example]Hidden [I]text[/I][/SPOILER]") + + expect(fragment.at_css("details.bb-spoiler > summary").text).to eq("Spoiler: Example") + expect(fragment.at_css(".bb-spoiler-content .bbcode-i").text).to eq("text") + end + + it "keeps formatted children inside an inline spoiler" do + fragment = bbcode_fragment("Before [inlinespoiler][b]Hidden[/b][/inlinespoiler] after") + + expect(fragment.at_css("span.bb-inline-spoiler .bbcode-b").text).to eq("Hidden") + expect(fragment.text).to include("Before", "after") + end + + it "preserves literal guide examples in BBCode and Markdown code blocks" do + example = "[spoiler=Example][b]Do not render this example[/b][/spoiler]" + fragment = bbcode_fragment("[code]#{example}[/code]\n\n```bbcode\n#{example}\n```") + + expect(fragment.css("pre code").size).to eq(2) + expect(fragment.css("pre code").map(&:text).map(&:strip)).to eq([example, example]) + expect(fragment.css("details, .bbcode-b")).to be_empty + end + + it "preserves inline code and plain-tag examples used by the guide" do + fragment = + bbcode_fragment( + "`[spoiler=Example]Hidden[/spoiler]` [plain][b]literal[/b][/plain] [b]rendered[/b]", + ) + + expect(fragment.at_css("code").text).to eq("[spoiler=Example]Hidden[/spoiler]") + expect(fragment.text).to include("[b]literal[/b]") + expect(fragment.css(".bbcode-b").map(&:text)).to eq(["rendered"]) + expect(fragment.css("details")).to be_empty + end + + it "does not reinterpret part of an unmatched backtick run as a shorter delimiter" do + fragment = bbcode_fragment("[b]Outside[/b] prefix ````example``` suffix") + + expect(fragment.css("code")).to be_empty + expect(fragment.text).to include("prefix ````example``` suffix") + expect(fragment.at_css(".bbcode-b").text).to eq("Outside") + end + + it "keeps exact-length backtick delimiters and multiline inline examples" do + fragment = + bbcode_fragment( + "[b]Outside[/b] prefix ``one ` two`` suffix `alpha\n[b]literal[/b]\nbeta`", + ) + + expect(fragment.css("code").size).to eq(2) + expect(fragment.css("code").first.text).to eq("one ` two") + expect(fragment.css("code").last.text).to include("alpha", "[b]literal[/b]", "beta") + expect(fragment.css(".bbcode-b").map(&:text)).to eq(["Outside"]) + end + + it "shows HTML examples as text inside BBCode and Markdown code blocks" do + example = '
    A & B
    ' + fragment = + bbcode_fragment( + "[code=html]#{example}[/code]\n\n```html\n#{example}\n```\n\n`#{example}`", + ) + + expect(fragment.css("code").map(&:text).map(&:strip)).to eq([example, example, example]) + expect(fragment.css("code div, .example")).to be_empty + end + + it "retains Markdown paragraphs when BBCode appears only inside a fenced example" do + example = "[spoiler=Example]Hidden text[/spoiler]" + fragment = + bbcode_fragment( + "First **paragraph**.\n\n```bbcode\n#{example}\n```\n\nSecond paragraph.", + ) + + expect(fragment.css("p").map(&:text)).to eq(["First paragraph.", "Second paragraph."]) + expect(fragment.at_css("strong").text).to eq("paragraph") + expect(fragment.at_css("pre code").text.strip).to eq(example) + expect(fragment.css("details")).to be_empty + end + + it "retains Markdown paragraphs when BBCode appears only inside inline code" do + example = "[div]A layout example[/div]" + fragment = bbcode_fragment("First `#{example}` paragraph.\n\nSecond **paragraph**.") + + expect(fragment.css("p").map(&:text)).to eq( + ["First #{example} paragraph.", "Second paragraph."], + ) + expect(fragment.at_css("code").text).to eq(example) + expect(fragment.at_css("strong").text).to eq("paragraph") + expect(fragment.css("div")).to be_empty + end + + it "retains native indented code containing BBCode examples" do + fragment = + bbcode_fragment(<<~MARKDOWN) + First **paragraph**. + + [b]Literal bold example[/b] + [spoiler=Example]Hidden text[/spoiler] + + Second paragraph. + MARKDOWN + + expect(fragment.css("p").map(&:text)).to eq(["First paragraph.", "Second paragraph."]) + expect(fragment.at_css("strong").text).to eq("paragraph") + expect(fragment.at_css("pre code").text.strip).to eq( + "[b]Literal bold example[/b]\n[spoiler=Example]Hidden text[/spoiler]", + ) + expect(fragment.css(".bbcode-b, details")).to be_empty + end + + it "retains native tab-indented code containing a BBCode example" do + example = "[spoiler=Example]Hidden text[/spoiler]" + fragment = bbcode_fragment("First paragraph.\n\n\t#{example}\n\nSecond paragraph.") + + expect(fragment.css("p").map(&:text)).to eq(["First paragraph.", "Second paragraph."]) + expect(fragment.at_css("pre code").text.strip).to eq(example) + expect(fragment.css("details")).to be_empty + end + + it "preserves historical curly-brace accordion slides and their nested content" do + fragment = + bbcode_fragment(<<~BBCODE) + [accordion] + {slide=First|open}[spoiler=Secret][b]First body[/b][/spoiler]{/slide} + {slide=Second}Second body{/slide} + [/accordion] + BBCODE + slides = fragment.css(".bb-accordion > details.bb-slide") + + expect(slides.size).to eq(2) + expect(slides.map { |slide| slide.at_css("summary").text }).to eq(%w[First Second]) + expect(slides.first.key?("open")).to eq(true) + expect(slides.last.key?("open")).to eq(false) + expect(slides.first.at_css(".bb-slide-content details.bb-spoiler .bbcode-b").text).to eq( + "First body", + ) + expect(slides.last.at_css(".bb-slide-content").text).to eq("Second body") + end + + it "gives separate tab sets independent controls and matching labels" do + fragment = + bbcode_fragment( + "[tabs][tab=First]One[/tab][tab=Second]Two[/tab][/tabs]\n" \ + "[tabs][tab=First]Three[/tab][tab=Second]Four[/tab][/tabs]", + ) + groups = fragment.css(".bb-tabs") + inputs = fragment.css("input.bb-tab") + + expect(groups.size).to eq(2) + expect(inputs.size).to eq(4) + expect(inputs.map { |input| input["id"] }.uniq.size).to eq(4) + expect(inputs.map { |input| input["name"] }.uniq.size).to eq(2) + expect(fragment.css("label.bb-tab-label").map { |label| label["for"] }).to eq( + inputs.map { |input| input["id"] }, + ) + groups.each do |group| + expect(group.css("input[checked]").size).to eq(1) + expect(group.css(".bb-tab-content").size).to eq(2) + end + end + + it "preserves nested column layout and fieldset content through sanitization" do + fragment = + bbcode_fragment( + "[row][column=4][fieldset=Character][b]Name[/b][/fieldset][/column]" \ + "[column=8][spoiler=History]Story[/spoiler][/column][/row]", + ) + columns = fragment.css(".bb-row > .bb-column") + + expect(columns.size).to eq(2) + expect(columns.map { |column| column["data-span"] }).to eq( + %w[column-width-span4 column-width-span8], + ) + expect(columns.first.at_css("fieldset.bb-fieldset legend").text).to eq("Character") + expect(columns.first.at_css("fieldset.bb-fieldset .bbcode-b").text).to eq("Name") + expect(columns.last.at_css("details.bb-spoiler .bb-spoiler-content").text).to eq("Story") + end + + it "retains native paragraphs, emphasis, lists and indented code for Markdown posts" do + fragment = + bbcode_fragment(<<~MARKDOWN) + First paragraph with **bold**. + + Second paragraph. + + - One + - Two + + standalone code + MARKDOWN + + expect(fragment.css("p").map(&:text)).to include( + "First paragraph with bold.", + "Second paragraph.", + ) + expect(fragment.at_css("strong").text).to eq("bold") + expect(fragment.css("ul > li").size).to eq(2) + + code = bbcode_fragment("Paragraph.\n\n standalone code") + expect(code.at_css("pre code").text.strip).to eq("standalone code") + end + + it "retains native Markdown tables" do + fragment = bbcode_fragment("| Name | Role |\n| --- | --- |\n| Alice | Writer |") + + expect(fragment.css("table th").map(&:text)).to eq(%w[Name Role]) + expect(fragment.css("table td").map(&:text)).to eq(%w[Alice Writer]) + end + + it "renders guide-style BBCode table headers, cells, footers and column spans" do + fragment = + bbcode_fragment(<<~BBCODE) + [table] + [tr][th=2]Characters[/th][/tr] + [tr][td][b]Name[/b][/td][td][spoiler=History]Story[/spoiler][/td][/tr] + [tr][tf=2]Footer note[/tf][/tr] + [/table] + BBCODE + table = fragment.at_css("table.bb-table") + + expect(table.css("tr").size).to eq(3) + expect(table.at_css("th")["colspan"]).to eq("2") + expect(table.at_css("th").text).to eq("Characters") + expect(table.at_css("td .bbcode-b").text).to eq("Name") + expect(table.at_css("td details.bb-spoiler .bb-spoiler-content").text).to eq("Story") + expect(table.at_css("td.bb-table-footer")["colspan"]).to eq("2") + expect(table.at_css("td.bb-table-footer").text).to eq("Footer note") + end + + it "preserves independent nested BBCode tables" do + fragment = + bbcode_fragment( + "[table][tr][td]Outer [table][tr][td]Inner[/td][/tr][/table][/td][/tr][/table]", + ) + + expect(fragment.css("table.bb-table").size).to eq(2) + expect(fragment.at_css("table.bb-table td table.bb-table td").text).to eq("Inner") + expect(fragment.at_css("table.bb-table > tbody > tr > td").text).to include("Outer") + end + + it "keeps supported XenForo table appearance choices through sanitization" do + styles = %w[none dotted-zebra dark-zebra2] + fragment = + bbcode_fragment( + styles.map { |style| "[table=#{style}][tr][td]Cell[/td][/tr][/table]" }.join("\n"), + ) + + expect(fragment.css("table.bb-table").map { |table| table["data-bb-table-style"] }).to eq( + styles, + ) + end + + it "keeps legacy table row highlights and normalizes grey to gray" do + fragment = + bbcode_fragment( + "[table][tr=blue][td]Blue[/td][/tr][tr=grey][td]Gray[/td][/tr][/table]", + ) + + expect(fragment.css("tr").map { |row| row["data-bb-table-row"] }).to eq(%w[blue gray]) + end + + it "ignores invalid table styling and out-of-range column spans" do + spans = %w[0 -1 1001 2px] + cells = spans.map { |span| "[td=#{span}]Cell[/td]" }.join + fragment = bbcode_fragment("[table=position:fixed][tr=red]#{cells}[/tr][/table]") + + expect(fragment.at_css("table.bb-table").key?("data-bb-table-style")).to eq(false) + expect(fragment.at_css("tr").key?("data-bb-table-row")).to eq(false) + expect(fragment.css("td").size).to eq(4) + expect(fragment.css("[colspan], [style]")).to be_empty + end + + it "preserves orphan table tags as text instead of dropping their content" do + fragment = bbcode_fragment("[td]Orphan cell[/td]\n[tr][th]Orphan header[/th][/tr]") + + expect(fragment.text).to include("[td]Orphan cell[/td]", "Orphan header") + expect(fragment.css("table, tr, td, th")).to be_empty + end + + it "preserves unexpected text around the rows of a legacy table" do + fragment = + bbcode_fragment("[table]Caption text[tr][td]Cell text[/td][/tr]Trailing text[/table]") + + expect(fragment.text).to include("Caption text", "Cell text", "Trailing text") + expect(fragment.css("table, tr, td")).to be_empty + end + + it "preserves unexpected text around the cells of a legacy table row" do + fragment = + bbcode_fragment("[table][tr]Before cell[td]Cell text[/td]After cell[/tr][/table]") + + expect(fragment.text).to include("Before cell", "Cell text", "After cell") + expect(fragment.css("table, tr, td")).to be_empty + end + + it "keeps table source examples literal in the guide's code blocks" do + example = "[table][tr][td]Example[/td][/tr][/table]" + fragment = bbcode_fragment("[code]#{example}[/code]") + + expect(fragment.at_css("pre code").text.strip).to eq(example) + expect(fragment.css("table")).to be_empty + end + + it "renders XenForo list separators with nested formatting" do + fragment = bbcode_fragment("[list][*]First item[*][b]Second item[/b][/list]") + items = fragment.css("ul > li") + + expect(items.map(&:text)).to eq(["First item", "Second item"]) + expect(items.last.at_css(".bbcode-b").text).to eq("Second item") + expect(fragment.text).not_to include("[*]") + end + + it "keeps a nested list inside its outer item" do + fragment = + bbcode_fragment( + "[list][*]First [list][*]Nested item[/list][*]Second item[/list]", + ) + outer = fragment.at_css("ul") + outer_items = outer.element_children.select { |child| child.name == "li" } + + expect(outer_items.size).to eq(2) + expect(outer_items.first.at_css("ul > li").text).to eq("Nested item") + expect(outer_items.last.text).to eq("Second item") + expect(outer_items.last.css("ul")).to be_empty + end + + it "accepts quoted ordered and unordered legacy list options" do + fragment = + bbcode_fragment( + "[LIST='1'][*]Ordered[/LIST]\n[LIST='*'][*]Unordered[/LIST]", + ) + + expect(fragment.css("ol > li").map(&:text)).to eq(["Ordered"]) + expect(fragment.css("ul > li").map(&:text)).to eq(["Unordered"]) + end + + it "renders lists inside layout and nobr wrappers used by design posts" do + fragment = + bbcode_fragment( + "[div class=card][nobr][LIST][*]One[*][i]Two[/i][/LIST][/nobr][/div]", + ) + + expect(fragment.css("div ul > li").map(&:text)).to eq(%w[One Two]) + expect(fragment.at_css("div ul > li .bbcode-i").text).to eq("Two") + expect(fragment.text).not_to include("[LIST]", "[*]") + end + + it "renders indented legacy div layouts inside nobr without exposing generated HTML" do + fragment = + bbcode_fragment(<<~BBCODE) + [div class=outer][nobr] + [div class=card] + [div class=inside][b]Character name[/b][/div] + [/div] + [/nobr][/div] + BBCODE + + expect(fragment.at_css("div div div .bbcode-b").text).to eq("Character name") + expect(fragment.css("pre, code")).to be_empty + expect(fragment.text).not_to include("", "[div", "[/div]") + end + + it "keeps the Midnight card's tiny hash labels literal inside a nobr layout" do + fragment = + bbcode_fragment(<<~BBCODE) + [nobr] + [div=position:relative; width:390px; height:250px;] + [div=position:absolute; top:205px; left:50px; width:120px; text-align:right; font-size:7px; line-height:7px;] + + # character sheet + [br][/br] + # mobile friendly + + [/div] + [/div] + [/nobr] + BBCODE + label = fragment.css("div[data-bbcode-div]").find { |div| div["style"].include?("font-size:7px") } + + expect(label).to be_present + expect(label["style"]).to include("font-size:7px", "line-height:7px") + expect(label.text).to include("# character sheet", "# mobile friendly") + expect(label.at_css("br")).to be_present + expect(fragment.css("h1, h2, h3, h4, h5, h6")).to be_empty + end + + it "keeps hash labels literal inside authored divs without a nobr wrapper" do + fragment = + bbcode_fragment(<<~BBCODE) + [div=font-size:7px;] + + # character sheet + + ## mobile friendly + + [/div] + BBCODE + + expect(fragment.at_css("div[data-bbcode-div]").text).to include( + "# character sheet", + "## mobile friendly", + ) + expect(fragment.css("h1, h2")).to be_empty + end + + it "retains native headings outside legacy layouts and formatting and links inside them" do + fragment = + bbcode_fragment(<<~BBCODE) + # Before the card + + [nobr][div=font-size:7px;] + + # character sheet + [br][/br] + [b]Midnight[/b] [url=https://example.com/character]Open character[/url] + + [/div][/nobr] + + # After the card + BBCODE + card = fragment.at_css("div[data-bbcode-div]") + + expect(fragment.css("h1").map(&:text)).to eq(["Before the card", "After the card"]) + expect(card.text).to include("# character sheet") + expect(card.css("h1")).to be_empty + expect(card.at_css(".bbcode-b").text).to eq("Midnight") + expect(card.at_css('a[href="https://example.com/character"]').text).to eq("Open character") + end + + it "keeps quoted legacy card labels literal while retaining the native quote" do + fragment = + bbcode_fragment(<<~BBCODE) + [quote="Original author"] + [nobr][div=font-size:7px;] + + # character sheet + [br][/br] + # mobile friendly + + [/div][/nobr] + [/quote] + BBCODE + quote = fragment.at_css("aside.quote") + + expect(quote).to be_present + expect(quote.at_css("div[data-bbcode-div]").text).to include( + "# character sheet", + "# mobile friendly", + ) + expect(quote.css("h1")).to be_empty + end + + it "keeps hash labels literal in a standalone nobr wrapper" do + fragment = bbcode_fragment("[nobr]\n\n# character sheet\n\n[/nobr]") + + expect(fragment.text).to include("# character sheet") + expect(fragment.css("h1")).to be_empty + expect(fragment.text).not_to include("[nobr]", "[/nobr]") + end + + it "keeps hash labels literal when the line ends with a Markdown link" do + fragment = + bbcode_fragment("[div]\n# [character sheet](https://example.com/character)\n[/div]") + + expect(fragment.css("h1")).to be_empty + expect(fragment.at_css("div[data-bbcode-div]").text).to include("# character sheet") + expect(fragment.at_css('a[href="https://example.com/character"]').text).to eq("character sheet") + end + + it "preserves indented CRLF hash labels nested inside BBCode formatting" do + fragment = + bbcode_fragment( + "[div=font-size:7px;]\r\n\r\n # character sheet\r\n\r\n" \ + "[b]\r\n ## mobile friendly\r\n[/b]\r\n\r\n[/div]", + ) + card = fragment.at_css("div[data-bbcode-div]") + + expect(card.text).to include("# character sheet") + expect(card.at_css(".bbcode-b").text).to include("## mobile friendly") + expect(fragment.css("h1, h2, pre")).to be_empty + end + + it "retains deliberate BBCode headings inside a layout with literal hash labels" do + fragment = + bbcode_fragment(<<~BBCODE) + [div=font-size:7px;] + [h1]Deliberate heading[/h1] + + # character sheet + + [/div] + BBCODE + card = fragment.at_css("div[data-bbcode-div]") + + expect(card.css("h1").map(&:text)).to eq(["Deliberate heading"]) + expect(card.text).to include("# character sheet") + end + + it "preserves BBCode and Markdown code examples inside a legacy layout" do + example = "# character sheet\n[b]Literal example[/b]" + fragment = + bbcode_fragment( + "[div=font-size:7px;]\n\n[code]#{example}[/code]\n\n" \ + "```bbcode\n#{example}\n```\n\n`# inline label`\n\n[/div]", + ) + card = fragment.at_css("div[data-bbcode-div]") + + expect(card.css("pre code").map(&:text).map(&:strip)).to eq([example, example]) + expect(card.css("code").last.text).to eq("# inline label") + expect(fragment.css("h1, .bbcode-b")).to be_empty + end + + it "preserves CSS hex colors and URL fragments while protecting layout hash labels" do + fragment = + bbcode_fragment(<<~BBCODE) + [div=color:#bf7043; background-color:#000000;] + + # character sheet + [url=https://example.com/character#details]Open details[/url] + + [/div] + BBCODE + card = fragment.at_css("div[data-bbcode-div]") + + expect(card["style"]).to include("color:#bf7043", "background-color:#000000") + expect(card.at_css("a")["href"]).to eq("https://example.com/character#details") + expect(card.at_css("a").text).to eq("Open details") + expect(card.text).to include("# character sheet") + expect(card.css("h1")).to be_empty + end + + it "keeps list source and item markers literal in code examples" do + example = "[LIST='1'][*]First[*][b]Second[/b][/LIST]" + fragment = bbcode_fragment("[code]#{example}[/code]\n\n```bbcode\n#{example}\n```") + + expect(fragment.css("pre code").map(&:text).map(&:strip)).to eq([example, example]) + expect(fragment.css("ul, ol, li, .bbcode-b")).to be_empty + end + + it "does not lose a design post when several nested layout tags are left open" do + fragment = + bbcode_fragment( + "[div class=card][nobr][div class=inner][nobr]Before [b]bold[/b] after[/nobr][/div]", + ) + + expect(fragment.text).to include("Before", "bold", "after") + expect(fragment.at_css(".bbcode-b").text).to eq("bold") + end + + it "preserves content after an outer layout tag closes before its inner tag" do + fragment = + bbcode_fragment( + "[div class=card][nobr]Before [b]bold[/b][/div]After outer close", + ) + + expect(fragment.text).to include("Before", "bold", "After outer close") + expect(fragment.at_css(".bbcode-b").text).to eq("bold") + end + + it "retains trailing content after a closed child inside an unclosed parent" do + fragment = + bbcode_fragment( + "[div class=card]Start [div class=inner]Middle[/div] End", + ) + + expect(fragment.text).to include("Start", "Middle", "End") + end + + it "leaves native cooking in control when the plugin is disabled" do + SiteSetting.bbcode_enabled = false + fragment = bbcode_fragment("First **paragraph**.\n\n[center]Legacy text[/center]") + + expect(fragment.at_css("strong").text).to eq("paragraph") + expect(fragment.css("p").size).to eq(2) + expect(fragment.text).to include("[center]Legacy text[/center]") + expect(fragment.css(".bb-center")).to be_empty + end + + it "honors an explicit feature override used by chat cooking" do + fragment = + bbcode_fragment( + "**Chat text**\n\n[center]Legacy text[/center]", + features_override: %w[bold italics code paragraph], + ) + + expect(fragment.text).to include("[center]Legacy text[/center]") + expect(fragment.css(".bb-center")).to be_empty + end + + it "keeps cooked custom content inside Discourse's sanitizer" do + fragment = + bbcode_fragment( + '[spoiler=Example]' \ + '[b]Safe text[/b][/spoiler]', + ) + + expect(fragment.at_css("details.bb-spoiler .bbcode-b").text).to eq("Safe text") + expect(fragment.css("script, [onerror]")).to be_empty + end + + it "marks authored divs without marking generated layout wrappers" do + fragment = + bbcode_fragment( + '[div="width: 600px"]Plain layout[/div]' \ + '[div class="card" style="width: 700px"]Class layout[/div]' \ + '[bg=red]Generated background[/bg]', + ) + + authored = fragment.css('div[data-bbcode-div="true"]') + expect(authored.map(&:text)).to eq(["Plain layout", "Class layout"]) + expect(authored.last["class"]).to start_with("card__post-") + expect(fragment.at_css(".bb-background").key?("data-bbcode-div")).to eq(false) + end + + { "unquoted" => "", "single-quoted" => "'", "double-quoted" => '"' }.each do |name, quote| + it "preserves a #{name} default div style and its child variable references" do + style = "--paper: #e8e8e8; width: 320px;" + fragment = + bbcode_fragment( + "[div=#{quote}#{style}#{quote}]" \ + "[div=background:var(--paper);]Character sheet[/div][/div]", + ) + outer = fragment.at_css("div[data-bbcode-div]") + child = outer.at_css("div[data-bbcode-div]") + + expect(outer["style"]).to eq(style) + expect(child["style"]).to eq("background:var(--paper);") + expect(child.text).to eq("Character sheet") + end + end + + it "preserves quoted font families and URLs inside a default div style" do + style = "font-family: 'Ubuntu Mono'; background-image: url('https://example.com/paper.png');" + fragment = bbcode_fragment("[div=\"#{style}\"]Character sheet[/div]") + + expect(fragment.at_css("div[data-bbcode-div]")["style"]).to eq(style) + end + + it "preserves keyed div attributes alongside quoted CSS values" do + style = "--paper: #e8e8e8; font-family: 'Ubuntu Mono';" + fragment = + bbcode_fragment( + "[div class=\"card\" style=\"#{style}\"]" \ + "[div=background:var(--paper);]Character sheet[/div][/div]", + ) + outer = fragment.at_css("div[data-bbcode-div]") + + expect(outer["class"]).to start_with("card__post-") + expect(outer["style"]).to eq(style) + expect(outer.at_css("div[data-bbcode-div]")["style"]).to eq("background:var(--paper);") + end + + it "preserves equals signs inside a default div CSS URL" do + style = "background-image:url('https://example.com/paper.png?version=2&mode=paper'); color:#123456;" + fragment = bbcode_fragment("[div=#{style}]Character sheet[/div]") + + expect(fragment.at_css("div[data-bbcode-div]")["style"]).to eq(style) + expect(fragment.text).to eq("Character sheet") + end +end diff --git a/test/javascripts/unit/inline-spoiler-test.js b/test/javascripts/unit/inline-spoiler-test.js new file mode 100644 index 0000000..2fbb25b --- /dev/null +++ b/test/javascripts/unit/inline-spoiler-test.js @@ -0,0 +1,127 @@ +import { module, test } from "qunit"; +import { addInlineSpoilerCode } from "discourse/plugins/bbcode/discourse/initializers/inlinespoiler"; + +module("BBCode | Unit | Inline spoiler", function (hooks) { + hooks.beforeEach(function () { + this.post = document.createElement("div"); + this.post.innerHTML = 'Hidden text'; + document.getElementById("qunit-fixture").append(this.post); + this.spoiler = this.post.querySelector(".bb-inline-spoiler"); + }); + + test("clicking formatted text toggles the entire spoiler", function (assert) { + addInlineSpoilerCode(this.post); + const child = this.spoiler.querySelector("strong"); + + child.click(); + assert.dom(this.spoiler).hasAttribute("data-displayed", "true"); + assert.dom(this.spoiler).hasAttribute("aria-expanded", "true"); + assert.dom(child).doesNotHaveAttribute("data-displayed"); + + child.click(); + assert.dom(this.spoiler).doesNotHaveAttribute("data-displayed"); + assert.dom(this.spoiler).hasAttribute("aria-expanded", "false"); + }); + + test("redecorating a preview does not add duplicate click handlers", function (assert) { + addInlineSpoilerCode(this.post); + addInlineSpoilerCode(this.post); + addInlineSpoilerCode(this.post); + + this.spoiler.click(); + assert.dom(this.spoiler).hasAttribute("data-displayed", "true"); + + this.spoiler.click(); + assert.dom(this.spoiler).doesNotHaveAttribute("data-displayed"); + }); + + test("supports keyboard activation without swallowing unrelated keys", function (assert) { + addInlineSpoilerCode(this.post); + assert.dom(this.spoiler).hasAttribute("tabindex", "0"); + assert.dom(this.spoiler).hasAttribute("role", "button"); + assert.dom(this.spoiler).hasAttribute("aria-expanded", "false"); + + const enter = new KeyboardEvent("keydown", { + key: "Enter", + bubbles: true, + cancelable: true, + }); + this.spoiler.dispatchEvent(enter); + assert.true(enter.defaultPrevented); + assert.dom(this.spoiler).hasAttribute("data-displayed", "true"); + + const arrow = new KeyboardEvent("keydown", { + key: "ArrowRight", + bubbles: true, + cancelable: true, + }); + this.spoiler.dispatchEvent(arrow); + assert.false(arrow.defaultPrevented); + assert.dom(this.spoiler).hasAttribute("data-displayed", "true"); + + const space = new KeyboardEvent("keydown", { + key: " ", + bubbles: true, + cancelable: true, + }); + this.spoiler.dispatchEvent(space); + assert.true(space.defaultPrevented); + assert.dom(this.spoiler).doesNotHaveAttribute("data-displayed"); + assert.dom(this.spoiler).hasAttribute("aria-expanded", "false"); + }); + + test("nested spoilers do not toggle their parent", function (assert) { + this.spoiler.innerHTML = 'Outer Inner'; + const inner = this.spoiler.querySelector(".bb-inline-spoiler"); + addInlineSpoilerCode(this.post); + + this.spoiler.click(); + inner.querySelector("strong").click(); + assert.dom(this.spoiler).hasAttribute("data-displayed", "true"); + assert.dom(inner).hasAttribute("data-displayed", "true"); + + inner.click(); + assert.dom(this.spoiler).hasAttribute("data-displayed", "true"); + assert.dom(inner).doesNotHaveAttribute("data-displayed"); + }); + + test("new preview nodes are decorated without resetting existing state", function (assert) { + addInlineSpoilerCode(this.post); + this.spoiler.click(); + this.post.insertAdjacentHTML("beforeend", 'New content'); + addInlineSpoilerCode(this.post); + + const added = this.post.lastElementChild; + assert.dom(this.spoiler).hasAttribute("aria-expanded", "true"); + assert.dom(added).hasAttribute("aria-expanded", "false"); + added.click(); + assert.dom(added).hasAttribute("data-displayed", "true"); + assert.dom(this.spoiler).hasAttribute("data-displayed", "true"); + }); + + test("revealed links reach delegated handlers through nested spoilers", function (assert) { + this.spoiler.innerHTML = + 'Outer @alice'; + const inner = this.spoiler.querySelector(".bb-inline-spoiler"); + const link = inner.querySelector("a"); + let delegatedClicks = 0; + this.post.addEventListener("click", (event) => { + delegatedClicks++; + assert.strictEqual(event.target, link); + assert.false(event.defaultPrevented, "the spoiler preserves native link handling"); + // Stand in for Discourse's user-card handler and avoid navigating the test. + event.preventDefault(); + }); + addInlineSpoilerCode(this.post); + + this.spoiler.click(); + link.click(); + assert.strictEqual(delegatedClicks, 0, "the first click only reveals hidden content"); + assert.dom(inner).hasAttribute("data-displayed", "true"); + + link.click(); + assert.strictEqual(delegatedClicks, 1, "the revealed link reaches the post handler"); + assert.dom(inner).hasAttribute("data-displayed", "true"); + assert.dom(this.spoiler).hasAttribute("data-displayed", "true"); + }); +}); diff --git a/test/javascripts/unit/scroll-containers-test.js b/test/javascripts/unit/scroll-containers-test.js new file mode 100644 index 0000000..7bcc301 --- /dev/null +++ b/test/javascripts/unit/scroll-containers-test.js @@ -0,0 +1,123 @@ +import { settled } from "@ember/test-helpers"; +import { module, test } from "qunit"; +import { + containBBCodeScrollers, + decorateBBCodeScrollers, +} from "discourse/plugins/bbcode/discourse/lib/bbcode-scroll-containers"; + +module("BBCode | Unit | Scroll containers", function (hooks) { + hooks.beforeEach(function () { + this.post = document.createElement("div"); + document.getElementById("qunit-fixture").append(this.post); + }); + + test("contains positioned artwork without resizing its author scroll area", function (assert) { + this.post.innerHTML = + '
    ' + + '
    ' + + '
    Artwork
    ' + + "
    "; + const scroller = this.post.firstElementChild; + const artwork = scroller.firstElementChild.firstElementChild; + + containBBCodeScrollers(this.post); + + assert.strictEqual(getComputedStyle(scroller).position, "relative"); + assert.strictEqual(artwork.offsetParent, scroller); + assert.strictEqual(scroller.clientWidth, 100); + assert.strictEqual(scroller.scrollWidth, 200); + const initialX = artwork.getBoundingClientRect().x; + scroller.scrollLeft = 50; + assert.strictEqual(artwork.getBoundingClientRect().x, initialX - 50); + }); + + test("leaves unmarked HTML and non-scrolling BBCode divs unchanged", function (assert) { + this.post.innerHTML = + '
    Ordinary HTML
    ' + + '
    Ordinary BBCode div
    ' + + '
    Clipped div
    '; + + containBBCodeScrollers(this.post); + + for (const element of this.post.children) { + assert.strictEqual(element.style.position, ""); + } + }); + + test("preserves explicit author positioning", function (assert) { + const positions = ["absolute", "relative", "fixed", "sticky"]; + this.post.innerHTML = positions + .map( + (position) => + `
    Content
    ` + ) + .join(""); + + containBBCodeScrollers(this.post); + + assert.deepEqual( + [...this.post.children].map((element) => element.style.position), + positions + ); + }); + + test("redecorating stays stable and respects changed author class styles", function (assert) { + this.post.innerHTML = + "" + + '
    Content
    '; + const style = this.post.firstElementChild; + const scroller = this.post.lastElementChild; + + containBBCodeScrollers(this.post); + const initialStyle = scroller.getAttribute("style"); + containBBCodeScrollers(this.post); + assert.strictEqual(scroller.getAttribute("style"), initialStyle); + + style.textContent = ".bbcode-test-scroller { position:absolute; overflow-x:auto; }"; + containBBCodeScrollers(this.post); + assert.strictEqual(scroller.style.position, ""); + assert.strictEqual(getComputedStyle(scroller).position, "absolute"); + }); + + test("restores the author value when a div no longer scrolls", function (assert) { + this.post.innerHTML = + '
    Content
    '; + const scroller = this.post.firstElementChild; + containBBCodeScrollers(this.post); + assert.strictEqual(scroller.style.position, "relative"); + + scroller.style.overflowX = "visible"; + containBBCodeScrollers(this.post); + assert.strictEqual(scroller.style.position, "static"); + }); + + test("waits for detached cooked content to be adopted before measuring CSS", async function (assert) { + const detachedDocument = document.implementation.createHTMLDocument(""); + const post = detachedDocument.createElement("div"); + post.innerHTML = + "" + + '
    Content
    '; + const scroller = post.lastElementChild; + + assert.strictEqual(post.ownerDocument.defaultView, null); + const cleanup = decorateBBCodeScrollers(post); + assert.strictEqual(scroller.style.position, ""); + this.post.append(post); + await settled(); + + assert.strictEqual(scroller.style.position, "relative"); + cleanup(); + }); + + test("cleanup cancels pending decoration before insertion", async function (assert) { + const detachedDocument = document.implementation.createHTMLDocument(""); + const post = detachedDocument.createElement("div"); + post.innerHTML = '
    Content
    '; + const cleanup = decorateBBCodeScrollers(post); + cleanup(); + this.post.append(post); + await settled(); + + assert.strictEqual(post.firstElementChild.style.position, ""); + }); +}); diff --git a/yarn.lock b/yarn.lock index b9d2c58..21c7382 100644 --- a/yarn.lock +++ b/yarn.lock @@ -262,7 +262,7 @@ "@bbob/plugin-helper" "*" "@bbob/types" "*" -"@bbob/parser@*": +"@bbob/parser@*", "@bbob/parser@4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@bbob/parser/-/parser-4.2.0.tgz#0b93898a43db50950f25a7bb05b0816ab6d9dc29" integrity sha512-l8BppXjdQClrUiv+qIN0Oe6aS/vlH7CEduMreDaxYDadUPC0RMzxj9lXVjO0xTbFEVEIUJCGc53qnpiApLbx2g==