├── .babelrc
├── .editorconfig
├── .gitignore
├── .idea
├── .gitignore
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── if-script.iml
├── misc.xml
├── modules.xml
└── vcs.xml
├── .prettierrc.js
├── README.md
├── config
├── paths.js
├── webpack.config.js
├── webpack.dev.js
├── webpack.gh.js
└── webpack.prod.js
├── dist
├── 08da13c09fcc675ab837.png
├── if-logo-nobg.png
├── index.html
├── preview
│ └── index.html
├── scripts
│ ├── 514.b1dd7ca94e8c1f0ef18f.bundle.js
│ ├── 514.b1dd7ca94e8c1f0ef18f.bundle.js.LICENSE.txt
│ ├── 886.1619d691d03b4fd46521.bundle.js
│ ├── 886.1619d691d03b4fd46521.bundle.js.LICENSE.txt
│ ├── main.f1255e2b0bead4cf4121.bundle.js
│ ├── main.f1255e2b0bead4cf4121.bundle.js.LICENSE.txt
│ ├── preview.b5ba595287997bff1d77.bundle.js
│ ├── preview.b5ba595287997bff1d77.bundle.js.LICENSE.txt
│ ├── runtime.0c68d17123e005a7980a.bundle.js
│ └── runtime.0c68d17123e005a7980a.bundle.js.LICENSE.txt
└── styles
│ ├── 824.css
│ ├── main.f38ec3779613ca9c7584.css
│ └── preview.6db1bab1bb1452ba5383.css
├── package-lock.json
├── package.json
└── src
├── assets
├── fonts
│ ├── FiraMono-Regular.ttf
│ └── VarelaRound-Regular.ttf
└── images
│ ├── book-background0.jpg
│ ├── if-logo-nobg.png
│ └── if-logo.png
├── banner-html.txt
├── banner.txt
├── lib
├── nearley.js
├── showdown.min.js
└── w3Highlighter.js
└── web
├── css
└── index.css
├── index.html
├── index.template.html
├── js
├── IFError.js
├── globals.js
└── index.js
├── preview
├── index.js
└── index.template.html
└── service-worker.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env"],
3 | "plugins": ["@babel/plugin-proposal-class-properties"]
4 | }
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 | [*]
3 | indent_style = spaces
4 | indent_size = 2
5 | end_of_line = lf
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 | max_line_length = 160
10 |
11 | [*.md]
12 | max_line_length = off
13 |
14 | [*.json]
15 | max_line_length = off
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | todo.html
2 | assets/grammar-archive
3 | assets/help.html
4 | js/if-highlighter.js
5 | file.html
6 |
7 | # dependencies
8 | /node_modules
9 | /.pnp
10 | .pnp.js
11 | /auth.json
12 |
13 | # testing
14 | /coverage
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | .env.local
22 | .env.development.local
23 | .env.test.local
24 | .env.production.local
25 | config/.env
26 |
27 | npm-debug.log*
28 | yarn-debug.log*
29 | yarn-error.log*
30 |
31 | # vscode
32 | .vscode
33 | *.code-workspace
34 |
35 | # nano
36 | *.save
37 |
38 | # db
39 | /data/*
40 | !/data/.gitkeep
41 |
42 | # sublime
43 | *.tern-project
44 | *.sublime-project
45 | *.flowconfig
46 | *.je-project-settings
47 | *.sublime-workspace
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Datasource local storage ignored files
5 | /dataSources/
6 | /dataSources.local.xml
7 | # Editor-based HTTP Client requests
8 | /httpRequests/
9 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
[^\r]+?<\/pre>)/gm,function(e,r){var t=r;return t=t.replace(/^ /gm,"¨0"),t=t.replace(/¨0/g,"")}),a.subParser("hashBlock")("","gim"),e=t.converter._dispatch("hashPreCodeTags.after",e,r,t)}),a.subParser("headers",function(e,r,t){"use strict";function n(e){var n,s;if(r.customizedHeaderId){var o=e.match(/\{([^{]+?)}\s*$/);o&&o[1]&&(e=o[1])}return n=e,s=a.helper.isString(r.prefixHeaderId)?r.prefixHeaderId:!0===r.prefixHeaderId?"section-":"",r.rawPrefixHeaderId||(n=s+n),n=r.ghCompatibleHeaderId?n.replace(/ /g,"-").replace(/&/g,"").replace(/¨T/g,"").replace(/¨D/g,"").replace(/[&+$,\/:;=?@"#{}|^¨~\[\]`\\*)(%.!'<>]/g,"").toLowerCase():r.rawHeaderId?n.replace(/ /g,"-").replace(/&/g,"&").replace(/¨T/g,"¨").replace(/¨D/g,"$").replace(/["']/g,"-").toLowerCase():n.replace(/[^\w]/g,"").toLowerCase(),r.rawPrefixHeaderId&&(n=s+n),t.hashLinkCounts[n]?n=n+"-"+t.hashLinkCounts[n]++:t.hashLinkCounts[n]=1,n}e=t.converter._dispatch("headers.before",e,r,t);var s=isNaN(parseInt(r.headerLevelStart))?1:parseInt(r.headerLevelStart),o=r.smoothLivePreview?/^(.+)[ \t]*\n={2,}[ \t]*\n+/gm:/^(.+)[ \t]*\n=+[ \t]*\n+/gm,i=r.smoothLivePreview?/^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm:/^(.+)[ \t]*\n-+[ \t]*\n+/gm;e=(e=e.replace(o,function(e,o){var i=a.subParser("spanGamut")(o,r,t),l=r.noHeaderId?"":' id="'+n(o)+'"',c="\n"+e+"\n",r,t)}),e=t.converter._dispatch("blockQuotes.after",e,r,t)}),a.subParser("codeBlocks",function(e,r,t){"use strict";e=t.converter._dispatch("codeBlocks.before",e,r,t);return e=(e+="¨0").replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=¨0))/g,function(e,n,s){var o=n,i=s,l="\n";return o=a.subParser("outdent")(o,r,t),o=a.subParser("encodeCode")(o,r,t),o=a.subParser("detab")(o,r,t),o=o.replace(/^\n+/g,""),o=o.replace(/\n+$/g,""),r.omitExtraWLInCodeBlocks&&(l=""),o="",a.subParser("hashBlock")(o,r,t)+i}),e=e.replace(/¨0/,""),e=t.converter._dispatch("codeBlocks.after",e,r,t)}),a.subParser("codeSpans",function(e,r,t){"use strict";return void 0===(e=t.converter._dispatch("codeSpans.before",e,r,t))&&(e=""),e=e.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(e,n,s,o){var i=o;return i=i.replace(/^([ \t]*)/g,""),i=i.replace(/[ \t]*$/g,""),i=a.subParser("encodeCode")(i,r,t),i=n+""+o+l+"
"+i+"
",i=a.subParser("hashHTMLSpans")(i,r,t)}),e=t.converter._dispatch("codeSpans.after",e,r,t)}),a.subParser("completeHTMLDocument",function(e,r,t){"use strict";if(!r.completeHTMLDocument)return e;e=t.converter._dispatch("completeHTMLDocument.before",e,r,t);var a="html",n="\n",s="",o='\n',i="",l="";void 0!==t.metadata.parsed.doctype&&(n="\n","html"!==(a=t.metadata.parsed.doctype.toString().toLowerCase())&&"html5"!==a||(o=''));for(var c in t.metadata.parsed)if(t.metadata.parsed.hasOwnProperty(c))switch(c.toLowerCase()){case"doctype":break;case"title":s=""+t.metadata.parsed.title+" \n";break;case"charset":o="html"===a||"html5"===a?'\n':'\n';break;case"language":case"lang":i=' lang="'+t.metadata.parsed[c]+'"',l+='\n';break;default:l+='\n'}return e=n+"\n\n"+s+o+l+"\n\n"+e.trim()+"\n\n",e=t.converter._dispatch("completeHTMLDocument.after",e,r,t)}),a.subParser("detab",function(e,r,t){"use strict";return e=t.converter._dispatch("detab.before",e,r,t),e=e.replace(/\t(?=\t)/g," "),e=e.replace(/\t/g,"¨A¨B"),e=e.replace(/¨B(.+?)¨A/g,function(e,r){for(var t=r,a=4-t.length%4,n=0;n/g,">"),e=t.converter._dispatch("encodeAmpsAndAngles.after",e,r,t)}),a.subParser("encodeBackslashEscapes",function(e,r,t){"use strict";return e=t.converter._dispatch("encodeBackslashEscapes.before",e,r,t),e=e.replace(/\\(\\)/g,a.helper.escapeCharactersCallback),e=e.replace(/\\([`*_{}\[\]()>#+.!~=|-])/g,a.helper.escapeCharactersCallback),e=t.converter._dispatch("encodeBackslashEscapes.after",e,r,t)}),a.subParser("encodeCode",function(e,r,t){"use strict";return e=t.converter._dispatch("encodeCode.before",e,r,t),e=e.replace(/&/g,"&").replace(//g,">").replace(/([*_{}\[\]\\=~-])/g,a.helper.escapeCharactersCallback),e=t.converter._dispatch("encodeCode.after",e,r,t)}),a.subParser("escapeSpecialCharsWithinTagAttributes",function(e,r,t){"use strict";return e=(e=t.converter._dispatch("escapeSpecialCharsWithinTagAttributes.before",e,r,t)).replace(/<\/?[a-z\d_:-]+(?:[\s]+[\s\S]+?)?>/gi,function(e){return e.replace(/(.)<\/?code>(?=.)/g,"$1`").replace(/([\\`*_~=|])/g,a.helper.escapeCharactersCallback)}),e=e.replace(/-]|-[^>])(?:[^-]|-[^-])*)--)>/gi,function(e){return e.replace(/([\\`*_~=|])/g,a.helper.escapeCharactersCallback)}),e=t.converter._dispatch("escapeSpecialCharsWithinTagAttributes.after",e,r,t)}),a.subParser("githubCodeBlocks",function(e,r,t){"use strict";return r.ghCodeBlocks?(e=t.converter._dispatch("githubCodeBlocks.before",e,r,t),e+="¨0",e=e.replace(/(?:^|\n)(?: {0,3})(```+|~~~+)(?: *)([^\s`~]*)\n([\s\S]*?)\n(?: {0,3})\1/g,function(e,n,s,o){var i=r.omitExtraWLInCodeBlocks?"":"\n";return o=a.subParser("encodeCode")(o,r,t),o=a.subParser("detab")(o,r,t),o=o.replace(/^\n+/g,""),o=o.replace(/\n+$/g,""),o="",o=a.subParser("hashBlock")(o,r,t),"\n\n¨G"+(t.ghCodeBlocks.push({text:e,codeblock:o})-1)+"G\n\n"}),e=e.replace(/¨0/,""),t.converter._dispatch("githubCodeBlocks.after",e,r,t)):e}),a.subParser("hashBlock",function(e,r,t){"use strict";return e=t.converter._dispatch("hashBlock.before",e,r,t),e=e.replace(/(^\n+|\n+$)/g,""),e="\n\n¨K"+(t.gHtmlBlocks.push(e)-1)+"K\n\n",e=t.converter._dispatch("hashBlock.after",e,r,t)}),a.subParser("hashCodeTags",function(e,r,t){"use strict";e=t.converter._dispatch("hashCodeTags.before",e,r,t);return e=a.helper.replaceRecursiveRegExp(e,function(e,n,s,o){var i=s+a.subParser("encodeCode")(n,r,t)+o;return"¨C"+(t.gHtmlSpans.push(i)-1)+"C"},""+o+i+"
]*>","
","gim"),e=t.converter._dispatch("hashCodeTags.after",e,r,t)}),a.subParser("hashElement",function(e,r,t){"use strict";return function(e,r){var a=r;return a=a.replace(/\n\n/g,"\n"),a=a.replace(/^\n/,""),a=a.replace(/\n+$/g,""),a="\n\n¨K"+(t.gHtmlBlocks.push(a)-1)+"K\n\n"}}),a.subParser("hashHTMLBlocks",function(e,r,t){"use strict";e=t.converter._dispatch("hashHTMLBlocks.before",e,r,t);var n=["pre","div","h1","h2","h3","h4","h5","h6","blockquote","table","dl","ol","ul","script","noscript","form","fieldset","iframe","math","style","section","header","footer","nav","article","aside","address","audio","canvas","figure","hgroup","output","video","p"],s=function(e,r,a,n){var s=e;return-1!==a.search(/\bmarkdown\b/)&&(s=a+t.converter.makeHtml(r)+n),"\n\n¨K"+(t.gHtmlBlocks.push(s)-1)+"K\n\n"};r.backslashEscapesHTMLTags&&(e=e.replace(/\\<(\/?[^>]+?)>/g,function(e,r){return"<"+r+">"}));for(var o=0;o]*>)","im"),c="<"+n[o]+"\\b[^>]*>",u=""+n[o]+">";-1!==(i=a.helper.regexIndexOf(e,l));){var d=a.helper.splitAtIndex(e,i),p=a.helper.replaceRecursiveRegExp(d[1],s,c,u,"im");if(p===d[1])break;e=d[0].concat(p)}return e=e.replace(/(\n {0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,a.subParser("hashElement")(e,r,t)),e=a.helper.replaceRecursiveRegExp(e,function(e){return"\n\n¨K"+(t.gHtmlBlocks.push(e)-1)+"K\n\n"},"^ {0,3}\x3c!--","--\x3e","gm"),e=e.replace(/(?:\n\n)( {0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,a.subParser("hashElement")(e,r,t)),e=t.converter._dispatch("hashHTMLBlocks.after",e,r,t)}),a.subParser("hashHTMLSpans",function(e,r,t){"use strict";function a(e){return"¨C"+(t.gHtmlSpans.push(e)-1)+"C"}return e=t.converter._dispatch("hashHTMLSpans.before",e,r,t),e=e.replace(/<[^>]+?\/>/gi,function(e){return a(e)}),e=e.replace(/<([^>]+?)>[\s\S]*?<\/\1>/g,function(e){return a(e)}),e=e.replace(/<([^>]+?)\s[^>]+?>[\s\S]*?<\/\1>/g,function(e){return a(e)}),e=e.replace(/<[^>]+?>/gi,function(e){return a(e)}),e=t.converter._dispatch("hashHTMLSpans.after",e,r,t)}),a.subParser("unhashHTMLSpans",function(e,r,t){"use strict";e=t.converter._dispatch("unhashHTMLSpans.before",e,r,t);for(var a=0;a ]*>\\s* ]*>","^ {0,3}
\\s*
"),l+="
",s.push(l))}for(o=s.length,i=0;i]*>/.test(u)&&(d=!0)}s[i]=u}return e=s.join("\n"),e=e.replace(/^\n+/g,""),e=e.replace(/\n+$/g,""),t.converter._dispatch("paragraphs.after",e,r,t)}),a.subParser("runExtension",function(e,r,t,a){"use strict";if(e.filter)r=e.filter(r,a.converter,t);else if(e.regex){var n=e.regex;n instanceof RegExp||(n=new RegExp(n,"g")),r=r.replace(n,e.replace)}return r}),a.subParser("spanGamut",function(e,r,t){"use strict";return e=t.converter._dispatch("spanGamut.before",e,r,t),e=a.subParser("codeSpans")(e,r,t),e=a.subParser("escapeSpecialCharsWithinTagAttributes")(e,r,t),e=a.subParser("encodeBackslashEscapes")(e,r,t),e=a.subParser("images")(e,r,t),e=a.subParser("anchors")(e,r,t),e=a.subParser("autoLinks")(e,r,t),e=a.subParser("simplifiedAutoLinks")(e,r,t),e=a.subParser("emoji")(e,r,t),e=a.subParser("underline")(e,r,t),e=a.subParser("italicsAndBold")(e,r,t),e=a.subParser("strikethrough")(e,r,t),e=a.subParser("ellipsis")(e,r,t),e=a.subParser("hashHTMLSpans")(e,r,t),e=a.subParser("encodeAmpsAndAngles")(e,r,t),r.simpleLineBreaks?/\n\n¨K/.test(e)||(e=e.replace(/\n+/g,"
\n")):e=e.replace(/ +\n/g,"
\n"),e=t.converter._dispatch("spanGamut.after",e,r,t)}),a.subParser("strikethrough",function(e,r,t){"use strict";return r.strikethrough&&(e=(e=t.converter._dispatch("strikethrough.before",e,r,t)).replace(/(?:~){2}([\s\S]+?)(?:~){2}/g,function(e,n){return function(e){return r.simplifiedAutoLink&&(e=a.subParser("simplifiedAutoLinks")(e,r,t)),""+e+""}(n)}),e=t.converter._dispatch("strikethrough.after",e,r,t)),e}),a.subParser("stripLinkDefinitions",function(e,r,t){"use strict";var n=function(e,n,s,o,i,l,c){return n=n.toLowerCase(),s.match(/^data:.+?\/.+?;base64,/)?t.gUrls[n]=s.replace(/\s/g,""):t.gUrls[n]=a.subParser("encodeAmpsAndAngles")(s,r,t),l?l+c:(c&&(t.gTitles[n]=c.replace(/"|'/g,""")),r.parseImgDimensions&&o&&i&&(t.gDimensions[n]={width:o,height:i}),"")};return e=(e+="¨0").replace(/^ {0,3}\[(.+)]:[ \t]*\n?[ \t]*(data:.+?\/.+?;base64,[A-Za-z0-9+/=\n]+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n\n|(?=¨0)|(?=\n\[))/gm,n),e=e.replace(/^ {0,3}\[(.+)]:[ \t]*\n?[ \t]*([^>\s]+)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n+|(?=¨0))/gm,n),e=e.replace(/¨0/,"")}),a.subParser("tables",function(e,r,t){"use strict";function n(e){return/^:[ \t]*--*$/.test(e)?' style="text-align:left;"':/^--*[ \t]*:[ \t]*$/.test(e)?' style="text-align:right;"':/^:[ \t]*--*[ \t]*:$/.test(e)?' style="text-align:center;"':""}function s(e,n){var s="";return e=e.trim(),(r.tablesHeaderId||r.tableHeaderId)&&(s=' id="'+e.replace(/ /g,"_").toLowerCase()+'"'),e=a.subParser("spanGamut")(e,r,t),""+e+" \n"}function o(e,n){return""+a.subParser("spanGamut")(e,r,t)+" \n"}function i(e){var i,l=e.split("\n");for(i=0;i\n\n\n",n=0;n\n";for(var s=0;s\n"}return t+=" \n\n"}(p,_)}if(!r.tables)return e;return e=t.converter._dispatch("tables.before",e,r,t),e=e.replace(/\\(\|)/g,a.helper.escapeCharactersCallback),e=e.replace(/^ {0,3}\|?.+\|.+\n {0,3}\|?[ \t]*:?[ \t]*(?:[-=]){2,}[ \t]*:?[ \t]*\|[ \t]*:?[ \t]*(?:[-=]){2,}[\s\S]+?(?:\n\n|¨0)/gm,i),e=e.replace(/^ {0,3}\|.+\|[ \t]*\n {0,3}\|[ \t]*:?[ \t]*(?:[-=]){2,}[ \t]*:?[ \t]*\|[ \t]*\n( {0,3}\|.+\|[ \t]*\n)*(?:\n|¨0)/gm,i),e=t.converter._dispatch("tables.after",e,r,t)}),a.subParser("underline",function(e,r,t){"use strict";return r.underline?(e=t.converter._dispatch("underline.before",e,r,t),e=r.literalMidWordUnderscores?(e=e.replace(/\b___(\S[\s\S]*?)___\b/g,function(e,r){return""+r+""})).replace(/\b__(\S[\s\S]*?)__\b/g,function(e,r){return""+r+""}):(e=e.replace(/___(\S[\s\S]*?)___/g,function(e,r){return/\S$/.test(r)?""+r+"":e})).replace(/__(\S[\s\S]*?)__/g,function(e,r){return/\S$/.test(r)?""+r+"":e}),e=e.replace(/(_)/g,a.helper.escapeCharactersCallback),e=t.converter._dispatch("underline.after",e,r,t)):e}),a.subParser("unescapeSpecialChars",function(e,r,t){"use strict";return e=t.converter._dispatch("unescapeSpecialChars.before",e,r,t),e=e.replace(/¨E(\d+)E/g,function(e,r){var t=parseInt(r);return String.fromCharCode(t)}),e=t.converter._dispatch("unescapeSpecialChars.after",e,r,t)}),a.subParser("makeMarkdown.blockquote",function(e,r){"use strict";var t="";if(e.hasChildNodes())for(var n=e.childNodes,s=n.length,o=0;o "+t.split("\n").join("\n> ")}),a.subParser("makeMarkdown.codeBlock",function(e,r){"use strict";var t=e.getAttribute("language"),a=e.getAttribute("precodenum");return"```"+t+"\n"+r.preList[a]+"\n```"}),a.subParser("makeMarkdown.codeSpan",function(e){"use strict";return"`"+e.innerHTML+"`"}),a.subParser("makeMarkdown.emphasis",function(e,r){"use strict";var t="";if(e.hasChildNodes()){t+="*";for(var n=e.childNodes,s=n.length,o=0;o",e.hasAttribute("width")&&e.hasAttribute("height")&&(r+=" ="+e.getAttribute("width")+"x"+e.getAttribute("height")),e.hasAttribute("title")&&(r+=' "'+e.getAttribute("title")+'"'),r+=")"),r}),a.subParser("makeMarkdown.links",function(e,r){"use strict";var t="";if(e.hasChildNodes()&&e.hasAttribute("href")){var n=e.childNodes,s=n.length;t="[";for(var o=0;o",e.hasAttribute("title")&&(t+=' "'+e.getAttribute("title")+'"'),t+=")"}return t}),a.subParser("makeMarkdown.list",function(e,r,t){"use strict";var n="";if(!e.hasChildNodes())return"";for(var s=e.childNodes,o=s.length,i=e.getAttribute("start")||1,l=0;l"+r.preList[t]+""}),a.subParser("makeMarkdown.strikethrough",function(e,r){"use strict";var t="";if(e.hasChildNodes()){t+="~~";for(var n=e.childNodes,s=n.length,o=0;otr>th"),l=e.querySelectorAll("tbody>tr");for(t=0;t_&&(_=g)}for(t=0;t/g,"\\$1>"),r=r.replace(/^#/gm,"\\#"),r=r.replace(/^(\s*)([-=]{3,})(\s*)$/,"$1\\$2$3"),r=r.replace(/^( {0,3}\d+)\./gm,"$1\\."),r=r.replace(/^( {0,3})([+-])/gm,"$1\\$2"),r=r.replace(/]([\s]*)\(/g,"\\]$1\\("),r=r.replace(/^ {0,3}\[([\S \t]*?)]:/gm,"\\[$1]:")});"function"==typeof define&&define.amd?define(function(){"use strict";return a}):"undefined"!=typeof module&&module.exports?module.exports=a:this.showdown=a}).call(this);
3 | //# sourceMappingURL=showdown.min.js.map
4 |
--------------------------------------------------------------------------------
/src/lib/w3Highlighter.js:
--------------------------------------------------------------------------------
1 | export default function w3CodeColor(elmnt, mode) {
2 | var lang = mode || 'html'
3 | var elmntObj =
4 | typeof elmnt === 'string' ? document.getElementById(elmnt) : elmnt
5 | var elmntTxt = elmntObj.innerHTML
6 | var tagcolor = '#444'
7 | var tagnamecolor = 'mediumblue'
8 | var attributecolor = '#3b8708'
9 | var attributevaluecolor = 'brown' // #3b8708
10 | var commentcolor = 'green'
11 | var cssselectorcolor = 'brown'
12 | var csspropertycolor = 'red'
13 | var csspropertyvaluecolor = 'mediumblue'
14 | var cssdelimitercolor = 'black'
15 | var cssimportantcolor = 'red'
16 | var jscolor = 'black'
17 | var jskeywordcolor = 'mediumblue'
18 | var jsstringcolor = 'brown'
19 | var jsnumbercolor = 'red'
20 | var jspropertycolor = 'black'
21 | elmntObj.style.fontFamily = 'monospace'
22 | if (!lang) {
23 | lang = 'html'
24 | }
25 | if (lang == 'html') {
26 | elmntTxt = htmlMode(elmntTxt)
27 | }
28 | if (lang == 'css') {
29 | elmntTxt = cssMode(elmntTxt)
30 | }
31 | if (lang == 'js') {
32 | elmntTxt = jsMode(elmntTxt)
33 | }
34 | elmntObj.innerHTML = elmntTxt
35 |
36 | function extract(str, start, end, func, repl) {
37 | var s,
38 | e,
39 | d = '',
40 | a = []
41 | while (str.search(start) > -1) {
42 | s = str.search(start)
43 | e = str.indexOf(end, s)
44 | if (e == -1) {
45 | e = str.length
46 | }
47 | if (repl) {
48 | a.push(func(str.substring(s, e + end.length)))
49 | str = str.substring(0, s) + repl + str.substr(e + end.length)
50 | } else {
51 | d += str.substring(0, s)
52 | d += func(str.substring(s, e + end.length))
53 | str = str.substr(e + end.length)
54 | }
55 | }
56 | this.rest = d + str
57 | this.arr = a
58 | }
59 |
60 | function htmlMode(txt) {
61 | var rest = txt,
62 | done = '',
63 | php,
64 | comment,
65 | angular,
66 | startpos,
67 | endpos,
68 | note,
69 | i
70 | comment = new extract(
71 | rest,
72 | '<!--',
73 | '-->',
74 | commentMode,
75 | 'W3HTMLCOMMENTPOS'
76 | )
77 | rest = comment.rest
78 | while (rest.indexOf('<') > -1) {
79 | note = ''
80 | startpos = rest.indexOf('<')
81 | if (rest.substr(startpos, 9).toUpperCase() == '<STYLE') {
82 | note = 'css'
83 | }
84 | if (rest.substr(startpos, 10).toUpperCase() == '<SCRIPT') {
85 | note = 'javascript'
86 | }
87 | endpos = rest.indexOf('>', startpos)
88 | if (endpos == -1) {
89 | endpos = rest.length
90 | }
91 | done += rest.substring(0, startpos)
92 | done += tagMode(rest.substring(startpos, endpos + 4))
93 | rest = rest.substr(endpos + 4)
94 | if (note == 'css') {
95 | endpos = rest.indexOf('</style>')
96 | if (endpos > -1) {
97 | done += cssMode(rest.substring(0, endpos))
98 | rest = rest.substr(endpos)
99 | }
100 | }
101 | if (note == 'javascript') {
102 | endpos = rest.indexOf('</script>')
103 | if (endpos > -1) {
104 | done += jsMode(rest.substring(0, endpos))
105 | rest = rest.substr(endpos)
106 | }
107 | }
108 | }
109 | rest = done + rest
110 | for (i = 0; i < comment.arr.length; i++) {
111 | rest = rest.replace('W3HTMLCOMMENTPOS', comment.arr[i])
112 | }
113 | return rest
114 | }
115 | function tagMode(txt) {
116 | var rest = txt,
117 | done = '',
118 | startpos,
119 | endpos,
120 | result
121 | while (rest.search(/(\s|
)/) > -1) {
122 | startpos = rest.search(/(\s|
)/)
123 | endpos = rest.indexOf('>')
124 | if (endpos == -1) {
125 | endpos = rest.length
126 | }
127 | done += rest.substring(0, startpos)
128 | done += attributeMode(rest.substring(startpos, endpos))
129 | rest = rest.substr(endpos)
130 | }
131 | result = done + rest
132 | result =
133 | '<' + result.substring(4)
134 | if (result.substr(result.length - 4, 4) == '>') {
135 | result =
136 | result.substring(0, result.length - 4) +
137 | '>'
140 | }
141 | return '' + result + ''
142 | }
143 | function attributeMode(txt) {
144 | var rest = txt,
145 | done = '',
146 | startpos,
147 | endpos,
148 | singlefnuttpos,
149 | doublefnuttpos,
150 | spacepos
151 | while (rest.indexOf('=') > -1) {
152 | endpos = -1
153 | startpos = rest.indexOf('=')
154 | singlefnuttpos = rest.indexOf("'", startpos)
155 | doublefnuttpos = rest.indexOf('"', startpos)
156 | spacepos = rest.indexOf(' ', startpos + 2)
157 | if (
158 | spacepos > -1 &&
159 | (spacepos < singlefnuttpos || singlefnuttpos == -1) &&
160 | (spacepos < doublefnuttpos || doublefnuttpos == -1)
161 | ) {
162 | endpos = rest.indexOf(' ', startpos)
163 | } else if (
164 | doublefnuttpos > -1 &&
165 | (doublefnuttpos < singlefnuttpos || singlefnuttpos == -1) &&
166 | (doublefnuttpos < spacepos || spacepos == -1)
167 | ) {
168 | endpos = rest.indexOf('"', rest.indexOf('"', startpos) + 1)
169 | } else if (
170 | singlefnuttpos > -1 &&
171 | (singlefnuttpos < doublefnuttpos || doublefnuttpos == -1) &&
172 | (singlefnuttpos < spacepos || spacepos == -1)
173 | ) {
174 | endpos = rest.indexOf("'", rest.indexOf("'", startpos) + 1)
175 | }
176 | if (!endpos || endpos == -1 || endpos < startpos) {
177 | endpos = rest.length
178 | }
179 | done += rest.substring(0, startpos)
180 | done += attributeValueMode(rest.substring(startpos, endpos + 1))
181 | rest = rest.substr(endpos + 1)
182 | }
183 | return '' + done + rest + ''
184 | }
185 | function attributeValueMode(txt) {
186 | return '' + txt + ''
187 | }
188 | function commentMode(txt) {
189 | return '' + txt + ''
190 | }
191 | function cssMode(txt) {
192 | var rest = txt,
193 | done = '',
194 | s,
195 | e,
196 | comment,
197 | i,
198 | midz,
199 | c,
200 | cc
201 | comment = new extract(rest, /\/\*/, '*/', commentMode, 'W3CSSCOMMENTPOS')
202 | rest = comment.rest
203 | while (rest.search('{') > -1) {
204 | s = rest.search('{')
205 | midz = rest.substr(s + 1)
206 | cc = 1
207 | c = 0
208 | for (i = 0; i < midz.length; i++) {
209 | if (midz.substr(i, 1) == '{') {
210 | cc++
211 | c++
212 | }
213 | if (midz.substr(i, 1) == '}') {
214 | cc--
215 | }
216 | if (cc == 0) {
217 | break
218 | }
219 | }
220 | if (cc != 0) {
221 | c = 0
222 | }
223 | e = s
224 | for (i = 0; i <= c; i++) {
225 | e = rest.indexOf('}', e + 1)
226 | }
227 | if (e == -1) {
228 | e = rest.length
229 | }
230 | done += rest.substring(0, s + 1)
231 | done += cssPropertyMode(rest.substring(s + 1, e))
232 | rest = rest.substr(e)
233 | }
234 | rest = done + rest
235 | rest = rest.replace(
236 | /{/g,
237 | '{'
238 | )
239 | rest = rest.replace(
240 | /}/g,
241 | '}'
242 | )
243 | for (i = 0; i < comment.arr.length; i++) {
244 | rest = rest.replace('W3CSSCOMMENTPOS', comment.arr[i])
245 | }
246 | return '' + rest + ''
247 | }
248 | function cssPropertyMode(txt) {
249 | var rest = txt,
250 | done = '',
251 | s,
252 | e,
253 | n,
254 | loop
255 | if (rest.indexOf('{') > -1) {
256 | return cssMode(rest)
257 | }
258 | while (rest.search(':') > -1) {
259 | s = rest.search(':')
260 | loop = true
261 | n = s
262 | while (loop == true) {
263 | loop = false
264 | e = rest.indexOf(';', n)
265 | if (rest.substring(e - 5, e + 1) == ' ') {
266 | loop = true
267 | n = e + 1
268 | }
269 | }
270 | if (e == -1) {
271 | e = rest.length
272 | }
273 | done += rest.substring(0, s)
274 | done += cssPropertyValueMode(rest.substring(s, e + 1))
275 | rest = rest.substr(e + 1)
276 | }
277 | return (
278 | '' + done + rest + ''
279 | )
280 | }
281 | function cssPropertyValueMode(txt) {
282 | var rest = txt,
283 | done = '',
284 | s
285 | rest =
286 | ':' + rest.substring(1)
287 | while (rest.search(/!important/i) > -1) {
288 | s = rest.search(/!important/i)
289 | done += rest.substring(0, s)
290 | done += cssImportantMode(rest.substring(s, s + 10))
291 | rest = rest.substr(s + 10)
292 | }
293 | result = done + rest
294 | if (
295 | result.substr(result.length - 1, 1) == ';' &&
296 | result.substr(result.length - 6, 6) != ' ' &&
297 | result.substr(result.length - 4, 4) != '<' &&
298 | result.substr(result.length - 4, 4) != '>' &&
299 | result.substr(result.length - 5, 5) != '&'
300 | ) {
301 | result =
302 | result.substring(0, result.length - 1) +
303 | ';'
306 | }
307 | return (
308 | '' + result + ''
309 | )
310 | }
311 | function cssImportantMode(txt) {
312 | return (
313 | '' +
316 | txt +
317 | ''
318 | )
319 | }
320 | function jsMode(txt) {
321 | var rest = txt,
322 | done = '',
323 | esc = [],
324 | i,
325 | cc,
326 | tt = '',
327 | sfnuttpos,
328 | dfnuttpos,
329 | compos,
330 | comlinepos,
331 | keywordpos,
332 | numpos,
333 | mypos,
334 | dotpos,
335 | y
336 | for (i = 0; i < rest.length; i++) {
337 | cc = rest.substr(i, 1)
338 | if (cc == '\\') {
339 | esc.push(rest.substr(i, 2))
340 | cc = 'W3JSESCAPE'
341 | i++
342 | }
343 | tt += cc
344 | }
345 | rest = tt
346 | y = 1
347 | while (y == 1) {
348 | sfnuttpos = getPos(rest, "'", "'", jsStringMode)
349 | dfnuttpos = getPos(rest, '"', '"', jsStringMode)
350 | compos = getPos(rest, /\/\*/, '*/', commentMode)
351 | comlinepos = getPos(rest, /\/\//, '
', commentMode)
352 | numpos = getNumPos(rest, jsNumberMode)
353 | keywordpos = getKeywordPos('js', rest, jsKeywordMode)
354 | dotpos = getDotPos(rest, jsPropertyMode)
355 | if (
356 | Math.max(
357 | numpos[0],
358 | sfnuttpos[0],
359 | dfnuttpos[0],
360 | compos[0],
361 | comlinepos[0],
362 | keywordpos[0],
363 | dotpos[0]
364 | ) == -1
365 | ) {
366 | break
367 | }
368 | mypos = getMinPos(
369 | numpos,
370 | sfnuttpos,
371 | dfnuttpos,
372 | compos,
373 | comlinepos,
374 | keywordpos,
375 | dotpos
376 | )
377 | if (mypos[0] == -1) {
378 | break
379 | }
380 | if (mypos[0] > -1) {
381 | done += rest.substring(0, mypos[0])
382 | done += mypos[2](rest.substring(mypos[0], mypos[1]))
383 | rest = rest.substr(mypos[1])
384 | }
385 | }
386 | rest = done + rest
387 | for (i = 0; i < esc.length; i++) {
388 | rest = rest.replace('W3JSESCAPE', esc[i])
389 | }
390 | return '' + rest + ''
391 | }
392 | function jsStringMode(txt) {
393 | return '' + txt + ''
394 | }
395 | function jsKeywordMode(txt) {
396 | return '' + txt + ''
397 | }
398 | function jsNumberMode(txt) {
399 | return '' + txt + ''
400 | }
401 | function jsPropertyMode(txt) {
402 | return '' + txt + ''
403 | }
404 | function getDotPos(txt, func) {
405 | var x,
406 | i,
407 | j,
408 | s,
409 | e,
410 | arr = [
411 | '.',
412 | '<',
413 | ' ',
414 | ';',
415 | '(',
416 | '+',
417 | ')',
418 | '[',
419 | ']',
420 | ',',
421 | '&',
422 | ':',
423 | '{',
424 | '}',
425 | '../index.html',
426 | '-',
427 | '*',
428 | '|',
429 | '%'
430 | ],
431 | cc
432 | s = txt.indexOf('.')
433 | if (s > -1) {
434 | x = txt.substr(s + 1)
435 | for (j = 0; j < x.length; j++) {
436 | cc = x[j]
437 | for (i = 0; i < arr.length; i++) {
438 | if (cc.indexOf(arr[i]) > -1) {
439 | e = j
440 | return [s + 1, e + s + 1, func]
441 | }
442 | }
443 | }
444 | }
445 | return [-1, -1, func]
446 | }
447 | function getMinPos() {
448 | var i,
449 | arr = []
450 | for (i = 0; i < arguments.length; i++) {
451 | if (arguments[i][0] > -1) {
452 | if (arr.length == 0 || arguments[i][0] < arr[0]) {
453 | arr = arguments[i]
454 | }
455 | }
456 | }
457 | if (arr.length == 0) {
458 | arr = arguments[i]
459 | }
460 | return arr
461 | }
462 | function getKeywordPos(typ, txt, func) {
463 | var words,
464 | i,
465 | pos,
466 | rpos = -1,
467 | rpos2 = -1,
468 | patt
469 | if (typ == 'js') {
470 | words = [
471 | 'abstract',
472 | 'arguments',
473 | 'boolean',
474 | 'break',
475 | 'byte',
476 | 'case',
477 | 'catch',
478 | 'char',
479 | 'class',
480 | 'const',
481 | 'continue',
482 | 'debugger',
483 | 'default',
484 | 'delete',
485 | 'do',
486 | 'double',
487 | 'else',
488 | 'enum',
489 | 'eval',
490 | 'export',
491 | 'extends',
492 | 'false',
493 | 'final',
494 | 'finally',
495 | 'float',
496 | 'for',
497 | 'function',
498 | 'goto',
499 | 'if',
500 | 'implements',
501 | 'import',
502 | 'in',
503 | 'instanceof',
504 | 'int',
505 | 'interface',
506 | 'let',
507 | 'long',
508 | 'NaN',
509 | 'native',
510 | 'new',
511 | 'null',
512 | 'package',
513 | 'private',
514 | 'protected',
515 | 'public',
516 | 'return',
517 | 'short',
518 | 'static',
519 | 'super',
520 | 'switch',
521 | 'synchronized',
522 | 'this',
523 | 'throw',
524 | 'throws',
525 | 'transient',
526 | 'true',
527 | 'try',
528 | 'typeof',
529 | 'var',
530 | 'void',
531 | 'volatile',
532 | 'while',
533 | 'with',
534 | 'yield'
535 | ]
536 | }
537 | for (i = 0; i < words.length; i++) {
538 | pos = txt.indexOf(words[i])
539 | if (pos > -1) {
540 | patt = /\W/g
541 | if (
542 | txt.substr(pos + words[i].length, 1).match(patt) &&
543 | txt.substr(pos - 1, 1).match(patt)
544 | ) {
545 | if (pos > -1 && (rpos == -1 || pos < rpos)) {
546 | rpos = pos
547 | rpos2 = rpos + words[i].length
548 | }
549 | }
550 | }
551 | }
552 | return [rpos, rpos2, func]
553 | }
554 | function getPos(txt, start, end, func) {
555 | var s, e
556 | s = txt.search(start)
557 | e = txt.indexOf(end, s + end.length)
558 | if (e == -1) {
559 | e = txt.length
560 | }
561 | return [s, e + end.length, func]
562 | }
563 | function getNumPos(txt, func) {
564 | var arr = [
565 | '
',
566 | ' ',
567 | ';',
568 | '(',
569 | '+',
570 | ')',
571 | '[',
572 | ']',
573 | ',',
574 | '&',
575 | ':',
576 | '{',
577 | '}',
578 | '../index.html',
579 | '-',
580 | '*',
581 | '|',
582 | '%',
583 | '='
584 | ],
585 | i,
586 | j,
587 | c,
588 | startpos = 0,
589 | endpos,
590 | word
591 | for (i = 0; i < txt.length; i++) {
592 | for (j = 0; j < arr.length; j++) {
593 | c = txt.substr(i, arr[j].length)
594 | if (c == arr[j]) {
595 | if (
596 | c == '-' &&
597 | (txt.substr(i - 1, 1) == 'e' || txt.substr(i - 1, 1) == 'E')
598 | ) {
599 | continue
600 | }
601 | endpos = i
602 | if (startpos < endpos) {
603 | word = txt.substring(startpos, endpos)
604 | if (!isNaN(word)) {
605 | return [startpos, endpos, func]
606 | }
607 | }
608 | i += arr[j].length
609 | startpos = i
610 | i -= 1
611 | break
612 | }
613 | }
614 | }
615 | return [-1, -1, func]
616 | }
617 | }
618 |
--------------------------------------------------------------------------------
/src/web/css/index.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'VarelaRound-Regular';
3 | src: url('../../assets/fonts/VarelaRound-Regular.ttf');
4 | }
5 |
6 | @font-face {
7 | font-family: 'FiraMono-Regular';
8 | src: url('../../assets/fonts/FiraMono-Regular.ttf');
9 | }
10 |
11 | html {
12 | padding: 0;
13 | margin: 0;
14 | }
15 |
16 | body {
17 | padding: 0;
18 | margin: 0;
19 | background-attachment: fixed;
20 | overflow: auto;
21 | }
22 |
23 | /* The Modal */
24 | .modal {
25 | display: none;
26 | /* Hidden by default */
27 | position: fixed;
28 | /* Stay in place */
29 | z-index: 1;
30 | /* Sit on top */
31 | padding-top: 100px;
32 | /* Location of the box */
33 | left: 0;
34 | top: 0;
35 | width: 100%;
36 | /* Full width */
37 | height: 100%;
38 | /* Full height */
39 | overflow: auto;
40 | /* Enable scroll if needed */
41 | background-color: rgb(0, 0, 0);
42 | /* Fallback color */
43 | background-color: rgba(0, 0, 0, 0.4);
44 | /* Black w/ opacity */
45 | }
46 |
47 | /* Modal Content */
48 | .modal-content {
49 | position: relative;
50 | background-color: #fefefe;
51 | margin: auto;
52 | border: 1px solid #888;
53 | width: 80%;
54 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
55 | -webkit-animation-name: animatetop;
56 | -webkit-animation-duration: 0.4s;
57 | animation-name: animatetop;
58 | animation-duration: 0.4s;
59 | padding: 10px 15px 10px 15px;
60 | }
61 |
62 | /* Add Animation */
63 | @-webkit-keyframes animatetop {
64 | from {
65 | top: -300px;
66 | opacity: 0;
67 | }
68 |
69 | to {
70 | top: 0;
71 | opacity: 1;
72 | }
73 | }
74 |
75 | @keyframes animatetop {
76 | from {
77 | top: -300px;
78 | opacity: 0;
79 | }
80 |
81 | to {
82 | top: 0;
83 | opacity: 1;
84 | }
85 | }
86 |
87 | /* The Close Button */
88 | .modal-close {
89 | color: black;
90 | float: right;
91 | font-size: 36px;
92 | font-weight: bold;
93 | padding: 10px;
94 | }
95 |
96 | .modal-close:hover,
97 | .modal-close:focus {
98 | color: red;
99 | text-decoration: none;
100 | cursor: pointer;
101 | }
102 | /* End Modal */
103 |
104 | /* Tooltip */
105 | .tooltip {
106 | position: relative;
107 | display: inline-block;
108 | }
109 |
110 | .tooltip .tooltiptext {
111 | visibility: hidden;
112 | width: 120px;
113 | background-color: #6060cc;
114 | color: #fff;
115 | text-align: center;
116 | border-radius: 6px;
117 | padding: 5px 0;
118 | position: absolute;
119 | z-index: 1;
120 | top: -5px;
121 | left: 110%;
122 | }
123 |
124 | /* The tootltip arrow */
125 | .tooltip .tooltiptext::after {
126 | content: '';
127 | position: absolute;
128 | top: 50%;
129 | right: 100%;
130 | margin-top: -5px;
131 | border-width: 5px;
132 | border-style: solid;
133 | border-color: transparent #6060cc transparent transparent;
134 | }
135 |
136 | .tooltip:hover .tooltiptext {
137 | visibility: visible;
138 | }
139 |
140 | /* Main body */
141 | .parallax {
142 | /* The image used */
143 | /* background-image: url("book-background0.jpg"); */
144 | background-color: rgb(42, 128, 139);
145 |
146 | /* Set a specific height */
147 | height: 100vh;
148 |
149 | /* Create the parallax scrolling effect */
150 | background-attachment: fixed;
151 | background-position: center;
152 | background-repeat: no-repeat;
153 | background-size: cover;
154 | }
155 |
156 | #root {
157 | display: flex;
158 | flex-shrink: 4;
159 | height: 100vh;
160 | padding: 0;
161 | margin: 0;
162 | left: 0;
163 | top: 0;
164 | bottom: 0;
165 | right: 0;
166 | flex-direction: column;
167 | /* flex-wrap: wrap; */
168 | }
169 |
170 | #main {
171 | display: flex;
172 | flex-shrink: 3;
173 | height: 90vh;
174 | width: 100vw;
175 | }
176 |
177 | /* End main body */
178 |
179 | /* Auxilliary button */
180 | #auxbtn {
181 | position: fixed;
182 | top: 0;
183 | right: 0;
184 | color: red;
185 | display: none;
186 | transition: all 0.3s;
187 | font-size: large;
188 | background: rgba(102, 102, 102, 0);
189 | /* border: 1px solid red; */
190 | height: 50px;
191 | width: 50px;
192 | }
193 |
194 | #auxbtn:hover,
195 | #auxbtn:focus {
196 | background: white;
197 | border: none;
198 | color: black;
199 | }
200 |
201 | /* Topnav */
202 | .topnav {
203 | background-color: #f1f1f1;
204 | width: 100%;
205 | height: 10%;
206 | overflow-x: auto;
207 | overflow-y: hidden;
208 | display: flex;
209 | align-content: center;
210 | vertical-align: middle;
211 | transition: all 0.3s;
212 | }
213 |
214 | .topnav a {
215 | float: left;
216 | color: #f2f2f2;
217 | text-align: center;
218 | text-decoration: none;
219 | font-size: 16px;
220 | display: flex;
221 | align-content: center;
222 | background: #4286f4;
223 | height: 20px;
224 | padding-top: 12px;
225 | padding-bottom: 15px;
226 | margin: auto 8px auto 8px;
227 | font-family: sans-serif;
228 | }
229 |
230 | .topnav a:hover {
231 | background-color: white;
232 | color: black;
233 | }
234 |
235 | .topnav a.active {
236 | background-color: #4caf50;
237 | color: white;
238 | }
239 |
240 | #settings-btn,
241 | #submit-btn,
242 | .download-btn,
243 | #write-btn,
244 | #read-btn,
245 | #balance-btn {
246 | padding: 12px 15px;
247 | cursor: pointer;
248 | }
249 |
250 | #submit-btn {
251 | background: #1fa01f;
252 | padding: 12px 15px 12px 15px;
253 | }
254 |
255 | #preview-btn {
256 | background: rgb(201, 20, 20);
257 | }
258 |
259 | #settings-btn {
260 | background: rgb(15, 15, 15);
261 | padding: 12px 15px 12px 15px;
262 | font-size: 18px;
263 | }
264 |
265 | #write-btn,
266 | #read-btn,
267 | #balance-btn {
268 | background-color: #4286f4;
269 | }
270 |
271 | #logo {
272 | margin-left: auto;
273 | background: rgba(102, 102, 102, 0);
274 | }
275 |
276 | #logo-img {
277 | transform: translate(0, -12px);
278 | }
279 |
280 | #upbtn {
281 | background: rgb(201, 20, 20);
282 | }
283 |
284 | #write-btn:hover,
285 | #read-btn:hover,
286 | #balance-btn:hover,
287 | #settings-btn:hover,
288 | #preview-btn:hover,
289 | #submit-btn:hover,
290 | #upbtn:hover {
291 | background: white;
292 | cursor: pointer;
293 | }
294 |
295 | .settings-modal {
296 | font-family: monospace;
297 | }
298 |
299 | .setting {
300 | font-family: monospace;
301 | margin: 2px;
302 | padding: 15px;
303 | }
304 |
305 | /* Topnav end */
306 |
307 | /* Main div (editor-div + output-div) */
308 | .main-div {
309 | background: rgba(102, 102, 102, 0);
310 | color: white;
311 | transition: all 0.3s;
312 | width: 50%;
313 | height: 100%;
314 | overflow-y: auto;
315 | overflow-x: hidden;
316 | font-family: monospace;
317 | text-align: justify;
318 | resize: horizontal;
319 | box-sizing: border-box;
320 | }
321 |
322 | #editor-div {
323 | overflow: hidden;
324 | left: 0;
325 | }
326 |
327 | #output-div {
328 | right: 0;
329 | }
330 |
331 | .right-align-top {
332 | margin-left: auto;
333 | margin-right: 8px;
334 | }
335 |
336 | .content-opts {
337 | padding-left: 15px;
338 | padding-right: 15px;
339 | }
340 |
341 | button {
342 | outline: none;
343 | border: none;
344 | padding: 5px;
345 | cursor: pointer;
346 | }
347 |
348 | button:hover {
349 | background: #74637f;
350 | color: #eee;
351 | }
352 |
353 | .editor {
354 | font-family: FiraMono-Regular;
355 | height: 100%;
356 | width: 100%;
357 | background: white;
358 | color: rgb(40, 40, 40);
359 | margin: 0px;
360 | border-radius: 0;
361 | resize: none;
362 | overflow: auto;
363 | font-size: 16px;
364 | padding: 25px;
365 | box-sizing: border-box;
366 | outline: none;
367 | }
368 |
369 | /*#alert-area {
370 | position: fixed;
371 | bottom: 0;
372 | width: 100vw;
373 | height: 20px;
374 | background-color: rgb(42, 128, 139);
375 | font-size: large;
376 | text-align: center;
377 | color: #fff;
378 | padding: 10px;
379 | }*/
380 |
381 | #alerts-area {
382 | display: none;
383 | }
384 |
385 | /* Style the tab */
386 | .tab {
387 | overflow-x: auto;
388 | /* border: 1px solid #ccc; */
389 | background-color: #f1f1f1;
390 | height: 8%;
391 | /*position: fixed;*/
392 | }
393 |
394 | /* Style the buttons inside the tab */
395 | .tab button {
396 | background-color: inherit;
397 | float: left;
398 | border: none;
399 | outline: none;
400 | cursor: pointer;
401 | padding: 10px 12px;
402 | transition: 0.3s;
403 | font-size: 16px;
404 | width: 100px;
405 | color: black;
406 | height: 100%;
407 | }
408 |
409 | /* Change background color of buttons on hover */
410 | .tab button:hover {
411 | background-color: #ddd;
412 | }
413 |
414 | /* Create an active/current tablink class */
415 | .tab button.active {
416 | /* background-color: rgb(201, 20, 20); */
417 | color: black;
418 | box-sizing: border-box;
419 | /* border-top: 3px solid rgb(201, 20, 20); */
420 | border-bottom: 3px solid rgb(201, 20, 20);
421 | }
422 |
423 | /* Style the tab content */
424 | .tabcontent1,
425 | .tabcontent2 {
426 | display: none;
427 | padding: 6px 12px;
428 | border-top: none;
429 | min-height: 88%;
430 | }
431 |
432 | .tabcontent1 {
433 | padding: 0;
434 | height: 92%;
435 | }
436 |
437 | .tabcontent2 {
438 | height: 100%;
439 | }
440 |
441 | .main-div a {
442 | color: white;
443 | text-decoration: none;
444 | padding: 1px;
445 | margin: 3px 0 3px 0;
446 | border-bottom: 1px dashed white;
447 | /*font-weight: bolder;*/
448 | }
449 |
450 | .main-div a:hover {
451 | background: #f1f1f1;
452 | color: #3e3e3e;
453 | text-decoration: none;
454 | }
455 |
456 | /* Resizer */
457 | #resizer {
458 | width: 100%;
459 | height: 100%;
460 | border-right: 3px solid #4286f4;
461 | box-sizing: border-box;
462 | }
463 |
464 | /* Output area */
465 | #if_r-output-area {
466 | display: none;
467 | }
468 |
469 | /* General formatting */
470 | .code:not(#if_r-output-area),
471 | code:not(#if_r-output-area) {
472 | padding: 5px;
473 | background: lightgrey;
474 | color: black;
475 | font-family: monospace;
476 | line-height: 1.9;
477 | margin: 10px auto;
478 | }
479 |
480 | .highlighted {
481 | background: lightgrey;
482 | padding: 10px;
483 | margin: 10px auto;
484 | color: blue;
485 | }
486 |
487 | .plain-text {
488 | font-family: sans-serif;
489 | font-size: normal;
490 | line-height: 1.3;
491 | }
492 |
493 | .symlink {
494 | cursor: pointer;
495 | }
496 |
497 | .main-div h3 {
498 | font-family: sans-serif;
499 | }
500 |
501 | /* Tools */
502 | .tool-btn {
503 | display: block;
504 | width: 80%;
505 | margin: 3px auto;
506 | padding: 10px;
507 | border: none;
508 | color: black;
509 | background-color: white;
510 | font: 18px 'Opens Sans', sans-serif;
511 | letter-spacing: 1px;
512 | appearance: none;
513 | cursor: pointer;
514 | margin-bottom: 15px;
515 | }
516 |
517 | @media (max-width: 850px) {
518 | #root {
519 | flex-wrap: wrap;
520 | }
521 |
522 | .main-div {
523 | width: 100vw;
524 | height: 90vh;
525 | }
526 | }
527 |
--------------------------------------------------------------------------------
/src/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IF
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/web/index.template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | <%= htmlWebpackPlugin.options.title %>
6 |
7 |
8 |
9 |
10 |
16 |
17 |
18 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/web/js/IFError.js:
--------------------------------------------------------------------------------
1 | class IFError extends Error {
2 | constructor (str = '', code, dontLogInstantly) {
3 | this.message = str
4 | this.expectedLine = this.message.split('\n')[2]
5 | this.code = code || null
6 | if (!dontLogInstantly) this.log()
7 | }
8 |
9 | log () {
10 | console.log('Error at:\n', this.expectedLine)
11 | };
12 | }
13 |
14 | export default IFError
15 |
--------------------------------------------------------------------------------
/src/web/js/globals.js:
--------------------------------------------------------------------------------
1 | const ua = window.navigator.userAgent.toLowerCase()
2 | const isIE = !!ua.match(/msie|trident\/7|edge/)
3 |
4 | const clickEvent = new Event('click')
5 |
6 | const tracking = [{ 0: 's', 1: 'Control' }]
7 | const down = []
8 |
9 | const instructions = `/*---------- Tutorial Story ----------*/
10 |
11 | settings>
12 |
13 | @referrable false
14 |
15 | @startAt 1
16 |
17 | @fullTimer 30000 [[3]]
18 |
19 | \${title=Tutorial Story}
20 | \${new=100}
21 | \${ten=12}
22 | \${one=a string}
23 | \${rand=random(5,10)}
24 |
25 |
31 | @first 3
32 | @music https://www.soundhelix.com/examples/mp3/SoundHelix-Song-10.mp3
33 | @sections 3
34 | @name first scene
35 |
38 | @first 2
39 | @music https://www.soundhelix.com/examples/mp3/SoundHelix-Song-8.mp3
40 | @sections 2
41 | @name second scene
42 |
45 | @first 1
46 | @music https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3
47 | @sections 1
48 | @name third scene
49 |
55 |
56 | secset>
57 | @timer 5000 [[3]]
58 | The first section Start scene two [[scene:2]] Go to the next section [[2]] Input Choice: __input \${__one} [[3]]
76 | tt>The second section Start third scene [[scene:3]] Go to next section [[3]]
88 | tt>The third \${title} Go to previous section [[2]] Start the first scene \${__new=ten} [[scene:1]]
106 |
107 |
108 | Press Top-left or
Ctrl + click
▶ Run to start a live preview of the story.
109 |
110 |
111 |
112 | View/Fork
113 |
114 | the core Parser + Renderer
115 | or
116 |
117 | the webapp This website (editors + preview tools)
118 |
119 | on GitHub.
120 |
121 |
122 |
123 | You can use Markdown to format your story.
124 | Markdown cheat-sheet
125 |
126 |
127 | Embedding
128 | Within the head tag, add the following.
129 |
130 | <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/plytonrexus/if-script@v0.4-alpha/downloadable/if_r.css">
131 |
132 |
133 | In your body, add the following scripts.
134 |
135 | <div id="if_r-output-area"></div>
136 |
137 | <script src="Story.js"></script>
138 |
139 | <script src="https://cdn.jsdelivr.net/gh/plytonrexus/if-script@v0.4-alpha/js/if_r-terp.js"></script>
140 |
141 | <script src="https://cdn.jsdelivr.net/npm/showdown@1.9.1/dist/showdown.min.js"></script>
142 |
143 | <script>
144 | IF.methods.loadStory(IF.story);
145 | </script>
146 |
147 |
148 |
149 | The indentation does not matter.
150 |
151 | Comments
152 |
153 | A comment is a text block that is not integrated into the final model of the story.
154 | Comments exist to help write cleaner stories.
155 | For example, by pointing out purposes of certain portions of the story.
156 |
157 | /*
158 | A
159 |
160 | multi-line
161 |
162 | comment
163 | */
164 |
165 |
166 | Variables
167 |
168 | Variables truly create dynamic stories.
169 | You can use variables to store a lot of things like character names,
170 | inventory items, visited scenes, number of things and many others.
171 | You can then display the values of these variables anywhere like so:
172 |
173 |
174 |
175 | Your name is \${name}.
176 |
177 |
178 |
179 | Obviously, here, variable name
was used to store the name of a character.
180 |
181 | The variables can also be used in conditional logic to determine which choices
182 | and (in a future release) what paragraphs of a section's
183 | text should be visible to the reader.
184 | You can find more about this under the choices heading.
185 |
186 |
187 |
188 | You can assign variables beforehand inside story settings.
189 |
190 |
191 |
192 | \${name='Felicity'}
193 |
194 |
195 | It is recommended (albeit not required) to keep the
196 | title
variable set as the title of the story.
197 |
198 |
199 | Story Settings
200 |
201 | Story settings allow you to customise the overall experience of the story.
202 | All of the settings are optional.
203 | The available settings are:
204 |
205 | @startAt
decides the starting section of a story. (Default: 1)
206 | @referrable
decides if the older sections remain visible once the reader moves to a new section. (Default: false)
207 | @fullTimer
decides the amount of time alloted for completing a story. (Default: none)
208 |
209 |
210 |
211 | settings>
212 |
213 | @startAt
214 |
215 | @referrable
216 |
217 | @fullTimer
218 |
219 | <settings
220 |
221 |
222 | Scenes
223 |
224 | Scenes are collections of sections
225 |
226 | scene>
227 |
228 | @first section_number (optional)
229 |
230 | @music link (optional)
231 |
232 | @sections space_seperated_section_numbers
233 |
234 | @name custom_name_for_scene
235 |
236 | <scene
237 |
238 |
239 | Sections
240 |
241 | Sections are independent locations/situations in a story.
242 | These can be reached through choices.
243 | Each section can have its own set of settings that allow it to have separate timers
244 | that send the reader to a separate section if they do not choose within specified time,
245 | and its own music. These features are particularly helpful in dramatic situations.
246 |
247 |
248 | ss>
249 |
250 | tt> Section Title <tt
251 |
252 | Section text.
253 |
254 | In paragraphs.
255 |
256 | You can use variables here.
257 |
258 | /*
259 |
260 | You can write choices about now.
261 |
262 | Read on to find out how to create them.
263 |
264 | */
265 |
266 | <ss
267 |
268 |
269 | Choices
270 |
271 | Choice are the sole method to navigate a story by reaching
272 | sections or scenes.
273 | To send to a section:
274 |
275 |
276 | ch>
277 |
278 | You are named five [[5]]
279 |
280 | <ch
281 |
282 |
283 |
284 | To send to the beginning of a scene:
285 |
286 |
287 | ch>
288 |
289 | You are named five [[scene:5]]
290 |
291 | <ch
292 |
293 |
294 |
295 | Choices can assign variables.
296 |
297 |
298 |
299 | ch>
300 |
301 | Choose 'Felicity' as your name \${__name} [[5]]
302 |
303 | <ch
304 |
305 |
306 |
307 | Choices can also have input boxes. These input boxes can be used to
308 | take in custom values from the user and then stored in variables for
309 | later use.
310 |
311 |
312 |
313 | ch>
314 |
315 | Type in your name here: __input \${__name} [[5]]
316 |
317 | <ch
318 |
319 |
320 |
321 | Choices can have conditions. Only if these conditions are met is the choice displayed.
322 | Available operators are:
323 |
324 | -
var1 == var2
Both are equal
325 | -
var1 != var2
Both are inequal
326 | -
var1 >= var2
First is greater than or equal to second
327 | -
var1 <= var2
Second is greater than or equal to first
328 | -
var1 > var2
First is greater than second
329 | -
var1 < var2
Second is greater than first
330 |
331 |
332 |
333 |
334 | ch>
335 |
336 | \${__if name == "" || namePower <= 0}
337 |
338 | Type in your name here __input \${__name} [[5]]
339 |
340 | <ch
341 |
342 |
343 |
344 | Choices can also do actions like
345 |
346 |
347 | -
\${__var1 = var2}
Assignment
348 | -
\${__var1 + var2}
Addition
349 | -
\${__var1 - var2}
Subtraction
350 | -
\${__var1 * var2}
Multiplication
351 | -
\${__var1 / var2}
Division
352 |
353 |
354 | In each of these, the first variable is assigned values that result from the operation.
355 |
356 |
357 |
358 | ch>
359 |
360 | The power of your name goes up 10 units and your health
361 | is multiplied \${namePower} times.
362 |
363 | \${__namePower + 10} \${__health * namePower} [[5]]
364 |
365 | <ch
366 |
367 | `
368 |
369 | const settingsHtml = `
370 |
371 | Settings
372 | `
373 |
374 | export {
375 | ua,
376 | isIE,
377 | clickEvent,
378 | tracking,
379 | down,
380 | instructions,
381 | statsInstructions,
382 | helpHtml,
383 | settingsHtml
384 | }
385 |
--------------------------------------------------------------------------------
/src/web/js/index.js:
--------------------------------------------------------------------------------
1 | import IFScript from 'if-script-core/src/IFScript.mjs'
2 | import { clickEvent, down, helpHtml, statsInstructions, tracking } from './globals'
3 | import w3CodeColor from '../../lib/w3Highlighter'
4 | import logo from '../../assets/images/if-logo-nobg.png'
5 | import instructions from 'if-script-core/test/examples/introduction.js'
6 | import '../css/index.css'
7 |
8 | const IF = new IFScript(IFScript.versions().STREAM)
9 | IF.start()
10 | const parseText = IF.parser.parseText
11 | const interpreter = new IF.interpreter()
12 |
13 | const $ = document.querySelector.bind(document)
14 | const $All = document.querySelectorAll.bind(document)
15 | const root = $('#root')
16 |
17 | console.log('Serving on: ' + __webpack_public_path__)
18 |
19 | const localStorageKeys = {
20 | storyText: 'if_r-story-text',
21 | statsText: 'if_r-story-stats',
22 | schemePreference: 'if_r-scheme-preference',
23 | modePreference: 'if_r-mode-preference',
24 | viewPreference: 'if_r-view-preference',
25 | objectStorage: 'if_r-if-object',
26 | editorPreference: 'if_r-editor-preference',
27 | outputPreference: 'if_r-output-preference'
28 | }
29 |
30 | const refs = {
31 | darkScheme: 'dark',
32 | lightScheme: 'light'
33 | }
34 |
35 | /// ////////////////////////////////
36 | // //
37 | // HELPER FUNCTIONS //
38 | // //
39 | /// ////////////////////////////////
40 |
41 | function fetchFile (addr, site) {
42 | fetch(addr)
43 | .then((response) => {
44 | if (!response.ok) {
45 | throw new Error('Network response was not ok')
46 | }
47 | return response.blob()
48 | })
49 | .then((blob) => {
50 | IF[site] = window.URL.createObjectURL(blob)
51 | })
52 | .catch((err) => console.log('Some fetch error occured.'))
53 | }
54 |
55 | function lread (key = localStorageKeys.storyText) {
56 | return localStorage.getItem(key)
57 | }
58 |
59 | function lwrite (key = localStorageKeys.storyText, value = instructions) {
60 | return localStorage.setItem(key, value)
61 | }
62 |
63 | function showAlert (text) {
64 | const alertArea = $('#alerts-area')
65 | alertArea.style.display = 'block'
66 | alertArea.innerHTML = text
67 | }
68 |
69 | function insertAtCursor (field, value) {
70 | // For textarea editor
71 | if (document.selection) {
72 | field.focus()
73 | let sel = document.selection.createRange()
74 | sel.text = value
75 | } else if (field.selectionStart || field.selectionStart == '0') {
76 | const startPos = field.selectionStart
77 | const endPos = field.selectionEnd
78 | field.value =
79 | field.value.substring(0, startPos) +
80 | value +
81 | field.value.substring(endPos, field.value.length)
82 | field.selectionStart = startPos + value.length
83 | field.selectionEnd = startPos + value.length
84 | } else {
85 | field.value += value
86 | }
87 | }
88 |
89 | function insertTextAtCaret (text) {
90 | // For editablecontent elements
91 | let sel, range
92 | if (window.getSelection) {
93 | sel = window.getSelection()
94 | if (sel.getRangeAt && sel.rangeCount) {
95 | range = sel.getRangeAt(0)
96 | range.deleteContents()
97 | range.insertNode(document.createTextNode(text))
98 | }
99 | } else if (document.selection && document.selection.createRange) {
100 | document.selection.createRange().text = text
101 | }
102 | }
103 |
104 | function formatDoc (cmd, value) {
105 | // For editablecontent elements
106 | if ($('#if_r-input-area').style.display !== 'none') {
107 | document.execCommand(cmd, false, value)
108 | $('#if_r-input-area').focus()
109 | }
110 | }
111 |
112 | function showModal (html, attrs, styles, ...nodes) {
113 | // todo: implement unclosabillity
114 | const modal = $('#modal')
115 | modal.style.display = 'block'
116 |
117 | const content = $('.modal-content')
118 | content.innerHTML = `×
${
119 | html ?? ''
120 | }`
121 |
122 | if (nodes) {
123 | nodes.forEach((el) => content.appendChild(el))
124 | }
125 |
126 | Object.keys(styles).forEach((sty) => (content.style[sty] = styles[sty]))
127 |
128 | window.onclick = function (event) {
129 | if (event.target === modal) {
130 | modal.style.display = 'none'
131 | }
132 | }
133 |
134 | $('.modal-close').onclick = function () {
135 | modal.style.display = 'none'
136 | }
137 | }
138 |
139 | function closeModal (node) {
140 | if (node) return (node.style.display = 'none')
141 | $('#modal').style.display = 'none'
142 | }
143 |
144 | function useMode (mode) {
145 | const modePref = localStorageKeys.modePreference
146 |
147 | if (mode === 'compact') {
148 | $('.topnav').style.height = '0'
149 | $('.main-div').style.height = '100vh'
150 | $('#output-div').style.height = '100vh'
151 | $('#editor-div').style.height = '100vh'
152 |
153 | const aux = $('#auxbtn')
154 | aux.style.display = 'block'
155 | aux.onclick = function () {
156 | useMode('regular')
157 | aux.onclick = ''
158 | aux.style.display = 'none'
159 | }
160 |
161 | lwrite(modePref, 'compact')
162 | } else if (mode === 'regular') {
163 | $('.topnav').style.height = '10vh'
164 | $('.main-div').style.height = '90vh'
165 | $('#output-div').style.height = '90vh'
166 | $('#editor-div').style.height = '90vh'
167 |
168 | lwrite(modePref, 'regular')
169 | }
170 | }
171 |
172 | function useScheme (type) {
173 | const editor = $('#if_r-input-area')
174 | const statsEditor = $('#if_r-stats-editor')
175 | const top = $('.topnav')
176 | const tabs = $All('.tab')
177 | const tBtn = $All('.tab button')
178 | const tabCon2 = $('#output-div')
179 |
180 | if (type === refs.darkScheme) {
181 | editor.style.background = 'rgb(50, 50, 50)'
182 | editor.style.color = 'white'
183 |
184 | statsEditor.style.background = 'rgb(50, 50, 50)'
185 | statsEditor.style.color = 'white'
186 |
187 | top.style.background = 'rgb(50, 50, 50)'
188 | top.style.color = 'black'
189 |
190 | tabs.forEach((el) => (el.style.background = 'rgb(50, 50, 50)'))
191 | tBtn.forEach((el) => (el.style.color = 'whitesmoke'))
192 |
193 | tBtnHover.forEach((el) => (el.style.color = 'red'))
194 |
195 | tabCon2.style.background = 'rgb(50, 50, 50)'
196 |
197 | // tBtnHov.style.color = "black";
198 |
199 | lwrite(localStorageKeys.schemePreference, refs.darkScheme)
200 | } else if (type === refs.lightScheme) {
201 | editor.style.background = 'whitesmoke'
202 | editor.style.color = 'rgb(40, 40, 40)'
203 |
204 | statsEditor.style.background = 'whitesmoke'
205 | statsEditor.style.color = 'rgb(40, 40, 40)'
206 |
207 | top.style.background = 'whitesmoke'
208 | // top.style.color = "black";
209 |
210 | tabs.forEach((el) => (el.style.background = 'whitesmoke'))
211 | tBtn.forEach((el) => (el.style.color = 'black'))
212 |
213 | tabCon2.style.background = 'rgba(102, 102, 102, 0)'
214 |
215 | lwrite(localStorageKeys.schemePreference, refs.lightScheme)
216 | }
217 | }
218 |
219 | function useView (view) {
220 | const $outputDiv = $('#output-div')
221 | const $editorDiv = $('#editor-div')
222 | const viewPref = localStorageKeys.viewPreference
223 |
224 | if (!view) console.warn('The view argument is required!')
225 |
226 | if (view === 'read') {
227 | $editorDiv.style.width = '0'
228 | $outputDiv.style.width = '100%'
229 |
230 | lwrite(viewPref, 'read')
231 | } else if (view === 'write') {
232 | $outputDiv.style.width = '0'
233 | $editorDiv.style.width = '100%'
234 |
235 | lwrite(viewPref, 'write')
236 | } else if (view === 'balanced') {
237 | $outputDiv.style.width = '50%'
238 | $editorDiv.style.width = '50%'
239 |
240 | lwrite(viewPref, 'balanced')
241 | }
242 | }
243 |
244 | function isLeftClick (event) {
245 | if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) {
246 | return false
247 | } else if ('buttons' in event) {
248 | return event.buttons === 1
249 | } else if ('which' in event) {
250 | return event.which === 1
251 | } else {
252 | return event.button === 1 || event.type === 'click'
253 | }
254 | }
255 |
256 | function handleSymlink (e) {
257 | if (e.ctrlKey) {
258 | const target = e.target.getAttribute('data-target-button')
259 | if (IF.DEBUG) {
260 | console.log(target)
261 | }
262 | if ($(target)) {
263 | $(target).click()
264 | }
265 | }
266 | }
267 |
268 | /// ////////////////////////////////
269 | // //
270 | // CREATE ELEMENT //
271 | // //
272 | /// ////////////////////////////////
273 |
274 | /**
275 | * Creates DOM nodes based on passed parameters.
276 | *
277 | * @param {string} name Name of the element
278 | * @param {object} attrs Attributes passed as an object
279 | * @param {object} styles CSS styles passed as an object
280 | * @param {object} listeners Other properties like "onclick"
281 | * and "innerHTML" of the element
282 | * @param {array} children Child nodes of the element
283 | * @returns {Node} element
284 | */
285 | function createElement (name, attrs, styles, listeners, children) {
286 | const ele = document.createElement(name)
287 | if (attrs) {
288 | Object.keys(attrs).forEach((attr) => {
289 | ele.setAttribute(attr, attrs[attr])
290 | })
291 | }
292 | if (styles) {
293 | Object.keys(styles).forEach((sty) => {
294 | ele.style[sty] = styles[sty]
295 | })
296 | }
297 | if (children) {
298 | children.forEach((child) => ele.appendChild(child))
299 | }
300 | if (listeners) {
301 | Object.keys(listeners).forEach((lsnr) => (ele[lsnr] = listeners[lsnr]))
302 | }
303 | return ele
304 | }
305 |
306 | /* Auxilliary Button */
307 | const auxBtn = createElement(
308 | 'button',
309 | {
310 | id: 'auxbtn',
311 | class: 'navbtn',
312 | title: 'Show menubar'
313 | },
314 | null,
315 | {
316 | innerHTML: '☰'
317 | }
318 | )
319 |
320 | /* Modal */
321 | /// ////////////////////////////////
322 | // //
323 | // MODAL //
324 | // //
325 | /// ////////////////////////////////
326 | const modal = createElement(
327 | 'div',
328 | {
329 | id: 'modal',
330 | class: 'modal'
331 | },
332 | null,
333 | null,
334 | [
335 | createElement('div', { class: 'modal-content' }, null, {
336 | innerHTML: '×'
337 | })
338 | ]
339 | )
340 |
341 | /* Topnav */
342 | /// ////////////////////////////////
343 | // //
344 | // TOPNAV //
345 | // //
346 | /// ////////////////////////////////
347 | const storyBtn = createElement(
348 | 'a',
349 | {
350 | class: 'download-btn tooltip navbtn',
351 | download: 'story.js'
352 | },
353 | null,
354 | {
355 | onclick: function () {
356 | if (!interpreter.run.story || Object.keys(interpreter.run.story).length <= 0) {
357 | return console.log('Parse a story at least once.')
358 | }
359 | const data = new Blob([`const IF = ${JSON.stringify(IF)}`], {
360 | type: 'text/javascript'
361 | })
362 | storyBtn.setAttribute('href', window.URL.createObjectURL(data))
363 | },
364 | innerHTML: `Download story
365 | Download the story file for embedding`
366 | }
367 | )
368 |
369 | const storyTextBtn = createElement(
370 | 'a',
371 | {
372 | class: 'download-btn tooltip navbtn',
373 | download: 'story.txt'
374 | },
375 | null,
376 | {
377 | onclick: function () {
378 | const data = new Blob([$('#if_r-input-area').innerText], {
379 | type: 'text/plain'
380 | })
381 | storyTextBtn.setAttribute('href', window.URL.createObjectURL(data))
382 | },
383 | innerHTML: `Download story text
384 | Download the text of the story`
385 | }
386 | )
387 |
388 | const parseBtn = createElement(
389 | 'a',
390 | {
391 | id: 'submit-btn',
392 | class: 'tooltip navbtn',
393 | title: 'Run'
394 | },
395 | null,
396 | {
397 | onclick: runSubmit,
398 | innerHTML: '▶ Run Play the story'
399 | }
400 | )
401 |
402 | const previewBtn = createElement(
403 | 'a',
404 | {
405 | id: 'preview-btn',
406 | class: 'download-btn tooltip navbtn'
407 | },
408 | null,
409 | {
410 | onclick: function () {
411 | if (Object.keys(interpreter.run.story).length <= 0)
412 | return showAlert('You haven\'t run a story yet.')
413 | else {
414 | window.open('preview')
415 | }
416 | },
417 | innerHTML: '👁 Preview'
418 | }
419 | )
420 |
421 | /// /////// SETTINGS //////////
422 | const darkBtn = createElement(
423 | 'button',
424 | {
425 | id: 'darkbtn',
426 | class: 'setting'
427 | },
428 | { background: 'rgb(50, 50, 50)', color: 'white' },
429 | {
430 | onclick: function (e) {
431 | useScheme(refs.darkScheme)
432 | e.target.style.display = 'none'
433 | $('#lightbtn').style.display = 'block'
434 | },
435 | innerHTML: 'Dark Mode'
436 | }
437 | )
438 |
439 | const lightBtn = createElement(
440 | 'button',
441 | {
442 | id: 'lightbtn',
443 | class: 'setting'
444 | },
445 | null,
446 | {
447 | onclick: function (e) {
448 | useScheme(refs.lightScheme)
449 | e.target.style.display = 'none'
450 | $('#darkbtn').style.display = 'block'
451 | },
452 | innerHTML: 'Light Mode'
453 | }
454 | )
455 |
456 | const settingsBtn = createElement(
457 | 'a',
458 | {
459 | id: 'settings-btn',
460 | class: 'tooltip navbtn'
461 | },
462 | null,
463 | {
464 | onclick: function () {
465 | const scheme = lread(localStorageKeys.schemePreference)
466 | showModal(
467 | null,
468 | null,
469 | { 'font-family': 'monospace' },
470 | createElement('div', null, null, { innerHTML: ' Settings
' }),
471 | createElement('h3', null, null, { innerHTML: 'Scheme Preference' }),
472 | lightBtn,
473 | darkBtn,
474 | createElement('h3', null, null, {
475 | innerHTML: 'Editor Alignment (Stub)'
476 | }),
477 | createElement('h3', null, null, { innerHTML: 'Font Size (Stub)' }),
478 | createElement('h3', null, null, { innerHTML: 'Font (Stub)' })
479 | )
480 | },
481 | innerHTML: '⚙Setings'
482 | }
483 | )
484 |
485 | /// /////// SETTINGS END //////////
486 |
487 | const readBtn = createElement(
488 | 'a',
489 | {
490 | id: 'read-btn',
491 | class: 'right-align-top content-opts tooltip navbtn'
492 | },
493 | null,
494 | {
495 | onclick: function () {
496 | useView('read')
497 | },
498 | innerHTML: '←Read View'
499 | }
500 | )
501 |
502 | const writeBtn = createElement(
503 | 'a',
504 | {
505 | id: 'write-btn',
506 | class: 'right-align-top content-opts tooltip navbtn'
507 | },
508 | null,
509 | {
510 | onclick: function () {
511 | useView('write')
512 | },
513 | innerHTML: '→Write View'
514 | }
515 | )
516 |
517 | const balanceBtn = createElement(
518 | 'a',
519 | {
520 | id: 'balance-btn',
521 | class: 'right-align-top content-opts tooltip navbtn'
522 | },
523 | null,
524 | {
525 | onclick: function () {
526 | useView('balanced')
527 | },
528 | innerHTML: '⇆Balanced View'
529 | }
530 | )
531 |
532 | const upBtn = createElement(
533 | 'a',
534 | {
535 | id: 'upbtn',
536 | class: 'right-align-top content-opts tooltip navbtn'
537 | },
538 | null,
539 | {
540 | onclick: function () {
541 | useMode('compact')
542 | },
543 | innerHTML: `×
544 | Close Menubar`
545 | }
546 | )
547 |
548 | const logoBtn = createElement(
549 | 'a',
550 | {
551 | id: 'logo',
552 | class: 'topnav-btn',
553 | href: __webpack_public_path__ || '/',
554 | title: 'IF-Script logo; Reload this page'
555 | },
556 | null,
557 | null,
558 | [
559 | (function () {
560 | let img =
561 | createElement('img', { src: logo, id: 'logo-img' }, { height: '50px', width: '50px' })
562 | img.src = logo
563 | return img
564 | })()
565 | ]
566 | )
567 |
568 | const topnav = createElement(
569 | 'div',
570 | {
571 | class: 'topnav'
572 | },
573 | null,
574 | null,
575 | [
576 | parseBtn,
577 | previewBtn,
578 | storyBtn,
579 | storyTextBtn,
580 | settingsBtn,
581 | readBtn,
582 | writeBtn,
583 | balanceBtn,
584 | upBtn,
585 | logoBtn
586 | ]
587 | )
588 |
589 | /// ////////////////////////////////
590 | // //
591 | // EDITOR - DIV //
592 | // //
593 | /// ////////////////////////////////
594 | /* div1 */
595 | const editor = createElement('textarea', {
596 | id: 'if_r-input-area',
597 | class: 'editor',
598 | contenteditable: 'true',
599 | spellcheck: 'false'
600 | })
601 |
602 | const statsEditor = createElement('textarea', {
603 | id: 'if_r-stats-editor',
604 | class: 'editor'
605 | })
606 |
607 | function openCity1 (evt, target) {
608 | let i, tabcontent, tablinks
609 | tabcontent = document.getElementsByClassName('tabcontent1')
610 | for (i = 0; i < tabcontent.length; i++) {
611 | tabcontent[i].style.display = 'none'
612 | }
613 | tablinks = document.getElementsByClassName('tablinks1')
614 | for (i = 0; i < tablinks.length; i++) {
615 | tablinks[i].className = tablinks[i].className.replace(' active', '')
616 | }
617 | $(target).style.display = 'block'
618 | evt.currentTarget.className += ' active'
619 |
620 | lwrite(
621 | localStorageKeys.editorPreference,
622 | '#' + evt.currentTarget.getAttribute('id')
623 | )
624 | }
625 |
626 | /// ////////////////////////////////
627 | // //
628 | // TABLINKS - 1 //
629 | // //
630 | /// ////////////////////////////////
631 | /* Tab links */
632 | const storyEd = createElement(
633 | 'button',
634 | {
635 | class: 'tablinks1 active',
636 | id: 'story-tablink',
637 | title: 'Story Editor'
638 | },
639 | null,
640 | {
641 | onclick: (e) => openCity1(e, '#Story'),
642 | innerHTML: 'Story'
643 | }
644 | )
645 |
646 | const statEd = createElement(
647 | 'button',
648 | {
649 | class: 'tablinks1',
650 | id: 'stats-tablink',
651 | title: 'Stats Editor'
652 | },
653 | null,
654 | {
655 | onclick: (e) => openCity1(e, '#Stats'),
656 | innerHTML: 'Stats'
657 | }
658 | )
659 |
660 | /// ////////////////////////////////
661 | // //
662 | // TABS - 1 //
663 | // //
664 | /// ////////////////////////////////
665 | /* Tabs drawer */
666 | const tabs1 = createElement(
667 | 'div',
668 | {
669 | class: 'tab'
670 | },
671 | null,
672 | null,
673 | [storyEd, statEd]
674 | )
675 |
676 | /// ////////////////////////////////
677 | // //
678 | // TAB - CONTENT - 1 //
679 | // //
680 | /// ////////////////////////////////
681 | /* Tab content */
682 | const storcon = createElement(
683 | 'div',
684 | {
685 | id: 'Story',
686 | class: 'tabcontent1'
687 | },
688 | null,
689 | null,
690 | [editor]
691 | )
692 |
693 | const statcon = createElement(
694 | 'div',
695 | {
696 | id: 'Stats',
697 | class: 'tabcontent1'
698 | },
699 | null,
700 | null,
701 | [statsEditor]
702 | )
703 |
704 | /// ////////////////////////////////
705 | // //
706 | // OUTPUT - DIV //
707 | // //
708 | /// ////////////////////////////////
709 | /* div2 */
710 | function openCity2 (evt, target) {
711 | let i, tabcontent, tablinks
712 | tabcontent = document.getElementsByClassName('tabcontent2')
713 | for (i = 0; i < tabcontent.length; i++) {
714 | tabcontent[i].style.display = 'none'
715 | }
716 | tablinks = document.getElementsByClassName('tablinks2')
717 | for (i = 0; i < tablinks.length; i++) {
718 | tablinks[i].className = tablinks[i].className.replace(' active', '')
719 | }
720 |
721 | $(target).style.display = 'block'
722 | evt.currentTarget.className += ' active'
723 |
724 | lwrite(
725 | localStorageKeys.outputPreference,
726 | '#' + evt.currentTarget.getAttribute('id')
727 | )
728 | }
729 |
730 | const output = createElement(
731 | 'div',
732 | {
733 | id: 'if_r-output-area'
734 | },
735 | {
736 | display: 'none'
737 | }
738 | )
739 |
740 | /// ////////////////////////////////
741 | // //
742 | // TABLINKS - 2 //
743 | // //
744 | /// ////////////////////////////////
745 | /* Tab links */
746 | const help = createElement(
747 | 'button',
748 | {
749 | class: 'tablinks2',
750 | id: 'help-tablink',
751 | title: 'Documentation for IF-Script'
752 | },
753 | null,
754 | {
755 | onclick: (e) => openCity2(e, '#Help'),
756 | innerHTML: 'Help'
757 | }
758 | )
759 |
760 | const tools = createElement(
761 | 'button',
762 | {
763 | class: 'tablinks2',
764 | id: 'tools-tablink',
765 | title: 'Tools for editing the story'
766 | },
767 | null,
768 | {
769 | onclick: (e) => openCity2(e, '#Tools'),
770 | innerHTML: 'Tools'
771 | }
772 | )
773 |
774 | const outputDiv = createElement(
775 | 'button',
776 | {
777 | class: 'tablinks2',
778 | id: 'output-tablink',
779 | title: 'Preview your story'
780 | },
781 | null,
782 | {
783 | onclick: (e) => openCity2(e, '#Output'),
784 | innerHTML: 'Output'
785 | }
786 | )
787 |
788 | /// ////////////////////////////////
789 | // //
790 | // TABS - 2 //
791 | // //
792 | /// ////////////////////////////////
793 | /* Tabs drawer */
794 | const tabs2 = createElement(
795 | 'div',
796 | {
797 | class: 'tab'
798 | },
799 | null,
800 | null,
801 | [help, tools, outputDiv]
802 | )
803 |
804 | /// ////////////////////////////////
805 | // //
806 | // TAB - CONTENT - 2 //
807 | // //
808 | /// ////////////////////////////////
809 | /* Tab content */
810 |
811 | const helpcon = createElement(
812 | 'div',
813 | {
814 | id: 'Help',
815 | class: 'tabcontent2'
816 | },
817 | null,
818 | {
819 | innerHTML: helpHtml
820 | }
821 | )
822 |
823 | /// ////////// TOOLS ///////////////
824 |
825 | const insertSectionBtn = createElement(
826 | 'button',
827 | {
828 | class: 'tool-btn'
829 | },
830 | null,
831 | {
832 | innerHTML: 'Add Section',
833 | onclick: function (event) {
834 | const sectionText = `
835 | ss>
836 | secset>
837 | @timer 10000
838 | @music link
839 | Untitled Section A choice [[1]] Another choice [[2]]
865 | @first section_number
866 | @music link
867 | @sections space_seperated_section_numbers
868 | @name custom_name_for_scene
869 | \${__if name == "" || namePower <= 0} Type in your name here __input \${__name} [[5]] Click Run to start playing!'
933 | }
934 | )
935 |
936 | const outcon = createElement(
937 | 'div',
938 | {
939 | id: 'Output',
940 | class: 'tabcontent2'
941 | },
942 | null,
943 | null,
944 | [instructDiv, output]
945 | )
946 |
947 | /// ////////////////////////////////
948 | // //
949 | // MAIN - DIVs //
950 | // //
951 | /// ////////////////////////////////
952 | /* Main divs */
953 | const div1 = createElement(
954 | 'div',
955 | {
956 | class: 'main-div',
957 | id: 'editor-div'
958 | },
959 | {
960 | left: '0'
961 | },
962 | null,
963 | [tabs1, storcon, statcon]
964 | )
965 | const div2 = createElement(
966 | 'div',
967 | {
968 | class: 'main-div',
969 | id: 'output-div'
970 | },
971 | {
972 | right: '0'
973 | },
974 | null,
975 | [tabs2, helpcon, toolcon, outcon]
976 | )
977 |
978 | const mainDiv = createElement(
979 | 'div',
980 | {
981 | id: 'main'
982 | },
983 | null,
984 | null,
985 | [div1, div2]
986 | )
987 |
988 | /// ////////////////////////////////
989 | // //
990 | // APPEND TO ROOT //
991 | // //
992 | /// ////////////////////////////////
993 | /* Append to root */
994 | root.appendChild(modal)
995 | root.appendChild(auxBtn)
996 | root.appendChild(topnav)
997 | root.appendChild(mainDiv)
998 |
999 | /// ////////////////////////////////
1000 | // //
1001 | // GETTING READY, FINALLY! //
1002 | // //
1003 | /// ////////////////////////////////
1004 | /* Getting ready... */
1005 |
1006 | /* Tab preferences */
1007 | $(lread(localStorageKeys.outputPreference) || '#help-tablink').dispatchEvent(
1008 | clickEvent
1009 | )
1010 | $(lread(localStorageKeys.editorPreference) || '#story-tablink').dispatchEvent(
1011 | clickEvent
1012 | )
1013 |
1014 | /* For textarea-style editors */
1015 | $('#if_r-input-area').value = lread(localStorageKeys.storyText) || instructions
1016 | $('#if_r-stats-editor').value =
1017 | lread(localStorageKeys.statsText) || statsInstructions
1018 | $All('.highlighted').forEach((ele) => w3CodeColor(ele))
1019 |
1020 | /* Colour scheme preferences */
1021 | if (lread(localStorageKeys.schemePreference)) {
1022 | useScheme(lread(localStorageKeys.schemePreference))
1023 | } else {
1024 | useScheme(refs.lightScheme)
1025 | }
1026 | /* Menubar preferences */
1027 | if (lread(localStorageKeys.modePreference)) {
1028 | useMode(lread(localStorageKeys.modePreference))
1029 | }
1030 | /* Read/Write View Preferences */
1031 | if (lread(localStorageKeys.viewPreference)) {
1032 | useView(lread(localStorageKeys.viewPreference))
1033 | }
1034 |
1035 | /* Ctrl + S Save implementation */
1036 | window.onkeydown = function (e) {
1037 | if (!down.includes(e.key)) down.push(e.key)
1038 | if (e.ctrlKey && !down.includes(tracking[0]['1'])) down.push('Control')
1039 |
1040 | if (down.length === 2 && down.includes(tracking[0]['0']) && e.ctrlKey) {
1041 | e.preventDefault()
1042 | saveStory($('#if_r-input-area').value)
1043 | saveStats($('#if_r-stats-editor').value)
1044 | document.title = 'Saved all edits'
1045 | setTimeout(() => {
1046 | document.title = story_title ?? 'IF'
1047 | }, 1500)
1048 | }
1049 | }
1050 |
1051 | window.onkeyup = function (e) {
1052 | let idx
1053 | if (e.ctrlKey) {
1054 | idx = down.findIndex((val) => val === 'Control')
1055 | } else {
1056 | idx = down.findIndex((val) => val === e.key)
1057 | }
1058 | down.splice(idx, 1)
1059 | }
1060 |
1061 | $All('.symlink').forEach((el) => el.addEventListener('click', handleSymlink))
1062 |
1063 | /// ////////////////////////////////
1064 | // //
1065 | // AUTOSAVE IMPLEMENTATION //
1066 | // //
1067 | /// ////////////////////////////////
1068 | /* Autosave implementation */
1069 | let editor_timeout, stats_timeout
1070 |
1071 | function saveStory (text) {
1072 | const story_title = interpreter.run.story.name ? interpreter.run.state.name : null
1073 | lwrite(localStorageKeys.storyText, text)
1074 | document.title = 'Story Saved.'
1075 | setTimeout(() => {
1076 | document.title = story_title ?? 'IF'
1077 | }, 1500)
1078 | }
1079 |
1080 | function saveStats (text) {
1081 | const story_title = interpreter.run.story.name ? interpreter.run.story.name : null
1082 | lwrite(localStorageKeys.statsText, text)
1083 | document.title = 'Stats Saved.'
1084 | setTimeout(() => {
1085 | document.title = story_title ?? 'IF'
1086 | }, 1500)
1087 | }
1088 |
1089 | $('#if_r-input-area').addEventListener('keyup', function (e) {
1090 | if (editor_timeout) clearTimeout(editor_timeout)
1091 |
1092 | editor_timeout = setTimeout(function () {
1093 | saveStory(e.target.value)
1094 | }, 750)
1095 | })
1096 |
1097 | $('#if_r-stats-editor').addEventListener('keyup', function (e) {
1098 | if (stats_timeout) clearTimeout(stats_timeout)
1099 |
1100 | editor_timeout = setTimeout(function () {
1101 | saveStats(e.target.value)
1102 | }, 750)
1103 | })
1104 |
1105 | /// ////////////////////////////////
1106 | // //
1107 | // PARSING //
1108 | // //
1109 | /// ////////////////////////////////
1110 | /* Parsing */
1111 | function runSubmit () {
1112 | const storyValue = $('#if_r-input-area').value
1113 | const statsValue = $('#if_r-stats-editor').value
1114 |
1115 | $('#instructDiv').style.display = 'none'
1116 | $('#if_r-output-area').style.display = 'flex'
1117 |
1118 | $('#output-tablink').dispatchEvent(clickEvent)
1119 |
1120 | lwrite(localStorageKeys.storyText, storyValue)
1121 | lwrite(localStorageKeys.statsText, statsValue)
1122 |
1123 | const story = parseText(storyValue)
1124 |
1125 | lwrite(localStorageKeys.objectStorage, JSON.stringify(story))
1126 |
1127 | interpreter.loadStory(story, null, 'default')
1128 | }
1129 |
--------------------------------------------------------------------------------
/src/web/preview/index.js:
--------------------------------------------------------------------------------
1 | import IFScript from 'if-script-core/src/IFScript.mjs'
2 | import Story from 'if-script-core/src/models/Story.mjs'
3 |
4 | const IF = new IFScript(IFScript.versions().STREAM)
5 |
6 | const interpreter = new IF.interpreter()
7 |
8 | let storyObj = JSON.parse(localStorage.getItem('if_r-if-object'))
9 | const story = new Story(storyObj.name, { sections: storyObj.sections, passages: [], scenes: storyObj.scenes }, storyObj.settings, {globals: null, stats: null})
10 | interpreter.loadStory(story, null, 'bricks')
11 | document.title = interpreter.run.story.name ?? 'IF | Preview'
12 |
--------------------------------------------------------------------------------
/src/web/preview/index.template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Previewing...
7 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/web/service-worker.js:
--------------------------------------------------------------------------------
1 | const CACHE_VERSIONS = {
2 | css: 1,
3 | terp: 1,
4 | fonts: 2,
5 | images: 1
6 | }
7 |
8 | const CURRENT_CACHES = {}
9 |
10 | const addrs = {
11 | css: ['css/index.css', 'downloadable/if_r.css'],
12 | fonts: ['assets/fonts/FiraMono-Regular.ttf', 'assets/fonts/VarelaRound-Regular.ttf'],
13 | images: ['assets/images/book-background0.jpg', 'assets/images/if-logo.png']
14 | }
15 |
16 | const props = Object.keys(CACHE_VERSIONS)
17 | props.forEach(val => {
18 | CURRENT_CACHES[val] = `${val}-v${CACHE_VERSIONS[val]}`
19 | })
20 |
21 | self.addEventListener('install', event =>
22 | event.waitUntil(
23 | props.forEach(prop =>
24 | caches
25 | .open(CURRENT_CACHES[prop])
26 | .then(cache => Object.keys(addrs).forEach(val => val.forEach(addr => cache.add(addr))))
27 | .catch(e => console.log(e)))
28 | ))
29 |
30 | self.addEventListener('fetch', function (event) {
31 | const preTypes = [/\/js\//, /\/css\//, /\/images\//, /\/fonts\//]
32 |
33 | const index = preTypes.findIndex(preType => {
34 | return !!event.request.url.match(preType)
35 | })
36 |
37 | let prop = 'css'
38 |
39 | if (index !== -1) { prop = event.request.url.match(preTypes[index])[0].replace(/\//g, '') }
40 |
41 | event.respondWith(
42 | caches.open(CURRENT_CACHES[prop]).then(function (cache) {
43 | return cache.match(event.request).then(function (response) {
44 | if (response) {
45 | return response
46 | }
47 | return fetch(event.request.clone()).then(function (response) {
48 | if (response.status < 400 &&
49 | response.url &&
50 | response.url.match(new RegExp(prop, 'i'))) {
51 | cache.put(event.request, response.clone())
52 | }
53 | return response
54 | }).catch(e => console.log('Why not?', e))
55 | }).catch(function (error) {
56 | throw error
57 | })
58 | })
59 | )
60 | })
61 |
--------------------------------------------------------------------------------