From 5fdb52f2055f592ed6971db3562ef263a075bee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?s=C3=B8renpeter?= Date: Sun, 11 Aug 2024 11:12:09 +0200 Subject: [PATCH 1/6] added tinyMarkDownEditor and adjustede the CSS --- libs/timeline.css | 2 +- libs/tiny-mde.css | 233 ++++++++++++++++++++++++++++++++++++++++++ libs/tiny-mde.min.css | 1 + libs/tiny-mde.min.js | 1 + partials/header.php | 5 + views/new_twt.php | 16 +++ 6 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 libs/tiny-mde.css create mode 100644 libs/tiny-mde.min.css create mode 100644 libs/tiny-mde.min.js diff --git a/libs/timeline.css b/libs/timeline.css index edffbb1..6107483 100644 --- a/libs/timeline.css +++ b/libs/timeline.css @@ -320,7 +320,7 @@ article small a:hover { #new_twt { border: none; - text-align: center +/* text-align: center*/ } #new_twt input[type="submit"] { diff --git a/libs/tiny-mde.css b/libs/tiny-mde.css new file mode 100644 index 0000000..ce8f9b9 --- /dev/null +++ b/libs/tiny-mde.css @@ -0,0 +1,233 @@ +.TinyMDE { +/* background-color:#fff; + color:#000; + font-size:16px; + line-height:24px; + outline: none; + padding:5px; */ + + color: var(--text); + background-color: var(--bg); + border: 1px solid var(--border); + + font-size: inherit; + font-family: inherit; + padding: 0.5rem; + margin: 0.5rem 0; + border-radius: var(--standard-border-radius); + min-height: 6rem; + background-color: #222; /* darch.dk only */ +} +/* +.TMBlankLine { + height:24px; +} + +.TMH1, .TMSetextH1 { + font-size:22px; + line-height:32px; + font-weight:bold; + margin-bottom:8px; +} + +.TMSetextH1 { + margin-bottom:0px; +} + +.TMSetextH1Marker { + margin-bottom:8px; +} + +.TMH2, .TMSetextH2 { + font-size:20px; + line-height:28px; + font-weight:bold; + margin-bottom:4px; +} +*/ + +.TMMark_TMCode { + font-family:monospace; + font-size:.9em; +} + +.TMFencedCodeBacktick, .TMFencedCodeTilde, .TMIndentedCode, .TMCode { + font-family:monospace; + font-size:.9em; + background-color: var(--accent-bg); +} + +.TMCodeFenceBacktickOpen, .TMCodeFenceTildeOpen { + border-bottom: 1px solid var(--border); + font-family: monospace; + font-size:.9em; +} + +.TMCodeFenceBacktickClose, .TMCodeFenceTildeClose { + border-top: 1px solid var(--border); + font-family: monospace; + font-size:.9em; +} + +.TMInfoString { + color: var(--accent); +} + +.TMCode { + border:1px solid var(--border); + border-radius: 2px; +} + +.TMBlockquote { + font-style: italic; + border-left:2px solid var(--border); + padding-left:10px; + margin-left:10px; +} + +/* +.TMMark { + color:#a0a0a0; +} + +.TMMark_TMH1, .TMMark_TMH2 { + color:#ff8080; +} +*/ + +.TMMark_TMUL, .TMMark_TMOL { + color: var(--accent); +} + +.TMImage { + text-decoration: underline; + text-decoration-color: #00ff00; +} + +.TMLink { + text-decoration: underline; + text-decoration-color: var(--accent); +} + +.TMLinkLabel { + text-decoration: underline; + font-family: monospace; +} + +.TMLinkLabel_Definition, .TMLinkLabel_Valid { + color: #40c040; +} + +.TMLinkLabel_Invalid { + color: #ff0000; +} + +.TMLinkTitle { + font-style:italic; +} + +.TMLinkDestination, .TMAutolink { + text-decoration: underline; + color: var(--accent); +} + +/* +.TMHR { + position: relative; +} + +.TMHR:before { + content: ''; + position: absolute; + bottom: 50%; + left: 40%; + border-bottom: 2px solid #808080; + width: 20%; + z-index:0; +} +*/ + +.TMHTML, .TMHTMLBlock { + font-family:monospace; + font-size:.9em; + color:#8000ff; +} + +.TMHTMLBlock { + color:#6000c0; +} + +.TMCommandBar { +/* background-color:#f8f8f8; + height:24px; + border:4px solid #f8f8f8; */ + box-sizing: content-box; + display:flex; + user-select: none; + overflow-x: scroll; + overflow-y: hidden; + scrollbar-width: none; + -ms-overflow-style: none; +} + +.TMCommandBar::-webkit-scrollbar { + display: none; +} + +.TMCommandButton { + box-sizing: border-box; + display: inline-block; + height:24px; + width:24px; + padding:3px; + margin-right:4px; + color: var(--text-light); + fill: var(--text-light); + text-align: center; + cursor: pointer; + vertical-align: middle; + font-size:20px; + line-height:18px; + font-family: sans-serif; +} + +.TMCommandDivider { + box-sizing: content-box; + height:24px; + margin-left:4px; + margin-right:8px; + width:0px; +/* border-left:1px solid var(--border);*/ +/* border-right:1px solid #ffffff;*/ + border-left:1px solid var(--border); +} + +.TMCommandButton_Active { + font-weight: bold; + color: var(--accent-bg); + fill: var(--accent-bg); + background-color: var(--accent); +} + +.TMCommandButton_Inactive { +/* background-color:#f8f8f8;*/ + background-color: var(--accent-bg); +} + +.TMCommandButton_Disabled { +/* color:#a0a0a0; + fill:#a0a0a0; */ + color: var(--text-light); + fill: var(--text-light); +} + +@media (hover: hover) { + .TMCommandButton_Active:hover, + .TMCommandButton_Disabled:hover, + .TMCommandButton_Inactive:hover { +/* background-color:#e0e0ff;*/ +/* fill:#000000;*/ + fill: var(--accent); + + } +} diff --git a/libs/tiny-mde.min.css b/libs/tiny-mde.min.css new file mode 100644 index 0000000..08d6828 --- /dev/null +++ b/libs/tiny-mde.min.css @@ -0,0 +1 @@ +.TinyMDE{background-color:#fff;color:#000;font-size:16px;line-height:24px;outline:none;padding:5px}.TMBlankLine{height:24px}.TMH1,.TMSetextH1{font-size:22px;font-weight:700;line-height:32px;margin-bottom:8px}.TMSetextH1{margin-bottom:0}.TMSetextH1Marker{margin-bottom:8px}.TMH2,.TMSetextH2{font-size:20px;font-weight:700;line-height:28px;margin-bottom:4px}.TMMark_TMCode{font-family:monospace;font-size:.9em}.TMCode,.TMFencedCodeBacktick,.TMFencedCodeTilde,.TMIndentedCode{background-color:#e0e0e0;font-family:monospace;font-size:.9em}.TMCodeFenceBacktickOpen,.TMCodeFenceTildeOpen{border-bottom:1px solid silver;font-family:monospace;font-size:.9em}.TMCodeFenceBacktickClose,.TMCodeFenceTildeClose{border-top:1px solid silver;font-family:monospace;font-size:.9em}.TMInfoString{color:#00f}.TMCode{border:1px solid silver;border-radius:2px}.TMBlockquote{border-left:2px solid silver;font-style:italic;margin-left:10px;padding-left:10px}.TMMark{color:#a0a0a0}.TMMark_TMH1,.TMMark_TMH2,.TMMark_TMOL,.TMMark_TMUL{color:#ff8080}.TMImage{text-decoration:underline;text-decoration-color:#0f0}.TMLink{text-decoration:underline;text-decoration-color:#00f}.TMLinkLabel{font-family:monospace;text-decoration:underline}.TMLinkLabel_Definition,.TMLinkLabel_Valid{color:#40c040}.TMLinkLabel_Invalid{color:red}.TMLinkTitle{font-style:italic}.TMAutolink,.TMLinkDestination{color:#00f;text-decoration:underline}.TMHR{position:relative}.TMHR:before{border-bottom:2px solid grey;bottom:50%;content:"";left:40%;position:absolute;width:20%;z-index:0}.TMHTML,.TMHTMLBlock{color:#8000ff;font-family:monospace;font-size:.9em}.TMHTMLBlock{color:#6000c0}.TMCommandBar{-ms-overflow-style:none;background-color:#f8f8f8;border:4px solid #f8f8f8;box-sizing:content-box;display:flex;height:24px;overflow-x:scroll;overflow-y:hidden;scrollbar-width:none;-webkit-user-select:none;user-select:none}.TMCommandBar::-webkit-scrollbar{display:none}.TMCommandButton{fill:#404040;box-sizing:border-box;color:#404040;cursor:pointer;display:inline-block;font-family:sans-serif;font-size:20px;height:24px;line-height:18px;margin-right:4px;padding:3px;text-align:center;vertical-align:middle;width:24px}.TMCommandDivider{border-left:1px solid silver;border-right:1px solid #fff;box-sizing:content-box;height:24px;margin-left:4px;margin-right:8px;width:0}.TMCommandButton_Active{fill:navy;background-color:#c0c0ff;color:navy;font-weight:700}.TMCommandButton_Inactive{background-color:#f8f8f8}.TMCommandButton_Disabled{fill:#a0a0a0;color:#a0a0a0}@media (hover:hover){.TMCommandButton_Active:hover,.TMCommandButton_Disabled:hover,.TMCommandButton_Inactive:hover{fill:#000;background-color:#e0e0ff}} \ No newline at end of file diff --git a/libs/tiny-mde.min.js b/libs/tiny-mde.min.js new file mode 100644 index 0000000..3cf2bc1 --- /dev/null +++ b/libs/tiny-mde.min.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TinyMDE={})}(this,(function(e){"use strict";const t={blockquote:'',bold:'',clear_formatting:'',code:'',h1:'',h2:'',hr:'',image:'',italic:'',link:'',ol:'',strikethrough:'',ul:''},n=/(Mac|iPhone|iPod|iPad)/i.test("undefined"!=typeof navigator?navigator.platform:""),s={bold:{name:"bold",action:"bold",innerHTML:t.bold,title:"Bold",hotkey:"Mod-B"},italic:{name:"italic",action:"italic",innerHTML:t.italic,title:"Italic",hotkey:"Mod-I"},strikethrough:{name:"strikethrough",action:"strikethrough",innerHTML:t.strikethrough,title:"Strikethrough",hotkey:"Mod2-Shift-5"},code:{name:"code",action:"code",innerHTML:t.code,title:"Format as code"},h1:{name:"h1",action:"h1",innerHTML:t.h1,title:"Level 1 heading",hotkey:"Mod-Shift-1"},h2:{name:"h2",action:"h2",innerHTML:t.h2,title:"Level 2 heading",hotkey:"Mod-Shift-2"},ul:{name:"ul",action:"ul",innerHTML:t.ul,title:"Bulleted list"},ol:{name:"ol",action:"ol",innerHTML:t.ol,title:"Numbered list"},blockquote:{name:"blockquote",action:"blockquote",innerHTML:t.blockquote,title:"Quote",hotkey:"Mod2-Shift-Q"},insertLink:{name:"insertLink",action:e=>{e.isInlineFormattingAllowed()&&e.wrapSelection("[","]()")},enabled:(e,t,n)=>!e.isInlineFormattingAllowed(t,n)&&null,innerHTML:t.link,title:"Insert link",hotkey:"Mod-K"},insertImage:{name:"insertImage",action:e=>{e.isInlineFormattingAllowed()&&e.wrapSelection("![","]()")},enabled:(e,t,n)=>!e.isInlineFormattingAllowed(t,n)&&null,innerHTML:t.image,title:"Insert image",hotkey:"Mod2-Shift-I"},hr:{name:"hr",action:e=>e.paste("\n***\n"),enabled:()=>!1,innerHTML:t.hr,title:"Insert horizontal line",hotkey:"Mod2-Shift-L"}};var i="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},r=function(e){return e&&e.Math===Math&&e},a=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof i&&i)||function(){return this}()||i||Function("return this")(),l=function(e){try{return!!e()}catch(e){return!0}},o=!l((function(){return 7!==Object.defineProperty({},1,{get:function(){return 7}})[1]})),u={},c={get exports(){return u},set exports(e){u=e}},h=!l((function(){var e=function(){}.bind();return"function"!=typeof e||e.hasOwnProperty("prototype")})),p=h,d=Function.prototype,g=d.call,m=p&&d.bind.bind(g,g),f=p?m:function(e){return function(){return g.apply(e,arguments)}},M="object"==typeof document&&document.all,T={all:M,IS_HTMLDDA:void 0===M&&void 0!==M},F=T.all,D=T.IS_HTMLDDA?function(e){return"function"==typeof e||e===F}:function(e){return"function"==typeof e},y=function(e){return null==e},C=y,k=TypeError,b=function(e){if(C(e))throw new k("Can't call method on "+e);return e},E=Object,$=function(e){return E(b(e))},A=f({}.hasOwnProperty),w=Object.hasOwn||function(e,t){return A($(e),t)},v=o,x=w,S=Function.prototype,L=v&&Object.getOwnPropertyDescriptor,B=x(S,"name"),N={EXISTS:B,PROPER:B&&"something"===function(){}.name,CONFIGURABLE:B&&(!v||v&&L(S,"name").configurable)},H=a,_=Object.defineProperty,O=function(e,t){try{_(H,e,{value:t,configurable:!0,writable:!0})}catch(n){H[e]=t}return t},I="__core-js_shared__",P=a[I]||O(I,{}),j=D,z=P,R=f(Function.toString);j(z.inspectSource)||(z.inspectSource=function(e){return R(e)});var q,V,Z=z.inspectSource,K=D,U=a.WeakMap,W=K(U)&&/native code/.test(String(U)),X=D,G=T.all,Q=T.IS_HTMLDDA?function(e){return"object"==typeof e?null!==e:X(e)||e===G}:function(e){return"object"==typeof e?null!==e:X(e)},J={},Y=Q,ee=a.document,te=Y(ee)&&Y(ee.createElement),ne=function(e){return te?ee.createElement(e):{}},se=!o&&!l((function(){return 7!==Object.defineProperty(ne("div"),"a",{get:function(){return 7}}).a})),ie=o&&l((function(){return 42!==Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype})),re=Q,ae=String,le=TypeError,oe=function(e){if(re(e))return e;throw new le(ae(e)+" is not an object")},ue=h,ce=Function.prototype.call,he=ue?ce.bind(ce):function(){return ce.apply(ce,arguments)},pe=a,de=D,ge=f({}.isPrototypeOf),me=a,fe="undefined"!=typeof navigator&&String(navigator.userAgent)||"",Me=me.process,Te=me.Deno,Fe=Me&&Me.versions||Te&&Te.version,De=Fe&&Fe.v8;De&&(V=(q=De.split("."))[0]>0&&q[0]<4?1:+(q[0]+q[1])),!V&&fe&&(!(q=fe.match(/Edge\/(\d+)/))||q[1]>=74)&&(q=fe.match(/Chrome\/(\d+)/))&&(V=+q[1]);var ye=V,Ce=l,ke=a.String,be=!!Object.getOwnPropertySymbols&&!Ce((function(){var e=Symbol("symbol detection");return!ke(e)||!(Object(e)instanceof Symbol)||!Symbol.sham&&ye&&ye<41})),Ee=be&&!Symbol.sham&&"symbol"==typeof Symbol.iterator,$e=function(e,t){return arguments.length<2?(n=pe[e],de(n)?n:void 0):pe[e]&&pe[e][t];var n},Ae=D,we=ge,ve=Object,xe=Ee?function(e){return"symbol"==typeof e}:function(e){var t=$e("Symbol");return Ae(t)&&we(t.prototype,ve(e))},Se=String,Le=D,Be=function(e){try{return Se(e)}catch(e){return"Object"}},Ne=TypeError,He=function(e){if(Le(e))return e;throw new Ne(Be(e)+" is not a function")},_e=y,Oe=he,Ie=D,Pe=Q,je=TypeError,ze={},Re=P;({get exports(){return ze},set exports(e){ze=e}}.exports=function(e,t){return Re[e]||(Re[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.33.0",mode:"global",copyright:"© 2014-2023 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.33.0/LICENSE",source:"https://github.com/zloirock/core-js"});var qe=f,Ve=0,Ze=Math.random(),Ke=qe(1..toString),Ue=function(e){return"Symbol("+(void 0===e?"":e)+")_"+Ke(++Ve+Ze,36)},We=ze,Xe=w,Ge=Ue,Qe=be,Je=Ee,Ye=a.Symbol,et=We("wks"),tt=Je?Ye.for||Ye:Ye&&Ye.withoutSetter||Ge,nt=he,st=Q,it=xe,rt=function(e,t){var n=e[t];return _e(n)?void 0:He(n)},at=function(e,t){var n,s;if("string"===t&&Ie(n=e.toString)&&!Pe(s=Oe(n,e)))return s;if(Ie(n=e.valueOf)&&!Pe(s=Oe(n,e)))return s;if("string"!==t&&Ie(n=e.toString)&&!Pe(s=Oe(n,e)))return s;throw new je("Can't convert object to primitive value")},lt=TypeError,ot=function(e){return Xe(et,e)||(et[e]=Qe&&Xe(Ye,e)?Ye[e]:tt("Symbol."+e)),et[e]}("toPrimitive"),ut=function(e,t){if(!st(e)||it(e))return e;var n,s=rt(e,ot);if(s){if(void 0===t&&(t="default"),n=nt(s,e,t),!st(n)||it(n))return n;throw new lt("Can't convert object to primitive value")}return void 0===t&&(t="number"),at(e,t)},ct=xe,ht=o,pt=se,dt=ie,gt=oe,mt=function(e){var t=ut(e,"string");return ct(t)?t:t+""},ft=TypeError,Mt=Object.defineProperty,Tt=Object.getOwnPropertyDescriptor,Ft="enumerable",Dt="configurable",yt="writable";J.f=ht?dt?function(e,t,n){if(gt(e),t=mt(t),gt(n),"function"==typeof e&&"prototype"===t&&"value"in n&&yt in n&&!n[yt]){var s=Tt(e,t);s&&s[yt]&&(e[t]=n.value,n={configurable:Dt in n?n[Dt]:s[Dt],enumerable:Ft in n?n[Ft]:s[Ft],writable:!1})}return Mt(e,t,n)}:Mt:function(e,t,n){if(gt(e),t=mt(t),gt(n),pt)try{return Mt(e,t,n)}catch(e){}if("get"in n||"set"in n)throw new ft("Accessors not supported");return"value"in n&&(e[t]=n.value),e};var Ct,kt,bt,Et=J,$t=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}},At=o?function(e,t,n){return Et.f(e,t,$t(1,n))}:function(e,t,n){return e[t]=n,e},wt=Ue,vt=ze("keys"),xt=W,St=a,Lt=Q,Bt=At,Nt=w,Ht=P,_t=function(e){return vt[e]||(vt[e]=wt(e))},Ot="Object already initialized",It=St.TypeError,Pt=St.WeakMap;if(xt||Ht.state){var jt=Ht.state||(Ht.state=new Pt);jt.get=jt.get,jt.has=jt.has,jt.set=jt.set,Ct=function(e,t){if(jt.has(e))throw new It(Ot);return t.facade=e,jt.set(e,t),t},kt=function(e){return jt.get(e)||{}},bt=function(e){return jt.has(e)}}else{var zt=_t("state");Ct=function(e,t){if(Nt(e,zt))throw new It(Ot);return t.facade=e,Bt(e,zt,t),t},kt=function(e){return Nt(e,zt)?e[zt]:{}},bt=function(e){return Nt(e,zt)}}var Rt={set:Ct,get:kt,has:bt,enforce:function(e){return bt(e)?kt(e):Ct(e,{})},getterFor:function(e){return function(t){var n;if(!Lt(t)||(n=kt(t)).type!==e)throw new It("Incompatible receiver, "+e+" required");return n}}},qt=f,Vt=l,Zt=D,Kt=w,Ut=o,Wt=N.CONFIGURABLE,Xt=Z,Gt=Rt.enforce,Qt=Rt.get,Jt=String,Yt=Object.defineProperty,en=qt("".slice),tn=qt("".replace),nn=qt([].join),sn=Ut&&!Vt((function(){return 8!==Yt((function(){}),"length",{value:8}).length})),rn=String(String).split("String"),an=c.exports=function(e,t,n){"Symbol("===en(Jt(t),0,7)&&(t="["+tn(Jt(t),/^Symbol\(([^)]*)\)/,"$1")+"]"),n&&n.getter&&(t="get "+t),n&&n.setter&&(t="set "+t),(!Kt(e,"name")||Wt&&e.name!==t)&&(Ut?Yt(e,"name",{value:t,configurable:!0}):e.name=t),sn&&n&&Kt(n,"arity")&&e.length!==n.arity&&Yt(e,"length",{value:n.arity});try{n&&Kt(n,"constructor")&&n.constructor?Ut&&Yt(e,"prototype",{writable:!1}):e.prototype&&(e.prototype=void 0)}catch(e){}var s=Gt(e);return Kt(s,"source")||(s.source=nn(rn,"string"==typeof t?t:"")),e};Function.prototype.toString=an((function(){return Zt(this)&&Qt(this).source||Xt(this)}),"toString");var ln=u,on=J,un=oe,cn=o,hn=function(e,t,n){return n.get&&ln(n.get,t,{getter:!0}),n.set&&ln(n.set,t,{setter:!0}),on.f(e,t,n)},pn=function(){var e=un(this),t="";return e.hasIndices&&(t+="d"),e.global&&(t+="g"),e.ignoreCase&&(t+="i"),e.multiline&&(t+="m"),e.dotAll&&(t+="s"),e.unicode&&(t+="u"),e.unicodeSets&&(t+="v"),e.sticky&&(t+="y"),t},dn=l,gn=a.RegExp,mn=gn.prototype;cn&&dn((function(){var e=!0;try{gn(".","d")}catch(t){e=!1}var t={},n="",s=e?"dgimsy":"gimsy",i=function(e,s){Object.defineProperty(t,e,{get:function(){return n+=s,!0}})},r={dotAll:"s",global:"g",ignoreCase:"i",multiline:"m",sticky:"y"};for(var a in e&&(r.hasIndices="d"),r)i(a,r[a]);return Object.getOwnPropertyDescriptor(mn,"flags").get.call(t)!==s||n!==s}))&&hn(mn,"flags",{configurable:!0,get:pn});const fn={ASCIIPunctuation:/[!"#$%&'()*+,\-./:;<=>?@[\]^_`{|}~\\]/,NotTriggerChar:/[^`_*[\]()<>!~]/,Scheme:/[A-Za-z][A-Za-z0-9+.-]{1,31}/,Email:/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*/,HTMLOpenTag://,HTMLCloseTag:/<\/HTMLTagName\s*>/,HTMLTagName:/[A-Za-z][A-Za-z0-9-]*/,HTMLComment://,HTMLPI:/<\?(?:|.|(?:[^?]|\?[^>])*)\?>/,HTMLDeclaration:/]*>/,HTMLCDATA://,HTMLAttribute:/\s+[A-Za-z_:][A-Za-z0-9_.:-]*(?:HTMLAttValue)?/,HTMLAttValue:/\s*=\s*(?:(?:'[^']*')|(?:"[^"]*")|(?:[^\s"'=<>`]+))/,KnownTag:/address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul/},Mn=new RegExp(/^(?:[!"#$%&'()*+,\-./:;<=>?@[\]\\^_`{|}~\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E42\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDF3C-\uDF3E]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B])/),Tn=new RegExp(/(?:[!"#$%&'()*+,\-./:;<=>?@[\]\\^_`{|}~\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E42\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDF3C-\uDF3E]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B])$/),Fn={TMH1:{regexp:/^( {0,3}#\s)(.*?)((?:\s+#+\s*)?)$/,replacement:'$1$$2$3'},TMH2:{regexp:/^( {0,3}##\s)(.*?)((?:\s+#+\s*)?)$/,replacement:'$1$$2$3'},TMH3:{regexp:/^( {0,3}###\s)(.*?)((?:\s+#+\s*)?)$/,replacement:'$1$$2$3'},TMH4:{regexp:/^( {0,3}####\s)(.*?)((?:\s+#+\s*)?)$/,replacement:'$1$$2$3'},TMH5:{regexp:/^( {0,3}#####\s)(.*?)((?:\s+#+\s*)?)$/,replacement:'$1$$2$3'},TMH6:{regexp:/^( {0,3}######\s)(.*?)((?:\s+#+\s*)?)$/,replacement:'$1$$2$3'},TMBlockquote:{regexp:/^( {0,3}>[ ]?)(.*)$/,replacement:'$1$$2'},TMCodeFenceBacktickOpen:{regexp:/^( {0,3}(?````*)\s*)([^`]*?)(\s*)$/,replacement:'$1$3$4'},TMCodeFenceTildeOpen:{regexp:/^( {0,3}(?~~~~*)\s*)(.*?)(\s*)$/,replacement:'$1$3$4'},TMCodeFenceBacktickClose:{regexp:/^( {0,3}(?````*))(\s*)$/,replacement:'$1$3'},TMCodeFenceTildeClose:{regexp:/^( {0,3}(?~~~~*))(\s*)$/,replacement:'$1$3'},TMBlankLine:{regexp:/^([ \t]*)$/,replacement:"$0"},TMSetextH1Marker:{regexp:/^ {0,3}=+\s*$/,replacement:'$0'},TMSetextH2Marker:{regexp:/^ {0,3}-+\s*$/,replacement:'$0'},TMHR:{regexp:/^( {0,3}(\*[ \t]*\*[ \t]*\*[ \t*]*)|(-[ \t]*-[ \t]*-[ \t-]*)|(_[ \t]*_[ \t]*_[ \t_]*))$/,replacement:'$0'},TMUL:{regexp:/^( {0,3}[+*-] {1,4})(.*)$/,replacement:'$1$$2'},TMOL:{regexp:/^( {0,3}\d{1,9}[.)] {1,4})(.*)$/,replacement:'$1$$2'},TMIndentedCode:{regexp:/^( {4}|\t)(.*)$/,replacement:'$1$2'},TMLinkReferenceDefinition:{regexp:/^( {0,3}\[\s*)([^\s\]](?:[^\]]|\\\])*?)(\s*\]:\s*)((?:[^\s<>]+)|(?:<(?:[^<>\\]|\\.)*>))?(\s*)((?:\((?:[^()\\]|\\.)*\))|(?:"(?:[^"\\]|\\.)*")|(?:'(?:[^'\\]|\\.)*'))?(\s*)$/,replacement:'$1$2$3$4$5$6$7',labelPlaceholder:2}};var Dn=[{start:/^ {0,3}<(?:script|pre|style)(?:\s|>|$)/i,end:/(?:<\/script>|<\/pre>|<\/style>)/i,paraInterrupt:!0},{start:/^ {0,3}/,paraInterrupt:!0},{start:/^ {0,3}<\?/,end:/\?>/,paraInterrupt:!0},{start:/^ {0,3}/,paraInterrupt:!0},{start:/^ {0,3}/,paraInterrupt:!0},{start:/^ {0,3}(?:<|<\/)(?:KnownTag)(?:\s|>|\/>|$)/i,end:!1,paraInterrupt:!0},{start:/^ {0,3}(?:HTMLOpenTag|HTMLCloseTag)\s*$/,end:!1,paraInterrupt:!1}],yn={escape:{regexp:/^\\(ASCIIPunctuation)/,replacement:'\\$1'},code:{regexp:/^(`+)((?:[^`])|(?:[^`].*?[^`]))(\1)/,replacement:'$1$2$3'},autolink:{regexp:/^<((?:Scheme:[^\s<>]*)|(?:Email))>/,replacement:'<$1>'},html:{regexp:/^((?:HTMLOpenTag)|(?:HTMLCloseTag)|(?:HTMLComment)|(?:HTMLPI)|(?:HTMLDeclaration)|(?:HTMLCDATA))/,replacement:'$1'},linkOpen:{regexp:/^\[/,replacement:""},imageOpen:{regexp:/^!\[/,replacement:""},linkLabel:{regexp:/^(\[\s*)([^\]]*?)(\s*\])/,replacement:"",labelPlaceholder:2},default:{regexp:/^(.|(?:NotTriggerChar+))/,replacement:"$1"}};const Cn=new RegExp(Object.keys(fn).join("|")),kn=[...Object.keys(yn)];for(let e of kn){let t=yn[e].regexp.source;for(;t.match(Cn);)t=t.replace(Cn,(e=>fn[e].source));yn[e].regexp=new RegExp(t,yn[e].regexp.flags)}for(let e of Dn){let t=e.start.source;for(;t.match(Cn);)t=t.replace(Cn,(e=>fn[e].source));e.start=new RegExp(t,e.start.flags)}function bn(e){return(e||"").replace(/&/g,"&").replace(//g,">")}const En={bold:{type:"inline",className:"TMStrong",set:{pre:"**",post:"**"},unset:{prePattern:/(?:\*\*|__)$/,postPattern:/^(?:\*\*|__)/}},italic:{type:"inline",className:"TMEm",set:{pre:"*",post:"*"},unset:{prePattern:/(?:\*|_)$/,postPattern:/^(?:\*|_)/}},code:{type:"inline",className:"TMCode",set:{pre:"`",post:"`"},unset:{prePattern:/`+$/,postPattern:/^`+/}},strikethrough:{type:"inline",className:"TMStrikethrough",set:{pre:"~~",post:"~~"},unset:{prePattern:/~~$/,postPattern:/^~~/}},h1:{type:"line",className:"TMH1",set:{pattern:/^( {0,3}(?:(?:#+|[0-9]{1,9}[).]|[>\-*+])\s+)?)(.*)$/,replacement:"# $2"},unset:{pattern:/^( {0,3}#\s+)(.*?)((?:\s+#+\s*)?)$/,replacement:"$2"}},h2:{type:"line",className:"TMH2",set:{pattern:/^( {0,3}(?:(?:#+|[0-9]{1,9}[).]|[>\-*+])\s+)?)(.*)$/,replacement:"## $2"},unset:{pattern:/^( {0,3}##\s+)(.*?)((?:\s+#+\s*)?)$/,replacement:"$2"}},h3:{type:"line",className:"TMH3",set:{pattern:/^( {0,3}(?:(?:#+|[0-9]{1,9}[).]|[>\-*+])\s+)?)(.*)$/,replacement:"### $2"},unset:{pattern:/^( {0,3}###\s+)(.*?)((?:\s+#+\s*)?)$/,replacement:"$2"}},h4:{type:"line",className:"TMH4",set:{pattern:/^( {0,3}(?:(?:#+|[0-9]{1,9}[).]|[>\-*+])\s+)?)(.*)$/,replacement:"#### $2"},unset:{pattern:/^( {0,3}####\s+)(.*?)((?:\s+#+\s*)?)$/,replacement:"$2"}},h5:{type:"line",className:"TMH5",set:{pattern:/^( {0,3}(?:(?:#+|[0-9]{1,9}[).]|[>\-*+])\s+)?)(.*)$/,replacement:"##### $2"},unset:{pattern:/^( {0,3}#####\s+)(.*?)((?:\s+#+\s*)?)$/,replacement:"$2"}},h6:{type:"line",className:"TMH6",set:{pattern:/^( {0,3}(?:(?:#+|[0-9]{1,9}[).]|[>\-*+])\s+)?)(.*)$/,replacement:"###### $2"},unset:{pattern:/^( {0,3}######\s+)(.*?)((?:\s+#+\s*)?)$/,replacement:"$2"}},ul:{type:"line",className:"TMUL",set:{pattern:/^( {0,3}(?:(?:#+|[0-9]{1,9}[).]|[>\-*+])\s+)?)(.*)$/,replacement:"- $2"},unset:{pattern:/^( {0,3}[+*-] {1,4})(.*)$/,replacement:"$2"}},ol:{type:"line",className:"TMOL",set:{pattern:/^( {0,3}(?:(?:#+|[0-9]{1,9}[).]|[>\-*+])\s+)?)(.*)$/,replacement:"$#. $2"},unset:{pattern:/^( {0,3}\d{1,9}[.)] {1,4})(.*)$/,replacement:"$2"}},blockquote:{type:"line",className:"TMBlockquote",set:{pattern:/^( {0,3}(?:(?:#+|[0-9]{1,9}[).]|[>\-*+])\s+)?)(.*)$/,replacement:"> $2"},unset:{pattern:/^( {0,3}>[ ]?)(.*)$/,replacement:"$2"}}};e.CommandBar=class{constructor(e){this.e=null,this.editor=null,this.commands=[],this.buttons={},this.state={},this.hotkeys=[];let t=e.element;t&&!t.tagName&&(t=document.getElementById(e.element)),t||(t=document.body),this.createCommandBarElement(t,e.commands||["bold","italic","strikethrough","|","code","|","h1","h2","|","ul","ol","|","blockquote","hr","|","insertLink","insertImage"]),document.addEventListener("keydown",(e=>this.handleKeydown(e))),e.editor&&this.setEditor(e.editor)}createCommandBarElement(e,t){this.e=document.createElement("div"),this.e.className="TMCommandBar";for(let e of t)if("|"==e){let e=document.createElement("div");e.className="TMCommandDivider",this.e.appendChild(e)}else{let t;if("string"==typeof e){if(!s[e])continue;t=e,this.commands[t]=s[t]}else{if("object"!=typeof e||!e.name)continue;t=e.name,this.commands[t]={},s[t]&&Object.assign(this.commands[t],s[t]),Object.assign(this.commands[t],e)}let i=this.commands[t].title||t;if(this.commands[t].hotkey){const e=this.commands[t].hotkey.split("-");let s=[],r=[];for(let t=0;tthis.handleClick(t,e))),this.e.appendChild(this.buttons[t])}e.appendChild(this.e)}handleClick(e,t){this.editor&&(t.preventDefault(),"string"==typeof this.commands[e].action?!1===this.state[e]?this.editor.setCommandState(e,!0):this.editor.setCommandState(e,!1):"function"==typeof this.commands[e].action&&this.commands[e].action(this.editor))}setEditor(e){this.editor=e,e.addEventListener("selection",(e=>this.handleSelection(e)))}handleSelection(e){if(e.commandState)for(let t in this.commands)void 0===e.commandState[t]?this.commands[t].enabled?this.state[t]=this.commands[t].enabled(this.editor,e.focus,e.anchor):this.state[t]=!e.focus&&null:this.state[t]=e.commandState[t],!0===this.state[t]?this.buttons[t].className="TMCommandButton TMCommandButton_Active":!1===this.state[t]?this.buttons[t].className="TMCommandButton TMCommandButton_Inactive":this.buttons[t].className="TMCommandButton TMCommandButton_Disabled"}handleKeydown(e){e:for(let t of this.hotkeys)if(t.key&&e.key.toLowerCase()==t.key||t.code&&e.code==t.code){for(let n of t.modifiers)if(!e[n])continue e;return void this.handleClick(t.command,e)}}},e.Editor=class{constructor(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.e=null,this.textarea=null,this.lines=[],this.lineElements=[],this.lineTypes=[],this.lineCaptures=[],this.lineReplacements=[],this.linkLabels=[],this.lineDirty=[],this.lastCommandState=null,this.listeners={change:[],selection:[]};let t=e.element;this.textarea=e.textarea,this.textarea&&(this.textarea.tagName||(this.textarea=document.getElementById(this.textarea)),t||(t=this.textarea)),t&&!t.tagName&&(t=document.getElementById(e.element)),t||(t=document.getElementsByTagName("body")[0]),"TEXTAREA"==t.tagName&&(this.textarea=t,t=this.textarea.parentNode),this.textarea&&(this.textarea.style.display="none"),this.createEditorElement(t),this.setContent("string"==typeof e.content?e.content:this.textarea?this.textarea.value:"# Hello TinyMDE!\nEdit **here**")}createEditorElement(e){this.e=document.createElement("div"),this.e.className="TinyMDE",this.e.contentEditable=!0,this.e.style.whiteSpace="pre-wrap",this.e.style.webkitUserModify="read-write-plaintext-only",this.textarea&&this.textarea.parentNode==e&&this.textarea.nextSibling?e.insertBefore(this.e,this.textarea.nextSibling):e.appendChild(this.e),this.e.addEventListener("input",(e=>this.handleInputEvent(e))),this.e.addEventListener("compositionend",(e=>this.handleInputEvent(e))),document.addEventListener("selectionchange",(e=>this.handleSelectionChangeEvent(e))),this.e.addEventListener("paste",(e=>this.handlePaste(e))),this.lineElements=this.e.childNodes}setContent(e){for(;this.e.firstChild;)this.e.removeChild(this.e.firstChild);this.lines=e.split(/(?:\r\n|\r|\n)/),this.lineDirty=[];for(let e=0;e"$"==n?bn(t[s]):`${this.processInlineStyles(t[s])}`))}applyLineTypes(){for(let e=0;e":t}this.lineElements[e].dataset.lineNum=e}}updateLineTypes(){let e=!1,t=0,n=!1;for(let s=0;s=t?(i="TMCodeFenceBacktickClose",a=Fn.TMCodeFenceBacktickClose.replacement,r=n,e=!1):(i="TMFencedCodeBacktick",a='$0
',r=[this.lines[s]])}else if("TMCodeFenceTildeOpen"==e){let n=Fn.TMCodeFenceTildeClose.regexp.exec(this.lines[s]);n&&n.groups.seq.length>=t?(i="TMCodeFenceTildeClose",a=Fn.TMCodeFenceTildeClose.replacement,r=n,e=!1):(i="TMFencedCodeTilde",a='$0
',r=[this.lines[s]])}if("TMPara"==i&&!1===n)for(let e of Dn)if(this.lines[s].match(e.start)&&(e.paraInterrupt||0==s||"TMPara"!=this.lineTypes[s-1]&&"TMUL"!=this.lineTypes[s-1]&&"TMOL"!=this.lineTypes[s-1]&&"TMBlockquote"!=this.lineTypes[s-1])){n=e;break}if(!1!==n&&(i="TMHTMLBlock",a='$0
',r=[this.lines[s]],n.end?this.lines[s].match(n.end)&&(n=!1):(s==this.lines.length-1||this.lines[s+1].match(Fn.TMBlankLine.regexp))&&(n=!1)),"TMPara"==i)for(let e in Fn)if(Fn[e].regexp){let t=Fn[e].regexp.exec(this.lines[s]);if(t){i=e,a=Fn[e].replacement,r=t;break}}if("TMCodeFenceBacktickOpen"!=i&&"TMCodeFenceTildeOpen"!=i||(e=i,t=r.groups.seq.length),"TMIndentedCode"!=i&&"TMLinkReferenceDefinition"!=i||!(s>0)||"TMPara"!=this.lineTypes[s-1]&&"TMUL"!=this.lineTypes[s-1]&&"TMOL"!=this.lineTypes[s-1]&&"TMBlockquote"!=this.lineTypes[s-1]||(i="TMPara",r=[this.lines[s]],a="$$0"),"TMSetextH2Marker"==i){let e=Fn.TMUL.regexp.exec(this.lines[s]);e&&(i="TMUL",a=Fn.TMUL.replacement,r=e)}if("TMSetextH1Marker"==i||"TMSetextH2Marker"==i)if(0==s||"TMPara"!=this.lineTypes[s-1]){let e=Fn.TMHR.regexp.exec(this.lines[s]);e?(i="TMHR",r=e,a=Fn.TMHR.replacement):(i="TMPara",r=[this.lines[s]],a="$$0")}else{let e=s-1;const t="TMSetextH1Marker"==i?"TMSetextH1":"TMSetextH2";do{this.lineTypes[t]!=t&&(this.lineTypes[e]=t,this.lineDirty[t]=!0),this.lineReplacements[e]="$$0",this.lineCaptures[e]=[this.lines[e]],e--}while(e>=0&&"TMPara"==this.lineTypes[e])}this.lineTypes[s]!=i&&(this.lineTypes[s]=i,this.lineDirty[s]=!0),this.lineReplacements[s]=a,this.lineCaptures[s]=r}}updateLineContentsAndFormatting(){this.clearDirtyFlag(),this.updateLineContents(),this.updateFormatting()}parseLinkOrImage(e,t){let n=t?2:1,s=e.substr(0,n),i=t?"TMImage":"TMLink",r=n,a=1,l=!1,o=!1,u=[],c=[];e:for(;r0;){let n=e.substr(r),s=/^\s+/.exec(n);if(s){switch(c.length){case 0:case 1:c.push(s[0]);break;case 2:if(c[0].match(//))if(s=/^["']/.exec(n),!s||0!=c.length&&1!=c.length&&4!=c.length)if(!s||5!=c.length&&6!=c.length||c[4]!=s[0])if(n.match(/^\(/)){switch(c.length){case 0:c.push("");case 1:c.push("");case 2:c[1]=c[1].concat("("),c[0].match(/<$/)||t++;break;case 3:c.push("");case 4:c.push("(");break;case 5:c.push("");case 6:if("("==c[4])return!1;c[5]=c[5].concat("(");break;default:return!1}r++}else if(n.match(/^\)/)){if(c.length<=2){for(;c.length<2;)c.push("");c[0].match(/<$/)||t--,t>0&&(c[1]=c[1].concat(")"))}else 5==c.length||6==c.length?"("==c[4]?(5==c.length&&c.push(""),c.push(")")):5==c.length?c.push(")"):c[5]=c[5].concat(")"):t--;if(0==t)for(;c.length<7;)c.push("");r++}else{if(s=/^./.exec(n),!s)throw"Infinite loop";switch(c.length){case 0:c.push("");case 1:c.push(s[0]);break;case 2:c[1]=c[1].concat(s[0]);break;case 3:case 4:return!1;case 5:c.push("");case 6:c[5]=c[5].concat(s[0]);break;default:return!1}r+=s[0].length}else 5==c.length&&c.push(""),c.push(s[0]),r++;else{for(;c.length<4;)c.push("");c.push(s[0]),r++}else 1==c.length&&c.push(""),c.push(">"),r++}if(t>0)return!1}if(!1!==o){let e=!1;for(let t of this.linkLabels)if(t==o){e=!0;break}let t=e?"TMLinkLabel TMLinkLabel_Valid":"TMLinkLabel TMLinkLabel_Invalid",n=`${s}${this.processInlineStyles(l)}]`;return u.length>=3&&(n=n.concat(`${u[0]}`,`${u[1]}`,`${u[2]}`)),{output:n,charCount:r}}if(c){for(;c.length<7;)c.push("");return{output:`${s}${this.processInlineStyles(l)}](${c[0]}${c[1]}${c[2]}${c[3]}${c[4]}${c[5]}${c[6]})`,charCount:r}}return!1}processInlineStyles(e){let t="",n=[],s=0,i=e;e:for(;i;){for(let e of["escape","code","autolink","html"]){let n=yn[e].regexp.exec(i);if(n){i=i.substr(n[0].length),s+=n[0].length,t+=yn[e].replacement.replace(/\$([1-9])/g,((e,t)=>bn(n[t])));continue e}}let r=i.match(yn.linkOpen.regexp),a=i.match(yn.imageOpen.regexp);if(a||r){let e=this.parseLinkOrImage(i,a);if(e){t=`${t}${e.output}`,i=i.substr(e.charCount),s+=e.charCount;continue e}}let l=/(^\*+)|(^_+)/.exec(i);if(l){let r=l[0].length;const a=l[0],o=l[0][0];i=i.substr(l[0].length);const u=s>0?e.substr(0,s):" ",c=s+l[0].length=0;)if(n[e].delimiter==o){for(;e=2&&n[e].count>=2?(t=`${o}${o}${t}${o}${o}`,r-=2,n[e].count-=2):(t=`${o}${t}${o}`,r-=1,n[e].count-=1),0==n[e].count){t=`${n.pop().output}${t}`,e--}}else e--}r&&m&&(n.push({delimiter:o,delimString:a,count:r,output:t}),t="",r=0),r&&(t=`${t}${a.substr(0,r)}`),s+=l[0].length}else if(l=/^~~/.exec(i),l){let e=!1,r=n.length-1;for(;!e&&r>=0;)if("~"==n[r].delimiter){for(;r~~${t}~~`,t=`${n.pop().output}${t}`,e=!0}else r--;e||(n.push({delimiter:"~",delimString:"~~",count:2,output:t}),t=""),s+=l[0].length,i=i.substr(l[0].length)}else{if(l=yn.default.regexp.exec(i),!l)throw"Infinite loop!";i=i.substr(l[0].length),s+=l[0].length,t+=yn.default.replacement.replace(/\$([1-9])/g,((e,t)=>bn(l[t])))}}for(;n.length;){const e=n.pop();t=`${e.output}${e.delimString.substr(0,e.count)}${t}`}return t}clearDirtyFlag(){this.lineDirty=new Array(this.lines.length);for(let e=0;e0?e.row:e.row-1;switch(this.lineTypes[n]){case"TMUL":t="TMUL";break;case"TMOL":t="TMOL";break;case"TMIndentedCode":t="TMIndentedCode"}let s=this.lines[e.row].replace(/\n\n$/,"\n").split(/(?:\r\n|\n|\r)/);if(1!=s.length){if(this.spliceLines(e.row,1,s,!0),e.row++,e.col=0,t){let n=Fn[t].regexp.exec(this.lines[e.row-1]);n&&(n[2]?("TMOL"==t&&(n[1]=n[1].replace(/\d{1,9}/,(e=>parseInt(e[0])+1))),this.lines[e.row]=`${n[1]}${this.lines[e.row]}`,this.lineDirty[e.row]=!0,e.col=n[1].length):(this.lines[e.row-1]="",this.lineDirty[e.row-1]=!0))}this.updateFormatting()}else this.updateFormatting()}getSelection(){let e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];const t=window.getSelection();let n=e?t.anchorNode:t.focusNode;if(!n)return null;let s=e?t.anchorOffset:t.focusOffset;if(n==this.e)return s0&&(s=e.childNodes[t-1],n=s.textContent.length);s.parentNode!=this.e;)s.previousSibling?(s=s.previousSibling,n+=s.textContent.length):s=s.parentNode;return n}computeNodeAndOffset(e,t){let n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];e>=this.lineElements.length&&(e=this.lineElements.length-1,t=this.lines[e].length),t>this.lines[e].length&&(t=this.lines[e].length);const s=this.lineElements[e];let i=s.firstChild,r=!1,a={node:s.firstChild?s.firstChild:s,offset:0};for(;i!=s;){if(!r&&i.nodeType===Node.TEXT_NODE)if(i.nodeValue.length>=t){if(!n||i.nodeValue.length!=t)return{node:i,offset:t};a={node:i,offset:t},t=0}else t-=i.nodeValue.length;!r&&i.firstChild?i=i.firstChild:i.nextSibling?(r=!1,i=i.nextSibling):(r=!0,i=i.parentNode)}return a}setSelection(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;if(!e)return;let n=document.createRange(),{node:s,offset:i}=this.computeNodeAndOffset(e.row,e.col,t&&t.row==e.row&&t.col>e.col),r=null,a=null;if(t&&(t.row!=e.row||t.col!=e.col)){let{node:n,offset:s}=this.computeNodeAndOffset(t.row,t.col,e.row==t.row&&e.col>t.col);r=n,a=s}r?n.setStart(r,a):n.setStart(s,i),n.setEnd(s,i);let l=window.getSelection();l.removeAllRanges(),l.addRange(n)}handleInputEvent(e){if("insertCompositionText"==e.inputType)return;let t=this.getSelection();"insertParagraph"!=e.inputType&&"insertLineBreak"!=e.inputType||!t?(this.e.firstChild?this.fixNodeHierarchy():this.e.innerHTML='

',this.updateLineContentsAndFormatting()):(this.clearDirtyFlag(),this.processNewParagraph(t)),t&&this.setSelection(t),this.fireChange()}fixNodeHierarchy(){const e=Array.from(this.e.childNodes),t=function(e){const t=e.parentElement,n=e.nextSibling;t.removeChild(e);for(var s=arguments.length,i=new Array(s>1?s-1:0),r=1;rn?t.insertBefore(e,n):t.appendChild(e)))};e.forEach((e=>{if(e.nodeType!==Node.ELEMENT_NODE||"DIV"!==e.tagName){const n=document.createElement("div");t(e,n),n.appendChild(e)}else if(0==e.childNodes.length)e.appendChild(document.createElement("br"));else{const n=Array.from(e.childNodes);if(n.some((e=>e.nodeType===Node.ELEMENT_NODE&&"DIV"===e.tagName)))return t(e,n)}}))}handleSelectionChangeEvent(){this.fireSelection()}spliceLines(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],s=!(arguments.length>3&&void 0!==arguments[3])||arguments[3];if(s)for(let n=0;n1&&void 0!==arguments[1]?arguments[1]:null,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;s||(s=this.getSelection(!0)),i||(i=this.getSelection(!1)),i||(i={row:this.lines.length-1,col:this.lines[this.lines.length-1].length}),s||(s=i),s.row{let t=[];for(;e;)t.unshift(e),e=e.parentNode;return t},s=n(e),i=n(t);if(s[0]!=i[0])return null;let r;for(r=0;s[r]==i[r];r++);return s[r-1]}computeEnclosingMarkupNode(e,t,n){let s=null;if(!e)return null;if(t){if(e.row!=t.row)return null;s=this.computeCommonAncestor(e.node,t.node)}else s=e.node;if(!s)return null;for(;s!=this.e;){if(s.className&&s.className.includes(n))return s;s=s.parentNode}return null}getCommandState(){let e,t,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,i={};if(n||(n=this.getSelection(!1)),s||(s=this.getSelection(!0)),!n){for(let e in En)i[e]=null;return i}s||(s=n),s.rowe.row&&0==t.col&&(t.row--,t.col=this.lines[t.row].length);for(let r in En)if("inline"==En[r].type&&(n&&n.row==s.row&&this.isInlineFormattingAllowed(n,s)?i[r]=!!this.computeEnclosingMarkupNode(n,s,En[r].className)||n.col==s.col&&!!this.lines[n.row].substr(0,n.col).match(En[r].unset.prePattern)&&!!this.lines[n.row].substr(n.col).match(En[r].unset.postPattern):i[r]=null),"line"==En[r].type)if(n){let n=this.lineTypes[e.row]==En[r].className;for(let s=e.row;s<=t.row;s++)if(this.lineTypes[s]==En[r].className!=n){n=null;break}i[r]=n}else i[r]=null;return i}setCommandState(e,t){if("inline"==En[e].type){let t=this.getSelection(!0),n=this.getSelection(!1);if(t||(t=n),!t)return;if(t.row!=n.row)return;if(!this.isInlineFormattingAllowed(n,t))return;let s=this.computeEnclosingMarkupNode(n,t,En[e].className);if(this.clearDirtyFlag(),s){this.lineDirty[n.row]=!0;const i=this.computeColumn(s,0),r=s.textContent.length,a=this.lines[n.row].substr(0,i).replace(En[e].unset.prePattern,""),l=this.lines[n.row].substr(i,r),o=this.lines[n.row].substr(i+r).replace(En[e].unset.postPattern,"");this.lines[n.row]=a.concat(l,o),t.col=a.length,n.col=t.col+r,this.updateFormatting(),this.setSelection(n,t),this.fireChange()}else if(n.col==t.col&&this.lines[n.row].substr(0,n.col).match(En[e].unset.prePattern)&&this.lines[n.row].substr(n.col).match(En[e].unset.postPattern)){this.lineDirty[n.row]=!0;const s=this.lines[n.row].substr(0,n.col).replace(En[e].unset.prePattern,""),i=this.lines[n.row].substr(n.col).replace(En[e].unset.postPattern,"");this.lines[n.row]=s.concat(i),n.col=t.col=s.length,this.updateFormatting(),this.setSelection(n,t),this.fireChange()}else{let{startCol:s,endCol:i}=n.col\s*).*\S(?\s*)$/);r&&(s+=r.groups.leading.length,i-=r.groups.trailing.length),n.col=s,t.col=i,this.wrapSelection(En[e].set.pre,En[e].set.post,n,t),this.fireChange()}}else if("line"==En[e].type){let n=this.getSelection(!0),s=this.getSelection(!1);if(n||(n=s),!s)return;this.clearDirtyFlag();let i=n.row>s.row?s:n,r=n.row>s.row?n:s;r.row>i.row&&0==r.col&&r.row--;for(let n=i.row;n<=r.row;n++)t&&this.lineTypes[n]!=En[e].className&&(this.lines[n]=this.lines[n].replace(En[e].set.pattern,En[e].set.replacement.replace("$#",n-i.row+1)),this.lineDirty[n]=!0),t||this.lineTypes[n]!=En[e].className||(this.lines[n]=this.lines[n].replace(En[e].unset.pattern,En[e].unset.replacement),this.lineDirty[n]=!0);this.updateFormatting(),this.setSelection({row:r.row,col:this.lines[r.row].length},{row:i.row,col:0}),this.fireChange()}}isInlineFormattingAllowed(){const e=window.getSelection();if(!e||!e.focusNode||!e.anchorNode)return!1;if(e.isCollapsed&&3==e.focusNode.nodeType&&e.focusOffset==e.focusNode.nodeValue.length){let t;for(t=e.focusNode;t&&null==t.nextSibling;t=t.parentNode);if(t&&t.nextSibling.className&&t.nextSibling.className.includes("TMInlineFormatted"))return!0}let t=this.computeCommonAncestor(e.focusNode,e.anchorNode);if(!t)return!1;for(;t&&t!=this.e;){if(t.className&&"function"==typeof t.className.includes&&(t.className.includes("TMInlineFormatted")||t.className.includes("TMBlankLine")))return!0;t=t.parentNode}return!1}wrapSelection(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,s=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;if(n||(n=this.getSelection(!1)),s||(s=this.getSelection(!0)),!n||!s||n.row!=s.row)return;this.lineDirty[n.row]=!0;const i=n.col + + + + + diff --git a/views/new_twt.php b/views/new_twt.php index bac375a..36dce65 100644 --- a/views/new_twt.php +++ b/views/new_twt.php @@ -2,6 +2,7 @@ // TODO: Give a warning if the file is not found $config = parse_ini_file('private/config.ini'); + if ($config['debug_mode']) { ini_set('display_errors', 1); ini_set('display_startup_errors', 1); @@ -151,12 +152,27 @@ if (isset($_GET['hash'])) {
+
+ + +
From cc7237013ce1deecd08af0612ab9efe1670a8d1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?s=C3=B8renpeter?= Date: Thu, 28 Nov 2024 13:22:30 +0100 Subject: [PATCH 2/6] changed CSS for tinyMDE --- libs/tiny-mde.css | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libs/tiny-mde.css b/libs/tiny-mde.css index ce8f9b9..9a8925d 100644 --- a/libs/tiny-mde.css +++ b/libs/tiny-mde.css @@ -16,9 +16,9 @@ margin: 0.5rem 0; border-radius: var(--standard-border-radius); min-height: 6rem; - background-color: #222; /* darch.dk only */ } -/* + +/* .TMBlankLine { height:24px; } @@ -47,13 +47,13 @@ */ .TMMark_TMCode { - font-family:monospace; - font-size:.9em; + font-family: monospace; + font-size: 0.9em; } .TMFencedCodeBacktick, .TMFencedCodeTilde, .TMIndentedCode, .TMCode { - font-family:monospace; - font-size:.9em; + font-family: monospace; + font-size: 0.9em; background-color: var(--accent-bg); } From e1f6072b314ce4718651ba95717715f94e0e29a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?s=C3=B8renpeter?= Date: Sat, 30 Nov 2024 14:47:46 +0100 Subject: [PATCH 3/6] Embeds youtube videos --- libs/timeline.css | 5 +++++ libs/twtxt.php | 33 +++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/libs/timeline.css b/libs/timeline.css index 6107483..c6a39df 100644 --- a/libs/timeline.css +++ b/libs/timeline.css @@ -141,6 +141,11 @@ img.avatar { border: none; } +.embed-video { + width: 100%; + aspect-ratio: 16/9; +} + a.author { text-decoration: none; color: var(--text); diff --git a/libs/twtxt.php b/libs/twtxt.php index 64631e1..4b2068e 100644 --- a/libs/twtxt.php +++ b/libs/twtxt.php @@ -180,8 +180,9 @@ function replaceLinksFromTwt(string $twtString) { // 1. Look into how yarnd handles this // Regular expression pattern to match URLs - $pattern = '/(?)(?\s]+)/is'; + // Replace URLs with clickable links $replacement = '$1'; $result = preg_replace($pattern, $replacement, $twtString); @@ -216,6 +217,29 @@ function replaceTagsFromTwt(string $twtString) { return $result; } +function embedYoutubeFromTwt(string $twtString) { + + // original regex source: https://gist.github.com/afeld/1254889#gistcomment-1253992 + $pattern = '/(?:youtube(?:-nocookie)?\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/mi'; + + if(preg_match_all($pattern, $twtString, $youtubeLinks)) { + + $youtubeLinks = array_unique($youtubeLinks[1]); // Remove dublicate cause by raw URLs conceverter to links + + //echo "
";
+		//print_r($youtubeLinks);
+		//echo "
"; + + foreach ($youtubeLinks as $videoID) { + $twtString .= '
'; + } + } + + $result = $twtString; + + return $result; +} + function getTimeElapsedString($timestamp, $full = false) { $now = new DateTime; @@ -407,11 +431,12 @@ function getTwtsFromTwtxtString($url) { // For some reason I was having trouble finding this nomenclature // that's why I leave the UTF-8 representation for future reference $twtContent = str_replace("\u{2028}", "\n
\n", $twtContent); - + $twtContent = replaceMarkdownLinksFromTwt($twtContent); $twtContent = replaceImagesFromTwt($twtContent); //$twtContent = Slimdown::render($twtContent); - $twtContent = replaceLinksFromTwt($twtContent); // TODO: + $twtContent = embedYoutubeFromTwt($twtContent); // TODO: Find the right order to embed youtube, so we don't get two video due to links containing URL as link texts + $twtContent = replaceLinksFromTwt($twtContent); // TODO // Get and remove the hash $hash = getReplyHashFromTwt($twtContent); From f9f09345708e8e08b76960fd2d3599ef2db3491f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?s=C3=B8renpeter?= Date: Sat, 30 Nov 2024 16:11:33 +0100 Subject: [PATCH 4/6] Replace Slimdown.php with Parsedown.php, so markdown now work --- libs/Parsedown.php | 1994 ++++++++++++++++++++++++++++++++++++++++++++ libs/Slimdown.php | 155 ---- libs/_Slimdown.php | 156 ---- libs/timeline.css | 35 +- libs/twtxt.php | 17 +- partials/base.php | 1 + 6 files changed, 2034 insertions(+), 324 deletions(-) create mode 100644 libs/Parsedown.php delete mode 100644 libs/Slimdown.php delete mode 100644 libs/_Slimdown.php diff --git a/libs/Parsedown.php b/libs/Parsedown.php new file mode 100644 index 0000000..38edfe9 --- /dev/null +++ b/libs/Parsedown.php @@ -0,0 +1,1994 @@ +textElements($text); + + # convert to markup + $markup = $this->elements($Elements); + + # trim line breaks + $markup = trim($markup, "\n"); + + return $markup; + } + + protected function textElements($text) + { + # make sure no definitions are set + $this->DefinitionData = array(); + + # standardize line breaks + $text = str_replace(array("\r\n", "\r"), "\n", $text); + + # remove surrounding line breaks + $text = trim($text, "\n"); + + # split text into lines + $lines = explode("\n", $text); + + # iterate through lines to identify blocks + return $this->linesElements($lines); + } + + # + # Setters + # + + function setBreaksEnabled($breaksEnabled) + { + $this->breaksEnabled = $breaksEnabled; + + return $this; + } + + protected $breaksEnabled; + + function setMarkupEscaped($markupEscaped) + { + $this->markupEscaped = $markupEscaped; + + return $this; + } + + protected $markupEscaped; + + function setUrlsLinked($urlsLinked) + { + $this->urlsLinked = $urlsLinked; + + return $this; + } + + protected $urlsLinked = true; + + function setSafeMode($safeMode) + { + $this->safeMode = (bool) $safeMode; + + return $this; + } + + protected $safeMode; + + function setStrictMode($strictMode) + { + $this->strictMode = (bool) $strictMode; + + return $this; + } + + protected $strictMode; + + protected $safeLinksWhitelist = array( + 'http://', + 'https://', + 'ftp://', + 'ftps://', + 'mailto:', + 'tel:', + 'data:image/png;base64,', + 'data:image/gif;base64,', + 'data:image/jpeg;base64,', + 'irc:', + 'ircs:', + 'git:', + 'ssh:', + 'news:', + 'steam:', + ); + + # + # Lines + # + + protected $BlockTypes = array( + '#' => array('Header'), + '*' => array('Rule', 'List'), + '+' => array('List'), + '-' => array('SetextHeader', 'Table', 'Rule', 'List'), + '0' => array('List'), + '1' => array('List'), + '2' => array('List'), + '3' => array('List'), + '4' => array('List'), + '5' => array('List'), + '6' => array('List'), + '7' => array('List'), + '8' => array('List'), + '9' => array('List'), + ':' => array('Table'), + '<' => array('Comment', 'Markup'), + '=' => array('SetextHeader'), + '>' => array('Quote'), + '[' => array('Reference'), + '_' => array('Rule'), + '`' => array('FencedCode'), + '|' => array('Table'), + '~' => array('FencedCode'), + ); + + # ~ + + protected $unmarkedBlockTypes = array( + 'Code', + ); + + # + # Blocks + # + + protected function lines(array $lines) + { + return $this->elements($this->linesElements($lines)); + } + + protected function linesElements(array $lines) + { + $Elements = array(); + $CurrentBlock = null; + + foreach ($lines as $line) + { + if (chop($line) === '') + { + if (isset($CurrentBlock)) + { + $CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted']) + ? $CurrentBlock['interrupted'] + 1 : 1 + ); + } + + continue; + } + + while (($beforeTab = strstr($line, "\t", true)) !== false) + { + $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4; + + $line = $beforeTab + . str_repeat(' ', $shortage) + . substr($line, strlen($beforeTab) + 1) + ; + } + + $indent = strspn($line, ' '); + + $text = $indent > 0 ? substr($line, $indent) : $line; + + # ~ + + $Line = array('body' => $line, 'indent' => $indent, 'text' => $text); + + # ~ + + if (isset($CurrentBlock['continuable'])) + { + $methodName = 'block' . $CurrentBlock['type'] . 'Continue'; + $Block = $this->$methodName($Line, $CurrentBlock); + + if (isset($Block)) + { + $CurrentBlock = $Block; + + continue; + } + else + { + if ($this->isBlockCompletable($CurrentBlock['type'])) + { + $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; + $CurrentBlock = $this->$methodName($CurrentBlock); + } + } + } + + # ~ + + $marker = $text[0]; + + # ~ + + $blockTypes = $this->unmarkedBlockTypes; + + if (isset($this->BlockTypes[$marker])) + { + foreach ($this->BlockTypes[$marker] as $blockType) + { + $blockTypes []= $blockType; + } + } + + # + # ~ + + foreach ($blockTypes as $blockType) + { + $Block = $this->{"block$blockType"}($Line, $CurrentBlock); + + if (isset($Block)) + { + $Block['type'] = $blockType; + + if ( ! isset($Block['identified'])) + { + if (isset($CurrentBlock)) + { + $Elements[] = $this->extractElement($CurrentBlock); + } + + $Block['identified'] = true; + } + + if ($this->isBlockContinuable($blockType)) + { + $Block['continuable'] = true; + } + + $CurrentBlock = $Block; + + continue 2; + } + } + + # ~ + + if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph') + { + $Block = $this->paragraphContinue($Line, $CurrentBlock); + } + + if (isset($Block)) + { + $CurrentBlock = $Block; + } + else + { + if (isset($CurrentBlock)) + { + $Elements[] = $this->extractElement($CurrentBlock); + } + + $CurrentBlock = $this->paragraph($Line); + + $CurrentBlock['identified'] = true; + } + } + + # ~ + + if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) + { + $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; + $CurrentBlock = $this->$methodName($CurrentBlock); + } + + # ~ + + if (isset($CurrentBlock)) + { + $Elements[] = $this->extractElement($CurrentBlock); + } + + # ~ + + return $Elements; + } + + protected function extractElement(array $Component) + { + if ( ! isset($Component['element'])) + { + if (isset($Component['markup'])) + { + $Component['element'] = array('rawHtml' => $Component['markup']); + } + elseif (isset($Component['hidden'])) + { + $Component['element'] = array(); + } + } + + return $Component['element']; + } + + protected function isBlockContinuable($Type) + { + return method_exists($this, 'block' . $Type . 'Continue'); + } + + protected function isBlockCompletable($Type) + { + return method_exists($this, 'block' . $Type . 'Complete'); + } + + # + # Code + + protected function blockCode($Line, $Block = null) + { + if (isset($Block) and $Block['type'] === 'Paragraph' and ! isset($Block['interrupted'])) + { + return; + } + + if ($Line['indent'] >= 4) + { + $text = substr($Line['body'], 4); + + $Block = array( + 'element' => array( + 'name' => 'pre', + 'element' => array( + 'name' => 'code', + 'text' => $text, + ), + ), + ); + + return $Block; + } + } + + protected function blockCodeContinue($Line, $Block) + { + if ($Line['indent'] >= 4) + { + if (isset($Block['interrupted'])) + { + $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']); + + unset($Block['interrupted']); + } + + $Block['element']['element']['text'] .= "\n"; + + $text = substr($Line['body'], 4); + + $Block['element']['element']['text'] .= $text; + + return $Block; + } + } + + protected function blockCodeComplete($Block) + { + return $Block; + } + + # + # Comment + + protected function blockComment($Line) + { + if ($this->markupEscaped or $this->safeMode) + { + return; + } + + if (strpos($Line['text'], '') !== false) + { + $Block['closed'] = true; + } + + return $Block; + } + } + + protected function blockCommentContinue($Line, array $Block) + { + if (isset($Block['closed'])) + { + return; + } + + $Block['element']['rawHtml'] .= "\n" . $Line['body']; + + if (strpos($Line['text'], '-->') !== false) + { + $Block['closed'] = true; + } + + return $Block; + } + + # + # Fenced Code + + protected function blockFencedCode($Line) + { + $marker = $Line['text'][0]; + + $openerLength = strspn($Line['text'], $marker); + + if ($openerLength < 3) + { + return; + } + + $infostring = trim(substr($Line['text'], $openerLength), "\t "); + + if (strpos($infostring, '`') !== false) + { + return; + } + + $Element = array( + 'name' => 'code', + 'text' => '', + ); + + if ($infostring !== '') + { + /** + * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes + * Every HTML element may have a class attribute specified. + * The attribute, if specified, must have a value that is a set + * of space-separated tokens representing the various classes + * that the element belongs to. + * [...] + * The space characters, for the purposes of this specification, + * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab), + * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and + * U+000D CARRIAGE RETURN (CR). + */ + $language = substr($infostring, 0, strcspn($infostring, " \t\n\f\r")); + + $Element['attributes'] = array('class' => "language-$language"); + } + + $Block = array( + 'char' => $marker, + 'openerLength' => $openerLength, + 'element' => array( + 'name' => 'pre', + 'element' => $Element, + ), + ); + + return $Block; + } + + protected function blockFencedCodeContinue($Line, $Block) + { + if (isset($Block['complete'])) + { + return; + } + + if (isset($Block['interrupted'])) + { + $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']); + + unset($Block['interrupted']); + } + + if (($len = strspn($Line['text'], $Block['char'])) >= $Block['openerLength'] + and chop(substr($Line['text'], $len), ' ') === '' + ) { + $Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1); + + $Block['complete'] = true; + + return $Block; + } + + $Block['element']['element']['text'] .= "\n" . $Line['body']; + + return $Block; + } + + protected function blockFencedCodeComplete($Block) + { + return $Block; + } + + # + # Header + + protected function blockHeader($Line) + { + $level = strspn($Line['text'], '#'); + + if ($level > 6) + { + return; + } + + $text = trim($Line['text'], '#'); + + if ($this->strictMode and isset($text[0]) and $text[0] !== ' ') + { + return; + } + + $text = trim($text, ' '); + + $Block = array( + 'element' => array( + 'name' => 'h' . $level, + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $text, + 'destination' => 'elements', + ) + ), + ); + + return $Block; + } + + # + # List + + protected function blockList($Line, ?array $CurrentBlock = null) + { + list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}+[.\)]'); + + if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches)) + { + $contentIndent = strlen($matches[2]); + + if ($contentIndent >= 5) + { + $contentIndent -= 1; + $matches[1] = substr($matches[1], 0, -$contentIndent); + $matches[3] = str_repeat(' ', $contentIndent) . $matches[3]; + } + elseif ($contentIndent === 0) + { + $matches[1] .= ' '; + } + + $markerWithoutWhitespace = strstr($matches[1], ' ', true); + + $Block = array( + 'indent' => $Line['indent'], + 'pattern' => $pattern, + 'data' => array( + 'type' => $name, + 'marker' => $matches[1], + 'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)), + ), + 'element' => array( + 'name' => $name, + 'elements' => array(), + ), + ); + $Block['data']['markerTypeRegex'] = preg_quote($Block['data']['markerType'], '/'); + + if ($name === 'ol') + { + $listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0'; + + if ($listStart !== '1') + { + if ( + isset($CurrentBlock) + and $CurrentBlock['type'] === 'Paragraph' + and ! isset($CurrentBlock['interrupted']) + ) { + return; + } + + $Block['element']['attributes'] = array('start' => $listStart); + } + } + + $Block['li'] = array( + 'name' => 'li', + 'handler' => array( + 'function' => 'li', + 'argument' => !empty($matches[3]) ? array($matches[3]) : array(), + 'destination' => 'elements' + ) + ); + + $Block['element']['elements'] []= & $Block['li']; + + return $Block; + } + } + + protected function blockListContinue($Line, array $Block) + { + if (isset($Block['interrupted']) and empty($Block['li']['handler']['argument'])) + { + return null; + } + + $requiredIndent = ($Block['indent'] + strlen($Block['data']['marker'])); + + if ($Line['indent'] < $requiredIndent + and ( + ( + $Block['data']['type'] === 'ol' + and preg_match('/^[0-9]++'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches) + ) or ( + $Block['data']['type'] === 'ul' + and preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches) + ) + ) + ) { + if (isset($Block['interrupted'])) + { + $Block['li']['handler']['argument'] []= ''; + + $Block['loose'] = true; + + unset($Block['interrupted']); + } + + unset($Block['li']); + + $text = isset($matches[1]) ? $matches[1] : ''; + + $Block['indent'] = $Line['indent']; + + $Block['li'] = array( + 'name' => 'li', + 'handler' => array( + 'function' => 'li', + 'argument' => array($text), + 'destination' => 'elements' + ) + ); + + $Block['element']['elements'] []= & $Block['li']; + + return $Block; + } + elseif ($Line['indent'] < $requiredIndent and $this->blockList($Line)) + { + return null; + } + + if ($Line['text'][0] === '[' and $this->blockReference($Line)) + { + return $Block; + } + + if ($Line['indent'] >= $requiredIndent) + { + if (isset($Block['interrupted'])) + { + $Block['li']['handler']['argument'] []= ''; + + $Block['loose'] = true; + + unset($Block['interrupted']); + } + + $text = substr($Line['body'], $requiredIndent); + + $Block['li']['handler']['argument'] []= $text; + + return $Block; + } + + if ( ! isset($Block['interrupted'])) + { + $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']); + + $Block['li']['handler']['argument'] []= $text; + + return $Block; + } + } + + protected function blockListComplete(array $Block) + { + if (isset($Block['loose'])) + { + foreach ($Block['element']['elements'] as &$li) + { + if (end($li['handler']['argument']) !== '') + { + $li['handler']['argument'] []= ''; + } + } + } + + return $Block; + } + + # + # Quote + + protected function blockQuote($Line) + { + if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches)) + { + $Block = array( + 'element' => array( + 'name' => 'blockquote', + 'handler' => array( + 'function' => 'linesElements', + 'argument' => (array) $matches[1], + 'destination' => 'elements', + ) + ), + ); + + return $Block; + } + } + + protected function blockQuoteContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + if ($Line['text'][0] === '>' and preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches)) + { + $Block['element']['handler']['argument'] []= $matches[1]; + + return $Block; + } + + if ( ! isset($Block['interrupted'])) + { + $Block['element']['handler']['argument'] []= $Line['text']; + + return $Block; + } + } + + # + # Rule + + protected function blockRule($Line) + { + $marker = $Line['text'][0]; + + if (substr_count($Line['text'], $marker) >= 3 and chop($Line['text'], " $marker") === '') + { + $Block = array( + 'element' => array( + 'name' => 'hr', + ), + ); + + return $Block; + } + } + + # + # Setext + + protected function blockSetextHeader($Line, ?array $Block = null) + { + if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted'])) + { + return; + } + + if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '') + { + $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2'; + + return $Block; + } + } + + # + # Markup + + protected function blockMarkup($Line) + { + if ($this->markupEscaped or $this->safeMode) + { + return; + } + + if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches)) + { + $element = strtolower($matches[1]); + + if (in_array($element, $this->textLevelElements)) + { + return; + } + + $Block = array( + 'name' => $matches[1], + 'element' => array( + 'rawHtml' => $Line['text'], + 'autobreak' => true, + ), + ); + + return $Block; + } + } + + protected function blockMarkupContinue($Line, array $Block) + { + if (isset($Block['closed']) or isset($Block['interrupted'])) + { + return; + } + + $Block['element']['rawHtml'] .= "\n" . $Line['body']; + + return $Block; + } + + # + # Reference + + protected function blockReference($Line) + { + if (strpos($Line['text'], ']') !== false + and preg_match('/^\[(.+?)\]:[ ]*+?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches) + ) { + $id = strtolower($matches[1]); + + $Data = array( + 'url' => $matches[2], + 'title' => isset($matches[3]) ? $matches[3] : null, + ); + + $this->DefinitionData['Reference'][$id] = $Data; + + $Block = array( + 'element' => array(), + ); + + return $Block; + } + } + + # + # Table + + protected function blockTable($Line, ?array $Block = null) + { + if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted'])) + { + return; + } + + if ( + strpos($Block['element']['handler']['argument'], '|') === false + and strpos($Line['text'], '|') === false + and strpos($Line['text'], ':') === false + or strpos($Block['element']['handler']['argument'], "\n") !== false + ) { + return; + } + + if (chop($Line['text'], ' -:|') !== '') + { + return; + } + + $alignments = array(); + + $divider = $Line['text']; + + $divider = trim($divider); + $divider = trim($divider, '|'); + + $dividerCells = explode('|', $divider); + + foreach ($dividerCells as $dividerCell) + { + $dividerCell = trim($dividerCell); + + if ($dividerCell === '') + { + return; + } + + $alignment = null; + + if ($dividerCell[0] === ':') + { + $alignment = 'left'; + } + + if (substr($dividerCell, - 1) === ':') + { + $alignment = $alignment === 'left' ? 'center' : 'right'; + } + + $alignments []= $alignment; + } + + # ~ + + $HeaderElements = array(); + + $header = $Block['element']['handler']['argument']; + + $header = trim($header); + $header = trim($header, '|'); + + $headerCells = explode('|', $header); + + if (count($headerCells) !== count($alignments)) + { + return; + } + + foreach ($headerCells as $index => $headerCell) + { + $headerCell = trim($headerCell); + + $HeaderElement = array( + 'name' => 'th', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $headerCell, + 'destination' => 'elements', + ) + ); + + if (isset($alignments[$index])) + { + $alignment = $alignments[$index]; + + $HeaderElement['attributes'] = array( + 'style' => "text-align: $alignment;", + ); + } + + $HeaderElements []= $HeaderElement; + } + + # ~ + + $Block = array( + 'alignments' => $alignments, + 'identified' => true, + 'element' => array( + 'name' => 'table', + 'elements' => array(), + ), + ); + + $Block['element']['elements'] []= array( + 'name' => 'thead', + ); + + $Block['element']['elements'] []= array( + 'name' => 'tbody', + 'elements' => array(), + ); + + $Block['element']['elements'][0]['elements'] []= array( + 'name' => 'tr', + 'elements' => $HeaderElements, + ); + + return $Block; + } + + protected function blockTableContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + if (count($Block['alignments']) === 1 or $Line['text'][0] === '|' or strpos($Line['text'], '|')) + { + $Elements = array(); + + $row = $Line['text']; + + $row = trim($row); + $row = trim($row, '|'); + + preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]++`|`)++/', $row, $matches); + + $cells = array_slice($matches[0], 0, count($Block['alignments'])); + + foreach ($cells as $index => $cell) + { + $cell = trim($cell); + + $Element = array( + 'name' => 'td', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $cell, + 'destination' => 'elements', + ) + ); + + if (isset($Block['alignments'][$index])) + { + $Element['attributes'] = array( + 'style' => 'text-align: ' . $Block['alignments'][$index] . ';', + ); + } + + $Elements []= $Element; + } + + $Element = array( + 'name' => 'tr', + 'elements' => $Elements, + ); + + $Block['element']['elements'][1]['elements'] []= $Element; + + return $Block; + } + } + + # + # ~ + # + + protected function paragraph($Line) + { + return array( + 'type' => 'Paragraph', + 'element' => array( + 'name' => 'p', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $Line['text'], + 'destination' => 'elements', + ), + ), + ); + } + + protected function paragraphContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + $Block['element']['handler']['argument'] .= "\n".$Line['text']; + + return $Block; + } + + # + # Inline Elements + # + + protected $InlineTypes = array( + '!' => array('Image'), + '&' => array('SpecialCharacter'), + '*' => array('Emphasis'), + ':' => array('Url'), + '<' => array('UrlTag', 'EmailTag', 'Markup'), + '[' => array('Link'), + '_' => array('Emphasis'), + '`' => array('Code'), + '~' => array('Strikethrough'), + '\\' => array('EscapeSequence'), + ); + + # ~ + + protected $inlineMarkerList = '!*_&[:<`~\\'; + + # + # ~ + # + + public function line($text, $nonNestables = array()) + { + return $this->elements($this->lineElements($text, $nonNestables)); + } + + protected function lineElements($text, $nonNestables = array()) + { + # standardize line breaks + $text = str_replace(array("\r\n", "\r"), "\n", $text); + + $Elements = array(); + + $nonNestables = (empty($nonNestables) + ? array() + : array_combine($nonNestables, $nonNestables) + ); + + # $excerpt is based on the first occurrence of a marker + + while ($excerpt = strpbrk($text, $this->inlineMarkerList)) + { + $marker = $excerpt[0]; + + $markerPosition = strlen($text) - strlen($excerpt); + + $Excerpt = array('text' => $excerpt, 'context' => $text); + + foreach ($this->InlineTypes[$marker] as $inlineType) + { + # check to see if the current inline type is nestable in the current context + + if (isset($nonNestables[$inlineType])) + { + continue; + } + + $Inline = $this->{"inline$inlineType"}($Excerpt); + + if ( ! isset($Inline)) + { + continue; + } + + # makes sure that the inline belongs to "our" marker + + if (isset($Inline['position']) and $Inline['position'] > $markerPosition) + { + continue; + } + + # sets a default inline position + + if ( ! isset($Inline['position'])) + { + $Inline['position'] = $markerPosition; + } + + # cause the new element to 'inherit' our non nestables + + + $Inline['element']['nonNestables'] = isset($Inline['element']['nonNestables']) + ? array_merge($Inline['element']['nonNestables'], $nonNestables) + : $nonNestables + ; + + # the text that comes before the inline + $unmarkedText = substr($text, 0, $Inline['position']); + + # compile the unmarked text + $InlineText = $this->inlineText($unmarkedText); + $Elements[] = $InlineText['element']; + + # compile the inline + $Elements[] = $this->extractElement($Inline); + + # remove the examined text + $text = substr($text, $Inline['position'] + $Inline['extent']); + + continue 2; + } + + # the marker does not belong to an inline + + $unmarkedText = substr($text, 0, $markerPosition + 1); + + $InlineText = $this->inlineText($unmarkedText); + $Elements[] = $InlineText['element']; + + $text = substr($text, $markerPosition + 1); + } + + $InlineText = $this->inlineText($text); + $Elements[] = $InlineText['element']; + + foreach ($Elements as &$Element) + { + if ( ! isset($Element['autobreak'])) + { + $Element['autobreak'] = false; + } + } + + return $Elements; + } + + # + # ~ + # + + protected function inlineText($text) + { + $Inline = array( + 'extent' => strlen($text), + 'element' => array(), + ); + + $Inline['element']['elements'] = self::pregReplaceElements( + $this->breaksEnabled ? '/[ ]*+\n/' : '/(?:[ ]*+\\\\|[ ]{2,}+)\n/', + array( + array('name' => 'br'), + array('text' => "\n"), + ), + $text + ); + + return $Inline; + } + + protected function inlineCode($Excerpt) + { + $marker = $Excerpt['text'][0]; + + if (preg_match('/^(['.$marker.']++)[ ]*+(.+?)[ ]*+(? strlen($matches[0]), + 'element' => array( + 'name' => 'code', + 'text' => $text, + ), + ); + } + } + + protected function inlineEmailTag($Excerpt) + { + $hostnameLabel = '[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?'; + + $commonMarkEmail = '[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]++@' + . $hostnameLabel . '(?:\.' . $hostnameLabel . ')*'; + + if (strpos($Excerpt['text'], '>') !== false + and preg_match("/^<((mailto:)?$commonMarkEmail)>/i", $Excerpt['text'], $matches) + ){ + $url = $matches[1]; + + if ( ! isset($matches[2])) + { + $url = "mailto:$url"; + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $matches[1], + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + protected function inlineEmphasis($Excerpt) + { + if ( ! isset($Excerpt['text'][1])) + { + return; + } + + $marker = $Excerpt['text'][0]; + + if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches)) + { + $emphasis = 'strong'; + } + elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches)) + { + $emphasis = 'em'; + } + else + { + return; + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => $emphasis, + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $matches[1], + 'destination' => 'elements', + ) + ), + ); + } + + protected function inlineEscapeSequence($Excerpt) + { + if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters)) + { + return array( + 'element' => array('rawHtml' => $Excerpt['text'][1]), + 'extent' => 2, + ); + } + } + + protected function inlineImage($Excerpt) + { + if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[') + { + return; + } + + $Excerpt['text']= substr($Excerpt['text'], 1); + + $Link = $this->inlineLink($Excerpt); + + if ($Link === null) + { + return; + } + + $Inline = array( + 'extent' => $Link['extent'] + 1, + 'element' => array( + 'name' => 'img', + 'attributes' => array( + 'src' => $Link['element']['attributes']['href'], + 'alt' => $Link['element']['handler']['argument'], + ), + 'autobreak' => true, + ), + ); + + $Inline['element']['attributes'] += $Link['element']['attributes']; + + unset($Inline['element']['attributes']['href']); + + return $Inline; + } + + protected function inlineLink($Excerpt) + { + $Element = array( + 'name' => 'a', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => null, + 'destination' => 'elements', + ), + 'nonNestables' => array('Url', 'Link'), + 'attributes' => array( + 'href' => null, + 'title' => null, + ), + ); + + $extent = 0; + + $remainder = $Excerpt['text']; + + if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) + { + $Element['handler']['argument'] = $matches[1]; + + $extent += strlen($matches[0]); + + $remainder = substr($remainder, $extent); + } + else + { + return; + } + + if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches)) + { + $Element['attributes']['href'] = $matches[1]; + + if (isset($matches[2])) + { + $Element['attributes']['title'] = substr($matches[2], 1, - 1); + } + + $extent += strlen($matches[0]); + } + else + { + if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) + { + $definition = strlen($matches[1]) ? $matches[1] : $Element['handler']['argument']; + $definition = strtolower($definition); + + $extent += strlen($matches[0]); + } + else + { + $definition = strtolower($Element['handler']['argument']); + } + + if ( ! isset($this->DefinitionData['Reference'][$definition])) + { + return; + } + + $Definition = $this->DefinitionData['Reference'][$definition]; + + $Element['attributes']['href'] = $Definition['url']; + $Element['attributes']['title'] = $Definition['title']; + } + + return array( + 'extent' => $extent, + 'element' => $Element, + ); + } + + protected function inlineMarkup($Excerpt) + { + if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false) + { + return; + } + + if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches)) + { + return array( + 'element' => array('rawHtml' => $matches[0]), + 'extent' => strlen($matches[0]), + ); + } + + if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches)) + { + return array( + 'element' => array('rawHtml' => $matches[0]), + 'extent' => strlen($matches[0]), + ); + } + + if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches)) + { + return array( + 'element' => array('rawHtml' => $matches[0]), + 'extent' => strlen($matches[0]), + ); + } + } + + protected function inlineSpecialCharacter($Excerpt) + { + if (substr($Excerpt['text'], 1, 1) !== ' ' and strpos($Excerpt['text'], ';') !== false + and preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches) + ) { + return array( + 'element' => array('rawHtml' => '&' . $matches[1] . ';'), + 'extent' => strlen($matches[0]), + ); + } + + return; + } + + protected function inlineStrikethrough($Excerpt) + { + if ( ! isset($Excerpt['text'][1])) + { + return; + } + + if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) + { + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'del', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $matches[1], + 'destination' => 'elements', + ) + ), + ); + } + } + + protected function inlineUrl($Excerpt) + { + if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/') + { + return; + } + + if (strpos($Excerpt['context'], 'http') !== false + and preg_match('/\bhttps?+:[\/]{2}[^\s<]+\b\/*+/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE) + ) { + $url = $matches[0][0]; + + $Inline = array( + 'extent' => strlen($matches[0][0]), + 'position' => $matches[0][1], + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), + ), + ); + + return $Inline; + } + } + + protected function inlineUrlTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w++:\/{2}[^ >]++)>/i', $Excerpt['text'], $matches)) + { + $url = $matches[1]; + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + # ~ + + protected function unmarkedText($text) + { + $Inline = $this->inlineText($text); + return $this->element($Inline['element']); + } + + # + # Handlers + # + + protected function handle(array $Element) + { + if (isset($Element['handler'])) + { + if (!isset($Element['nonNestables'])) + { + $Element['nonNestables'] = array(); + } + + if (is_string($Element['handler'])) + { + $function = $Element['handler']; + $argument = $Element['text']; + unset($Element['text']); + $destination = 'rawHtml'; + } + else + { + $function = $Element['handler']['function']; + $argument = $Element['handler']['argument']; + $destination = $Element['handler']['destination']; + } + + $Element[$destination] = $this->{$function}($argument, $Element['nonNestables']); + + if ($destination === 'handler') + { + $Element = $this->handle($Element); + } + + unset($Element['handler']); + } + + return $Element; + } + + protected function handleElementRecursive(array $Element) + { + return $this->elementApplyRecursive(array($this, 'handle'), $Element); + } + + protected function handleElementsRecursive(array $Elements) + { + return $this->elementsApplyRecursive(array($this, 'handle'), $Elements); + } + + protected function elementApplyRecursive($closure, array $Element) + { + $Element = call_user_func($closure, $Element); + + if (isset($Element['elements'])) + { + $Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']); + } + elseif (isset($Element['element'])) + { + $Element['element'] = $this->elementApplyRecursive($closure, $Element['element']); + } + + return $Element; + } + + protected function elementApplyRecursiveDepthFirst($closure, array $Element) + { + if (isset($Element['elements'])) + { + $Element['elements'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['elements']); + } + elseif (isset($Element['element'])) + { + $Element['element'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['element']); + } + + $Element = call_user_func($closure, $Element); + + return $Element; + } + + protected function elementsApplyRecursive($closure, array $Elements) + { + foreach ($Elements as &$Element) + { + $Element = $this->elementApplyRecursive($closure, $Element); + } + + return $Elements; + } + + protected function elementsApplyRecursiveDepthFirst($closure, array $Elements) + { + foreach ($Elements as &$Element) + { + $Element = $this->elementApplyRecursiveDepthFirst($closure, $Element); + } + + return $Elements; + } + + protected function element(array $Element) + { + if ($this->safeMode) + { + $Element = $this->sanitiseElement($Element); + } + + # identity map if element has no handler + $Element = $this->handle($Element); + + $hasName = isset($Element['name']); + + $markup = ''; + + if ($hasName) + { + $markup .= '<' . $Element['name']; + + if (isset($Element['attributes'])) + { + foreach ($Element['attributes'] as $name => $value) + { + if ($value === null) + { + continue; + } + + $markup .= " $name=\"".self::escape($value).'"'; + } + } + } + + $permitRawHtml = false; + + if (isset($Element['text'])) + { + $text = $Element['text']; + } + // very strongly consider an alternative if you're writing an + // extension + elseif (isset($Element['rawHtml'])) + { + $text = $Element['rawHtml']; + + $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode']; + $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode; + } + + $hasContent = isset($text) || isset($Element['element']) || isset($Element['elements']); + + if ($hasContent) + { + $markup .= $hasName ? '>' : ''; + + if (isset($Element['elements'])) + { + $markup .= $this->elements($Element['elements']); + } + elseif (isset($Element['element'])) + { + $markup .= $this->element($Element['element']); + } + else + { + if (!$permitRawHtml) + { + $markup .= self::escape($text, true); + } + else + { + $markup .= $text; + } + } + + $markup .= $hasName ? '' : ''; + } + elseif ($hasName) + { + $markup .= ' />'; + } + + return $markup; + } + + protected function elements(array $Elements) + { + $markup = ''; + + $autoBreak = true; + + foreach ($Elements as $Element) + { + if (empty($Element)) + { + continue; + } + + $autoBreakNext = (isset($Element['autobreak']) + ? $Element['autobreak'] : isset($Element['name']) + ); + // (autobreak === false) covers both sides of an element + $autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext; + + $markup .= ($autoBreak ? "\n" : '') . $this->element($Element); + $autoBreak = $autoBreakNext; + } + + $markup .= $autoBreak ? "\n" : ''; + + return $markup; + } + + # ~ + + protected function li($lines) + { + $Elements = $this->linesElements($lines); + + if ( ! in_array('', $lines) + and isset($Elements[0]) and isset($Elements[0]['name']) + and $Elements[0]['name'] === 'p' + ) { + unset($Elements[0]['name']); + } + + return $Elements; + } + + # + # AST Convenience + # + + /** + * Replace occurrences $regexp with $Elements in $text. Return an array of + * elements representing the replacement. + */ + protected static function pregReplaceElements($regexp, $Elements, $text) + { + $newElements = array(); + + while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE)) + { + $offset = $matches[0][1]; + $before = substr($text, 0, $offset); + $after = substr($text, $offset + strlen($matches[0][0])); + + $newElements[] = array('text' => $before); + + foreach ($Elements as $Element) + { + $newElements[] = $Element; + } + + $text = $after; + } + + $newElements[] = array('text' => $text); + + return $newElements; + } + + # + # Deprecated Methods + # + + function parse($text) + { + $markup = $this->text($text); + + return $markup; + } + + protected function sanitiseElement(array $Element) + { + static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/'; + static $safeUrlNameToAtt = array( + 'a' => 'href', + 'img' => 'src', + ); + + if ( ! isset($Element['name'])) + { + unset($Element['attributes']); + return $Element; + } + + if (isset($safeUrlNameToAtt[$Element['name']])) + { + $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]); + } + + if ( ! empty($Element['attributes'])) + { + foreach ($Element['attributes'] as $att => $val) + { + # filter out badly parsed attribute + if ( ! preg_match($goodAttribute, $att)) + { + unset($Element['attributes'][$att]); + } + # dump onevent attribute + elseif (self::striAtStart($att, 'on')) + { + unset($Element['attributes'][$att]); + } + } + } + + return $Element; + } + + protected function filterUnsafeUrlInAttribute(array $Element, $attribute) + { + foreach ($this->safeLinksWhitelist as $scheme) + { + if (self::striAtStart($Element['attributes'][$attribute], $scheme)) + { + return $Element; + } + } + + $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]); + + return $Element; + } + + # + # Static Methods + # + + protected static function escape($text, $allowQuotes = false) + { + return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8'); + } + + protected static function striAtStart($string, $needle) + { + $len = strlen($needle); + + if ($len > strlen($string)) + { + return false; + } + else + { + return strtolower(substr($string, 0, $len)) === strtolower($needle); + } + } + + static function instance($name = 'default') + { + if (isset(self::$instances[$name])) + { + return self::$instances[$name]; + } + + $instance = new static(); + + self::$instances[$name] = $instance; + + return $instance; + } + + private static $instances = array(); + + # + # Fields + # + + protected $DefinitionData; + + # + # Read-Only + + protected $specialCharacters = array( + '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', '~' + ); + + protected $StrongRegex = array( + '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s', + '_' => '/^__((?:\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us', + ); + + protected $EmRegex = array( + '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', + '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us', + ); + + protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+'; + + protected $voidElements = array( + 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', + ); + + protected $textLevelElements = array( + 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont', + 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing', + 'i', 'rp', 'del', 'code', 'strike', 'marquee', + 'q', 'rt', 'ins', 'font', 'strong', + 's', 'tt', 'kbd', 'mark', + 'u', 'xm', 'sub', 'nobr', + 'sup', 'ruby', + 'var', 'span', + 'wbr', 'time', + ); +} diff --git a/libs/Slimdown.php b/libs/Slimdown.php deleted file mode 100644 index 60b2080..0000000 --- a/libs/Slimdown.php +++ /dev/null @@ -1,155 +0,0 @@ - - * Website: https://github.com/jbroadway/slimdown - * License: MIT - */ -class Slimdown { - public static $rules = array ( - '/```(.*?)```/s' => self::class .'::code_parse', // code blocks - '/\n(#+)\s+(.*)/' => self::class .'::header', // headers - '/\!\[([^\[]*?)\]\(([^\)]+)\)/' => self::class .'::img', // images - '/\[([^\[]+)\]\(([^\)]+)\)/' => self::class .'::link', // links - '/(\*\*|__)(?=(?:(?:[^`]*`[^`\r\n]*`)*[^`]*$))(?![^\/<]*>.*<\/.+>)(.*?)\1/' => '\2', // bold - '/(\*|_)(?=(?:(?:[^`]*`[^`\r\n]*`)*[^`]*$))(?![^\/<]*>.*<\/.+>)(.*?)\1/' => '\2', // emphasis - '/(\~\~)(?=(?:(?:[^`]*`[^`\r\n]*`)*[^`]*$))(?![^\/<]*>.*<\/.+>)(.*?)\1/' => '\2', // del - '/\:\"(.*?)\"\:/' => '\1', // quote - '/`(.*?)`/' => '\1', // inline code - '/\n\*(.*)/' => self::class .'::ul_list', // ul lists - '/\n[0-9]+\.(.*)/' => self::class .'::ol_list', // ol lists - '/\n(>|\>)(.*)/' => self::class .'::blockquote', // blockquotes - '/\n-{5,}/' => "\n
", // horizontal rule - '/\n([^\n]+)\n/' => self::class .'::para', // add paragraphs - '/<\/ul>\s?