├── .nojekyll ├── CNAME ├── spirit.mp4 ├── favicon.ico ├── screenshot.png ├── sipOfCoffee.m4a ├── public ├── screenshot.png ├── github-fork.svg ├── app.css └── codeMirror.css ├── .gitignore ├── testAll.js ├── components ├── Types.js ├── BottomBar.js ├── TopBar.js ├── Share.js ├── EditorApp.test.node.js ├── Export.js ├── EditorHandle.js ├── Showcase.js ├── ScrollFileEditor.js ├── CodeEditor.js └── EditorApp.js ├── jscroll.html ├── external.scroll ├── index.scroll ├── .roboto.css ├── .dark.css ├── .gazette.css ├── jscroll.scroll ├── readme.scroll ├── dist ├── libs.scroll └── app.js ├── .debug.css ├── .inspector.css ├── .tufte.css ├── package.json ├── lib ├── jquery.ui.touch-punch.min.js └── jquery-ui.min.js ├── .prestige.css ├── .slideshow.js ├── .helpfulNotFound.js ├── BrowserGlue.js ├── .sparkline.js ├── .tableSearch.js ├── .scroll.css ├── .dayjs.min.js ├── .leaflet.css ├── index.html └── .katex.min.css /.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | try.scroll.pub -------------------------------------------------------------------------------- /spirit.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breck7/tryscroll/HEAD/spirit.mp4 -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breck7/tryscroll/HEAD/favicon.ico -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breck7/tryscroll/HEAD/screenshot.png -------------------------------------------------------------------------------- /sipOfCoffee.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breck7/tryscroll/HEAD/sipOfCoffee.m4a -------------------------------------------------------------------------------- /public/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breck7/tryscroll/HEAD/public/screenshot.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | readme.html 3 | ignore/ 4 | coverage/ 5 | .nyc_output/ 6 | .DS_Store 7 | package-lock.json 8 | external.html 9 | .* -------------------------------------------------------------------------------- /testAll.js: -------------------------------------------------------------------------------- 1 | const runTests = (testParticles) => { 2 | const tap = require("tap") 3 | Object.keys(testParticles).forEach((key) => { 4 | testParticles[key](tap.equal) 5 | }) 6 | } 7 | 8 | runTests({ ...require("./components/EditorApp.test.node.js").testParticles }) 9 | -------------------------------------------------------------------------------- /components/Types.js: -------------------------------------------------------------------------------- 1 | const LocalStorageKeys = {} 2 | 3 | LocalStorageKeys.scroll = "scroll" 4 | LocalStorageKeys.editorStartWidth = "editorStartWidth" 5 | 6 | const UrlKeys = {} 7 | 8 | UrlKeys.scroll = "scroll" 9 | UrlKeys.url = "url" 10 | 11 | module.exports = { LocalStorageKeys, UrlKeys } 12 | -------------------------------------------------------------------------------- /jscroll.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | -------------------------------------------------------------------------------- /components/BottomBar.js: -------------------------------------------------------------------------------- 1 | const { AbstractParticleComponentParser } = require("scrollsdk/products/ParticleComponentFramework.node.js") 2 | const { Particle } = require("scrollsdk/products/Particle.js") 3 | 4 | class BottomBarComponent extends AbstractParticleComponentParser { 5 | createParserPool() { 6 | return new Particle.ParserPool(undefined, {}) 7 | } 8 | } 9 | 10 | module.exports = { BottomBarComponent } 11 | -------------------------------------------------------------------------------- /external.scroll: -------------------------------------------------------------------------------- 1 | // This file ensures all the keywords with external assets have their assets copied here 2 | // We dont need to check in the html, just the external assets copied 3 | katex 4 | map 5 | tableSearch 6 | slideshow 7 | helpfulNotFound 8 | sparkline 1 2 3 9 | scatterplot 10 | buildHtml 11 | theme gazette 12 | theme roboto 13 | theme dark 14 | theme tufte 15 | theme prestige 16 | debugAbove 17 | @@ javascript 18 | Hello world -------------------------------------------------------------------------------- /index.scroll: -------------------------------------------------------------------------------- 1 | buildHtml 2 | title Try Scroll - a language for scientists of all ages 3 | description Scroll is an extendible language for writing and thought. 4 | openGraphImage https://scroll.pub/public/screenshot.png 5 | metaTags 6 | 7 | inlineCss public/codeMirror.css public/app.css 8 | 9 | ./dist/libs.js 10 | ./.d3.js 11 | ./lodash.min.js 12 | ./dist/constants.js 13 | ./dist/app.js 14 | 15 | script 16 | const lodash = _; 17 | document.addEventListener('DOMContentLoaded', () => new BrowserGlue().init(AppConstants.parsers)) 18 | -------------------------------------------------------------------------------- /components/TopBar.js: -------------------------------------------------------------------------------- 1 | const { AbstractParticleComponentParser } = require("scrollsdk/products/ParticleComponentFramework.node.js") 2 | const { ShareComponent } = require("./Share.js") 3 | const { ExportComponent } = require("./Export.js") 4 | const { Particle } = require("scrollsdk/products/Particle.js") 5 | 6 | class TopBarComponent extends AbstractParticleComponentParser { 7 | createParserPool() { 8 | return new Particle.ParserPool(undefined, { 9 | ShareComponent, 10 | ExportComponent, 11 | }) 12 | } 13 | } 14 | 15 | module.exports = { TopBarComponent } 16 | -------------------------------------------------------------------------------- /public/github-fork.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.roboto.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Base Colors */ 3 | --scrollPrimaryRgb: 0, 102, 204; 4 | --scrollSurfaceRgb: 224, 224, 224; 5 | --scrollTextBase: 51, 51, 51; 6 | --scrollLinkBase: 0, 102, 204; 7 | 8 | /* Semantic Colors */ 9 | --scrollColorBackground: rgb(240, 240, 240); 10 | --scrollColorText: rgb(51, 51, 51); 11 | --scrollColorLink: #0066cc; 12 | --scrollColorSubdued: rgb(102, 102, 102); 13 | 14 | /* Typography */ 15 | --scrollFontPrimary: "Roboto", sans-serif; 16 | --scrollFontUi: "Roboto", sans-serif; 17 | --scrollFontMono: monospace; 18 | --scrollBaseFontSize: 16px; 19 | --scrollLineHeight: 1.6; 20 | } 21 | -------------------------------------------------------------------------------- /.dark.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Base Colors */ 3 | --scrollPrimaryRgb: 102, 179, 255; 4 | --scrollSurfaceRgb: 224, 224, 224; 5 | --scrollTextBase: 224, 224, 224; 6 | --scrollLinkBase: 102, 179, 255; 7 | 8 | /* Semantic Colors */ 9 | --scrollColorBackground: rgb(26, 26, 26); 10 | --scrollColorText: rgb(224, 224, 224); 11 | --scrollColorLink: #66b3ff; 12 | --scrollColorSubdued: rgb(179, 179, 179); 13 | 14 | /* Typography */ 15 | --scrollFontPrimary: "Roboto", sans-serif; 16 | --scrollFontUi: "Roboto", sans-serif; 17 | --scrollFontMono: monospace; 18 | --scrollBaseFontSize: 16px; 19 | --scrollLineHeight: 1.6; 20 | } 21 | -------------------------------------------------------------------------------- /.gazette.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Base Colors */ 3 | --scrollPrimaryRgb: 10, 92, 202; /* Base primary button */ 4 | --scrollSurfaceRgb: 204, 204, 204; /* Base surface */ 5 | --scrollTextBase: 0, 0, 0; 6 | --scrollLinkBase: 51, 102, 204; 7 | 8 | /* Semantic Colors */ 9 | --scrollColorBackground: rgb(244, 244, 244); 10 | --scrollColorText: rgba(var(--scrollTextBase), 1); 11 | --scrollColorLink: rgb(var(--scrollLinkBase), 1); 12 | --scrollColorSubdued: rgb(150, 150, 150); 13 | 14 | /* Typography */ 15 | --scrollFontPrimary: Exchange, Georgia, serif; 16 | --scrollFontUi: "SF Pro", "Helvetica Neue", "Segoe UI", "Arial"; 17 | --scrollFontMono: monospace; 18 | --scrollBaseFontSize: 16px; 19 | } 20 | -------------------------------------------------------------------------------- /components/Share.js: -------------------------------------------------------------------------------- 1 | const { AbstractParticleComponentParser } = require("scrollsdk/products/ParticleComponentFramework.node.js") 2 | 3 | class ShareComponent extends AbstractParticleComponentParser { 4 | toStumpCode() { 5 | return `div 6 | class ShareComponent 7 | input 8 | readonly 9 | title ${this.link} 10 | value ${this.link}` 11 | } 12 | 13 | getDependencies() { 14 | return [this.root.mainProgram] 15 | } 16 | 17 | get link() { 18 | const url = new URL(typeof location === "undefined" ? "http://localhost/" : location.href) // todo: TCF should provide shim for this 19 | url.hash = "" 20 | return url.toString() + this.root.urlHash 21 | } 22 | } 23 | 24 | module.exports = { ShareComponent } 25 | -------------------------------------------------------------------------------- /jscroll.scroll: -------------------------------------------------------------------------------- 1 | buildJs 2 | inlineJs 3 | .dayjs.min.js 4 | .d3.js 5 | lodash.min.js 6 | ../sdk/sandbox/lib/codemirror.js 7 | ../sdk/sandbox/lib/show-hint.js 8 | ../sdk/sandbox/lib/placeholder.js 9 | ../sdk/products/Utils.browser.js 10 | ../sdk/products/Particle.browser.js 11 | ../sdk/products/Parsers.ts.browser.js 12 | ../sdk/products/parsers.browser.js 13 | ../sdk/products/ParsersCodeMirrorMode.browser.js 14 | ../sdk/products/ScrollFileSystem.browser.js 15 | ../sdk/products/stump.browser.js 16 | ../sdk/products/hakon.browser.js 17 | ../sdk/products/ParticleComponentFramework.browser.js 18 | components/ScrollFileEditor.js 19 | dist/constants.js 20 | 21 | script 22 | const jscroll = new ScrollFileEditor(AppConstants.parsers) -------------------------------------------------------------------------------- /readme.scroll: -------------------------------------------------------------------------------- 1 | title TryScroll: Scroll Demo Page 2 | 3 | ? What is Scroll? 4 | Scroll is a language for scientists of all ages. 5 | https://scroll.pub/ Scroll 6 | 7 | ? Can you show me a screenshot? 8 | public/screenshot.png 9 | 10 | ? What is this tool? 11 | This folder contains a simple web editor for trying Scroll. You can write Scroll and see the rendered HTML in realtime. You can try Scroll without downloading the Scroll command line app. 12 | https://scroll.pub Scroll command line app 13 | 14 | # Maintenance Notes 15 | 16 | ? How do I run the tests? 17 | code 18 | npm install . 19 | npm test 20 | 21 | ? How do I update this app to the latest version of Scroll? 22 | * Currently a 3 step process: 23 | code 24 | npm run build 25 | open index.html 26 | git add .; git commit -m "Updated Scroll"; git push 27 | -------------------------------------------------------------------------------- /dist/libs.scroll: -------------------------------------------------------------------------------- 1 | buildJs 2 | inlineJs 3 | ../../sdk/particleComponentFramework/sweepercraft/lib/mousetrap.min.js 4 | ../node_modules/jquery/dist/jquery.min.js 5 | ../lib/jquery-ui.min.js 6 | ../node_modules/scroll-cli/external/.dayjs.min.js 7 | ../lib/jquery.ui.touch-punch.min.js 8 | ../../sdk/sandbox/lib/codemirror.js 9 | ../../sdk/sandbox/lib/show-hint.js 10 | ../../sdk/sandbox/lib/placeholder.js 11 | ../../sdk/products/Utils.browser.js 12 | ../../sdk/products/Particle.browser.js 13 | ../../sdk/products/Parsers.ts.browser.js 14 | ../../sdk/products/parsers.browser.js 15 | ../../sdk/products/ParsersCodeMirrorMode.browser.js 16 | ../../sdk/products/ScrollFileSystem.browser.js 17 | ../../sdk/products/stump.browser.js 18 | ../../sdk/products/hakon.browser.js 19 | ../../sdk/products/ParticleComponentFramework.browser.js 20 | -------------------------------------------------------------------------------- /.debug.css: -------------------------------------------------------------------------------- 1 | .debugParticle { 2 | font-family: arial; 3 | white-space: nowrap; 4 | font-size: 18px; 5 | background: rgba(0, 119, 182, 0.7); 6 | display: inline-block; 7 | padding: 5px; 8 | padding-top: 1.5em; 9 | position: relative; 10 | min-width: 20ch; 11 | border: 3px solid rgb(0, 119, 182); 12 | } 13 | .debugSubparticles { 14 | margin-left: 5px; 15 | display: inline-block; 16 | padding: 5px; 17 | border-radius: 2px; 18 | background: rgba(0, 0, 0, 0.05); 19 | } 20 | .debugAtom { 21 | background: #fcbf49; 22 | position: relative; 23 | display: inline-block; 24 | padding: 1.5em 10px 0.5em 10px; 25 | border-radius: 5px; 26 | text-align: center; 27 | min-width: 15ch; 28 | } 29 | .debugAtomType, 30 | .debugParticleId { 31 | position: absolute; 32 | text-align: center; 33 | left: 0; 34 | top: 2px; 35 | right: 0; 36 | color: green; 37 | } 38 | .debugParticleId { 39 | color: white; 40 | } 41 | -------------------------------------------------------------------------------- /.inspector.css: -------------------------------------------------------------------------------- 1 | .inspectorParticle { 2 | font-family: arial; 3 | white-space: nowrap; 4 | font-size: 18px; 5 | background: rgba(0, 119, 182, 0.7); 6 | display: inline-block; 7 | padding: 5px; 8 | padding-top: 1.5em; 9 | position: relative; 10 | min-width: 20ch; 11 | border: 3px solid rgb(0, 119, 182); 12 | } 13 | .inspectorSubparticles { 14 | margin-left: 5px; 15 | display: inline-block; 16 | padding: 5px; 17 | border-radius: 2px; 18 | background: rgba(0, 0, 0, 0.05); 19 | } 20 | .inspectorAtom { 21 | background: #fcbf49; 22 | position: relative; 23 | display: inline-block; 24 | padding: 1.5em 10px 0.5em 10px; 25 | border-radius: 5px; 26 | text-align: center; 27 | min-width: 15ch; 28 | } 29 | .inspectorAtomType, 30 | .inspectorParticleId { 31 | position: absolute; 32 | text-align: center; 33 | left: 0; 34 | top: 2px; 35 | right: 0; 36 | color: green; 37 | } 38 | .inspectorParticleId { 39 | color: white; 40 | } 41 | -------------------------------------------------------------------------------- /components/EditorApp.test.node.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { Particle } = require("scrollsdk/products/Particle.js") 4 | const { Disk } = require("scrollsdk/products/Disk.node.js") 5 | const parsersParser = require("scrollsdk/products/parsers.nodejs.js") 6 | const { EditorApp } = require("./EditorApp.js") 7 | const { DefaultScrollParser } = require("scroll-cli") 8 | 9 | const testParticles = {} 10 | 11 | testParticles.parsers = (areEqual) => { 12 | const errs = new parsersParser(new DefaultScrollParser().definition.asString) 13 | .getAllErrors() 14 | .map((err) => err.toObject()) 15 | if (errs.length) console.log(new Particle(errs).toFormattedTable(60)) 16 | areEqual(errs.length, 0, "no parsers errors") 17 | } 18 | 19 | testParticles.EditorApp = async (areEqual) => { 20 | const app = await EditorApp.setupApp("") 21 | areEqual(!!app, true) 22 | } 23 | 24 | module.exports = { testParticles } 25 | const runTests = (testParticles) => { 26 | const tap = require("tap") 27 | Object.keys(testParticles).forEach((key) => testParticles[key](tap.equal)) 28 | } 29 | if (module && !module.parent) runTests(testParticles) 30 | -------------------------------------------------------------------------------- /.tufte.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Base Colors */ 3 | --scrollPrimaryRgb: 17, 17, 17; 4 | --scrollSurfaceRgb: 204, 204, 204; 5 | --scrollTextBase: 17, 17, 17; 6 | --scrollLinkBase: 17, 17, 17; 7 | 8 | /* Semantic Colors */ 9 | --scrollColorBackground: #fffff8; 10 | --scrollColorText: #111; 11 | --scrollColorLink: inherit; 12 | --scrollColorSubdued: rgb(102, 102, 102); 13 | 14 | /* Typography */ 15 | --scrollFontPrimary: Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif; 16 | --scrollFontUi: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; 17 | --scrollFontMono: Consolas, "Liberation Mono", Menlo, Courier, monospace; 18 | --scrollBaseFontSize: 15px; 19 | --scrollLineHeight: 2; 20 | 21 | /* Typography Scale */ 22 | --scrollH1Size: 3.2rem; 23 | --scrollH2Size: 2.2rem; 24 | --scrollH3Size: 1.7rem; 25 | --scrollCodeSize: 1rem; 26 | 27 | /* Layout */ 28 | --scrollMaxWidth: 1400px; 29 | --scrollBodyPadding: 12.5%; 30 | 31 | /* Dark Mode Override */ 32 | @media (prefers-color-scheme: dark) { 33 | --scrollColorBackground: #151515; 34 | --scrollColorText: #ddd; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tryscroll", 3 | "version": "1.4.0", 4 | "description": "Try Scroll", 5 | "main": "components/EditorApp.js", 6 | "prettier": { 7 | "printWidth": 120, 8 | "semi": false 9 | }, 10 | "dependencies": { 11 | "jquery": "^3.6.0", 12 | "scroll-cli": "^178.2.2", 13 | "scrollsdk": "^107.0.1" 14 | }, 15 | "devDependencies": { 16 | "tap": "^18.7.2" 17 | }, 18 | "scripts": { 19 | "up": "npm remove scroll-cli;npm install scroll-cli;npm install scrollsdk@latest;./build.js; scroll list | scroll build", 20 | "buildCoverageReport": "tap testAll.js", 21 | "buildCoverageHtmlReport": "tap testAll.js --coverage-report=lcov", 22 | "open": "open index.html", 23 | "test": "node testAll.js", 24 | "beta": "cd ~/scroll; scroll build; cd - ;npm install ~/scroll; npm install ~/sdk; node ./build.js; scroll list | scroll build" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/breck7/tryscroll.git" 29 | }, 30 | "author": "", 31 | "bugs": { 32 | "url": "https://github.com/breck7/tryscroll/issues" 33 | }, 34 | "homepage": "https://github.com/breck7/tryscroll" 35 | } 36 | -------------------------------------------------------------------------------- /lib/jquery.ui.touch-punch.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Touch Punch 0.2.3 3 | * 4 | * Copyright 2011–2014, Dave Furfero 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * 7 | * Depends: 8 | * jquery.ui.widget.js 9 | * jquery.ui.mouse.js 10 | */ 11 | !function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery); -------------------------------------------------------------------------------- /components/Export.js: -------------------------------------------------------------------------------- 1 | const { AbstractParticleComponentParser } = require("scrollsdk/products/ParticleComponentFramework.node.js") 2 | 3 | class ExportComponent extends AbstractParticleComponentParser { 4 | toStumpCode() { 5 | return `div 6 | class ExportComponent 7 | a Format 8 | clickCommand formatScrollCommand 9 | span | 10 | a Tutorial 11 | target _blank 12 | href index.html#${encodeURIComponent("url https://scroll.pub/tutorial.scroll")} 13 | span | 14 | a Reset 15 | clickCommand resetCommand 16 | span | 17 | a Copy Output 18 | clickCommand copyOutputToClipboardCommand 19 | span | 20 | a Download Output 21 | clickCommand downloadOutputCommand` 22 | } 23 | 24 | resetCommand() { 25 | if (!confirm("Are you sure you want to reset?")) return 26 | localStorage.clear() 27 | window.location = "" 28 | } 29 | 30 | copyOutputToClipboardCommand() { 31 | this.root.willowBrowser.copyTextToClipboard(this.root.mainOutput.content) 32 | } 33 | 34 | formatScrollCommand() { 35 | this.root.formatScrollCommand() 36 | } 37 | 38 | downloadOutputCommand() { 39 | const program = this.root.mainProgram 40 | let mainOutput = this.root.mainOutput 41 | const filename = program.permalink 42 | let type = "text/" + mainOutput.type 43 | this.root.willowBrowser.downloadFile(mainOutput.content, filename, type) 44 | } 45 | 46 | get app() { 47 | return this.root 48 | } 49 | } 50 | 51 | module.exports = { ExportComponent } 52 | -------------------------------------------------------------------------------- /components/EditorHandle.js: -------------------------------------------------------------------------------- 1 | const { AbstractParticleComponentParser } = require("scrollsdk/products/ParticleComponentFramework.node.js") 2 | 3 | class EditorHandleComponent extends AbstractParticleComponentParser { 4 | get left() { 5 | return this.root.editor.width 6 | } 7 | 8 | makeDraggable() { 9 | if (this.isNodeJs()) return 10 | 11 | const root = this.root 12 | const handle = this.getStumpParticle().getShadow().element 13 | jQuery(handle).draggable({ 14 | axis: "x", 15 | drag: function (event, ui) { 16 | if ("ontouchend" in document) return // do not update live on a touch device. otherwise buggy. 17 | root.resizeEditorCommand(Math.max(ui.offset.left, 5) + "") 18 | jQuery(".EditorHandleComponent").addClass("rightBorder") 19 | }, 20 | start: function (event, ui) { 21 | jQuery(".EditorHandleComponent").addClass("rightBorder") 22 | }, 23 | stop: function (event, ui) { 24 | root.resizeEditorCommand(Math.max(ui.offset.left, 5) + "") 25 | window.location = window.location 26 | jQuery(".EditorHandleComponent").removeClass("rightBorder") 27 | }, 28 | }) 29 | jQuery(this.getStumpParticle().getShadow().element).on("dblclick", () => { 30 | root.resizeEditorCommand() 31 | window.location = window.location 32 | }) 33 | } 34 | 35 | particleComponentDidMount() { 36 | this.makeDraggable() 37 | } 38 | 39 | particleComponentDidUpdate() { 40 | this.makeDraggable() 41 | } 42 | 43 | toStumpCode() { 44 | return `div 45 | class ${EditorHandleComponent.name} 46 | style left:${this.left}px;` 47 | } 48 | 49 | getDependencies() { 50 | return [this.root.editor] 51 | } 52 | } 53 | 54 | module.exports = { EditorHandleComponent } 55 | -------------------------------------------------------------------------------- /.prestige.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Base Colors */ 3 | --scrollPrimaryRgb: 51, 51, 51; 4 | --scrollSurfaceRgb: 204, 204, 204; 5 | --scrollTextBase: 51, 51, 51; 6 | --scrollLinkBase: 0, 0, 0; 7 | 8 | /* Semantic Colors */ 9 | --scrollColorBackground: rgb(250, 250, 250); 10 | --scrollColorText: rgba(var(--scrollTextBase), 1); 11 | --scrollColorLink: rgb(var(--scrollLinkBase), 1); 12 | --scrollColorSubdued: rgb(102, 102, 102); 13 | 14 | /* Typography */ 15 | --scrollFontPrimary: "Georgia", "Times New Roman", serif; 16 | --scrollFontUi: "Baskerville", "Times New Roman", serif; 17 | --scrollFontMono: monospace; 18 | --scrollBaseFontSize: 17.6px; 19 | --scrollLineHeight: 1.8; 20 | } 21 | 22 | /* Title Styling */ 23 | h1 { 24 | font-family: var(--scrollFontUi); 25 | font-size: 2.5rem; 26 | text-align: center; 27 | text-transform: uppercase; 28 | letter-spacing: 0.1em; 29 | color: var(--scrollColorLink); 30 | margin-bottom: 1rem; 31 | } 32 | 33 | /* Subtitle Styling */ 34 | h2, 35 | .printDateParser, 36 | .printAuthorsParser { 37 | font-size: 1.25rem; 38 | text-align: center; 39 | text-transform: uppercase; 40 | letter-spacing: 0.08em; 41 | color: #555555; 42 | margin-bottom: 1rem; 43 | } 44 | 45 | .printAuthorsParser { 46 | font-weight: bold; 47 | } 48 | 49 | .printTitleParser a { 50 | text-decoration: none; 51 | color: var(--scrollColorLink); 52 | } 53 | 54 | a { 55 | color: var(--scrollColorLink); 56 | text-decoration-color: transparent; 57 | } 58 | a:hover { 59 | text-decoration-color: unset; 60 | } 61 | 62 | /* Drop Cap Styling */ 63 | .dropcap::first-letter { 64 | font-family: var(--scrollFontPrimary); 65 | font-size: 4rem; 66 | float: left; 67 | line-height: 0.8; 68 | margin-right: 0.5rem; 69 | margin-top: 0.2rem; 70 | font-weight: bold; 71 | color: var(--scrollColorLink); 72 | } 73 | 74 | /* Paragraph Styling */ 75 | p { 76 | font-size: var(--scrollBaseFontSize); 77 | text-align: justify; 78 | margin-bottom: 1.5rem; 79 | } 80 | 81 | /* Blockquote Styling */ 82 | blockquote { 83 | font-style: italic; 84 | color: var(--scrollColorSubdued); 85 | border-left: 4px solid rgba(var(--scrollSurfaceRgb), 1); 86 | padding-left: 1rem; 87 | margin: 1.5rem 0; 88 | } 89 | 90 | /* Additional Fine-Tuning */ 91 | h1, 92 | h2 { 93 | margin-top: 0; 94 | } 95 | 96 | .abstractDinkusParser { 97 | text-align: center; 98 | padding: 1rem; 99 | } 100 | 101 | .abstractDinkusParser span { 102 | vertical-align: sub; 103 | } 104 | -------------------------------------------------------------------------------- /.slideshow.js: -------------------------------------------------------------------------------- 1 | class SlideShow { 2 | constructor() { 3 | const hash = window.location.hash.replace("#", "") 4 | this.page = hash === "" ? 1 : parseInt(hash) 5 | window.location.hash = "#" + this.page 6 | this.listenToKeyboard() 7 | this.listenToHash() 8 | this.renderAll() 9 | } 10 | 11 | renderSlide() { 12 | jQuery(this.slides[this.page - 1]).show() 13 | jQuery(this.slides[this.page - 1]) 14 | .children() 15 | .show() 16 | jQuery(".abstractDinkusParser").hide() 17 | } 18 | 19 | hideAll() { 20 | jQuery("div,p,figure,code,pre,li").hide() 21 | } 22 | 23 | get slides() { 24 | return jQuery(".abstractDinkusParser") 25 | .map(function () { 26 | return jQuery(this).prevUntil(".abstractDinkusParser").addBack().prev() 27 | }) 28 | .get() 29 | .slice(1) 30 | } 31 | 32 | renderAll() { 33 | this.hideAll() 34 | this.renderSlide() 35 | this.renderNav() 36 | } 37 | 38 | listenToKeyboard() { 39 | document.addEventListener("keydown", function (event) { 40 | if (document.activeElement !== document.body) return 41 | const getLinks = () => document.getElementsByClassName("slideshowNav")[0].getElementsByTagName("a") 42 | if (event.key === "ArrowLeft") getLinks()[0].click() 43 | else if (event.key === "ArrowRight") getLinks()[1].click() 44 | }) 45 | } 46 | 47 | listenToHash() { 48 | window.addEventListener("hashchange", event => { 49 | this.page = parseInt(window.location.hash.replace("#", "")) 50 | this.hideAll() 51 | this.renderAll() 52 | }) 53 | } 54 | 55 | renderNav() { 56 | const that = this 57 | jQuery(".slideshowNav").html(this.nav).show() 58 | jQuery(".slideshowNav a").on("click", function (event) { 59 | event.preventDefault() 60 | that.page = parseInt(jQuery(this).attr("href").replace("#", "")) 61 | that.renderAll() 62 | window.location.hash = "#" + that.page 63 | }) 64 | } 65 | 66 | page = 1 67 | 68 | get pages() { 69 | return this.slides.length 70 | } 71 | 72 | get previousPage() { 73 | let { page } = this 74 | page-- 75 | if (page === 0) page = this.pages 76 | return page 77 | } 78 | 79 | get nextPage() { 80 | let { page } = this 81 | page++ 82 | if (page > this.pages) page = 1 83 | return page 84 | } 85 | 86 | get nav() { 87 | return `< ${this.page}/${this.pages} >` 88 | } 89 | } 90 | 91 | document.addEventListener("DOMContentLoaded", () => new SlideShow()) 92 | -------------------------------------------------------------------------------- /.helpfulNotFound.js: -------------------------------------------------------------------------------- 1 | // This script lets you worry less about broken links and help readers who use an incorrect link. 2 | // It shows the closest matching link. 3 | // To use: 4 | // Include this script on your 404.html page. 5 | // Make sure to generate a sitemap to sitemap.txt 6 | // Make sure your page has a div with the id "helpfulNotFound". 7 | // This script will fetch your sitemap, find the closest matching link, and suggest that to the user. 8 | class NotFoundApp { 9 | constructor(sitemapUrls = "/sitemap.txt") { 10 | this.load(sitemapUrls) 11 | } 12 | 13 | async load(sitemapUrls) { 14 | const getSuggestion = async () => { 15 | const currentUrl = window.location.href 16 | // Function to calculate the Levenshtein distance between two strings 17 | function levenshteinDistance(a, b) { 18 | const m = a.length 19 | const n = b.length 20 | const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0)) 21 | for (let i = 0; i <= m; i++) { 22 | dp[i][0] = i 23 | } 24 | for (let j = 0; j <= n; j++) { 25 | dp[0][j] = j 26 | } 27 | for (let i = 1; i <= m; i++) { 28 | for (let j = 1; j <= n; j++) { 29 | const cost = a[i - 1] === b[j - 1] ? 0 : 1 30 | dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost) 31 | } 32 | } 33 | return dp[m][n] 34 | } 35 | 36 | try { 37 | const responses = await Promise.all(sitemapUrls.split(" ").map(async url => await fetch(url))) 38 | const sitemap = await Promise.all(responses.map(async response => await response.text())) 39 | const urls = sitemap.join("\n").split("\n") 40 | 41 | let closestMatch = null 42 | let minDistance = Infinity 43 | 44 | urls.forEach(url => { 45 | const trimmedUrl = url.trim() 46 | if (trimmedUrl) { 47 | const distance = levenshteinDistance(currentUrl, trimmedUrl) 48 | if (distance < minDistance) { 49 | minDistance = distance 50 | closestMatch = trimmedUrl 51 | } 52 | } 53 | }) 54 | 55 | return closestMatch 56 | } catch (error) { 57 | console.error("Failed to fetch the sitemap:", error) 58 | } 59 | } 60 | 61 | const closestMatch = await getSuggestion() 62 | const outputDiv = document.getElementById("helpfulNotFound") 63 | if (closestMatch) outputDiv.innerHTML = `Maybe the url you want is
${closestMatch}?` 64 | else outputDiv.parentElement.textContent = "No similar pages found." 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /public/app.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | width: 100%; 4 | box-sizing: border-box; 5 | overflow: hidden; 6 | } 7 | 8 | *, 9 | *:before, 10 | *:after { 11 | box-sizing: inherit; 12 | } 13 | 14 | body { 15 | box-sizing: border-box; 16 | padding: 0; 17 | margin: 0; 18 | height: 100%; 19 | width: 100%; 20 | font-family: -apple-system, BlinkMacSystemFont, sans-serif; 21 | } 22 | 23 | :root { 24 | --topBarHeight: 28px; 25 | --titleHeight: 20px; 26 | --bottomBarHeight: 10px; 27 | --chromeColor: rgb(47, 47, 51); 28 | } 29 | 30 | .EditorApp { 31 | height: 100%; 32 | width: 100%; 33 | } 34 | 35 | .EditorHandleComponent { 36 | width: 10px; 37 | position: absolute; 38 | top: var(--topBarHeight); 39 | bottom: var(--bottomBarHeight); 40 | cursor: ew-resize; 41 | background-color: rgba(0, 0, 0, 0.1); 42 | z-index: 1000; 43 | } 44 | .rightBorder { 45 | border-right: 10000px solid rgba(0, 0, 0, 0.0001); 46 | } 47 | 48 | .CodeEditorComponent { 49 | position: absolute; 50 | top: var(--topBarHeight); 51 | bottom: var(--bottomBarHeight); 52 | border: 0; 53 | left: 0; 54 | } 55 | 56 | .TopBarComponent { 57 | background-color: var(--chromeColor); 58 | color: white; 59 | font-size: 14px; 60 | line-height: var(--topBarHeight); 61 | height: var(--topBarHeight); 62 | white-space: nowrap; 63 | } 64 | .TopBarComponent a { 65 | color: white; 66 | text-decoration-color: transparent; 67 | } 68 | .TopBarComponent a:hover { 69 | text-decoration-color: white; 70 | } 71 | .ShareComponent { 72 | display: inline; 73 | } 74 | .ShareComponent input { 75 | background: rgb(79, 79, 79); 76 | padding: 3px; 77 | border-radius: 5px; 78 | color: white; 79 | border: 0; 80 | width: 230px; 81 | } 82 | 83 | .BottomBarComponent { 84 | position: absolute; 85 | line-height: var(--bottomBarHeight); 86 | height: var(--bottomBarHeight); 87 | padding-left: 5px; 88 | bottom: 0; 89 | left: 0; 90 | right: 0; 91 | background-color: var(--chromeColor); 92 | color: white; 93 | font-size: 34px; 94 | text-align: center; 95 | } 96 | 97 | #codeErrorsConsole { 98 | font-size: 10px; 99 | color: #999; 100 | text-align: center; 101 | } 102 | 103 | .BottomButton:hover { 104 | background-color: rgba(255, 255, 255, 0.2); 105 | opacity: 1; 106 | } 107 | 108 | .BottomButton:active { 109 | background-color: rgba(255, 255, 255, 0.3); 110 | opacity: 1; 111 | } 112 | 113 | .ShowcaseComponent { 114 | position: absolute; 115 | top: var(--topBarHeight); 116 | right: 5px; 117 | bottom: var(--bottomBarHeight); 118 | } 119 | 120 | .ShowcaseComponent iframe { 121 | height: 100%; 122 | width: 100%; 123 | border: 0; 124 | } 125 | 126 | .ExportComponent { 127 | display: inline; 128 | } 129 | 130 | .ExportComponent a:hover { 131 | text-decoration: underline; 132 | cursor: pointer; 133 | } 134 | -------------------------------------------------------------------------------- /components/Showcase.js: -------------------------------------------------------------------------------- 1 | const { AbstractParticleComponentParser } = require("scrollsdk/products/ParticleComponentFramework.node.js") 2 | 3 | class ShowcaseComponent extends AbstractParticleComponentParser { 4 | // Track which iframe is currently visible 5 | activeIframeId = "theIframe1" 6 | 7 | async refresh() { 8 | // Get both iframes 9 | const iframe1 = document.getElementById("theIframe1") 10 | const iframe2 = document.getElementById("theIframe2") 11 | 12 | // Determine active and buffer iframes 13 | const activeIframe = document.getElementById(this.activeIframeId) 14 | const bufferIframe = this.activeIframeId === "theIframe1" ? iframe2 : iframe1 15 | 16 | // Store scroll position from active iframe 17 | const scrollTop = activeIframe.contentWindow ? activeIframe.contentWindow.scrollY : 0 18 | const scrollLeft = activeIframe.contentWindow ? activeIframe.contentWindow.scrollX : 0 19 | 20 | // Prepare content 21 | await this.root.buildMainProgram() 22 | const { mainOutput } = this.root 23 | let content = mainOutput.content 24 | if (mainOutput.type !== "html") { 25 | content = `
${content}
` 26 | } 27 | 28 | // Add scroll restoration script 29 | const scrollScript = ` 30 | 36 | ` 37 | 38 | // Update the hidden buffer iframe 39 | bufferIframe.srcdoc = content + scrollScript 40 | 41 | // Set up message listener for one-time swap 42 | const swapHandler = (event) => { 43 | if (event.data === "iframeReady") { 44 | // Swap visibility 45 | activeIframe.style.display = "none" 46 | bufferIframe.style.display = "block" 47 | 48 | // Update active iframe tracking 49 | this.activeIframeId = bufferIframe.id 50 | 51 | // Force all links to open in a new tab. 52 | // todo: perhaps handle differently for # links? 53 | jQuery(bufferIframe).contents().find("a:not([target])").attr("target", "_blank") 54 | 55 | // Remove the message listener 56 | window.removeEventListener("message", swapHandler) 57 | } 58 | } 59 | window.addEventListener("message", swapHandler) 60 | } 61 | 62 | particleComponentDidMount() { 63 | this.refresh() 64 | } 65 | 66 | toStumpCode() { 67 | return `div 68 | class ${ShowcaseComponent.name} 69 | style left:${this.root.leftStartPosition + 10}px; 70 | iframe 71 | id theIframe1 72 | style display:block;width:100%;height:100%;border:none; 73 | srcdoc   74 | iframe 75 | id theIframe2 76 | style display:none;width:100%;height:100%;border:none; 77 | srcdoc  ` 78 | } 79 | } 80 | 81 | module.exports = { ShowcaseComponent } 82 | -------------------------------------------------------------------------------- /BrowserGlue.js: -------------------------------------------------------------------------------- 1 | const { Particle } = require("scrollsdk/products/Particle.js") 2 | const { HandParsersProgram } = require("scrollsdk/products/Parsers.ts.js") 3 | const { AbstractParticleComponentParser } = require("scrollsdk/products/ParticleComponentFramework.node.js") 4 | const { LocalStorageKeys, UrlKeys } = require("./components/Types.js") 5 | 6 | const DEFAULT_PROGRAM = `title Scroll is a language for scientists of all ages 7 | printTitle 8 | 9 | theme gazette 10 | 11 | # Refine, share and collaborate on ideas 12 | 13 | ## Build html files, CSV files, text files, and more. 14 | 15 | ### Scroll is an extensible alternative to Markdown. 16 | https://scroll.pub Scroll 17 | 18 | *** 19 | 20 | // You can have multiple columns 21 | thinColumns 2 22 | 23 | ## Links are different 24 | You put links _after_ the text, like this one. The code: 25 | https://scroll.pub one. 26 | aboveAsCode 27 | 28 | ? What's the benefit for using today? 29 | - A very flat plain text format 30 | - Keeps your thoughts and data organized 31 | - Build fully documented and cited CSV files 32 | - Ready to _grow_ with _you_. 33 | - Designed to last. 34 | 35 | # Scroll supports tables 36 | datatable 37 | printTable 38 | delimiter , 39 | data 40 | Language,Types 41 | HTML,~142 42 | Markdown,~192 43 | Scroll,~202 + yours 44 | 45 | # Images in Scroll 46 | // Just put the filename or URL: 47 | https://scroll.pub/blog/screenshot.png 48 | caption This is a screenshot of a blog 49 | https://breckyunits.com/ blog 50 | aboveAsCode 51 | 52 | expander Click me. 53 | You can easily add collapsed content. 54 | 55 | ## Scroll has footnotes^note 56 | 57 | ^note Sometimes called endnotes 58 | ` 59 | 60 | class BrowserGlue extends AbstractParticleComponentParser { 61 | async fetchAndLoadScrollCodeFromUrlCommand(url) { 62 | const code = await this.fetchText(url) 63 | return code 64 | } 65 | 66 | async fetchText(url) { 67 | const result = await fetch(url) 68 | const text = await result.text() 69 | return text 70 | } 71 | 72 | getFromLocalStorage() { 73 | return localStorage.getItem(LocalStorageKeys.scroll) 74 | } 75 | 76 | async fetchCode() { 77 | const hash = this.willowBrowser.getHash().substr(1) 78 | const deepLink = new Particle(decodeURIComponent(hash)) 79 | const fromUrl = deepLink.get(UrlKeys.url) 80 | const code = deepLink.getParticle(UrlKeys.scroll) 81 | 82 | // Clear hash 83 | history.pushState("", document.title, window.location.pathname) 84 | 85 | if (fromUrl) return this.fetchAndLoadScrollCodeFromUrlCommand(fromUrl) 86 | if (code) return code.subparticlesToString() 87 | 88 | const localStorageCode = this.getFromLocalStorage() 89 | if (localStorageCode) return localStorageCode 90 | 91 | return DEFAULT_PROGRAM 92 | } 93 | 94 | async init(parsersCode) { 95 | const scrollCode = await this.fetchCode() 96 | 97 | window.app = await EditorApp.setupApp(scrollCode, parsersCode, window.innerWidth, window.innerHeight) 98 | window.app.start() 99 | return window.app 100 | } 101 | } 102 | 103 | module.exports = { BrowserGlue } 104 | -------------------------------------------------------------------------------- /components/ScrollFileEditor.js: -------------------------------------------------------------------------------- 1 | class UrlWriter extends MemoryWriter { 2 | async read(fileName) { 3 | if (this.inMemoryFiles[fileName]) return this.inMemoryFiles[fileName] 4 | if (!isUrl(fileName)) fileName = this.getBaseUrl() + fileName 5 | return await super.read(fileName) 6 | } 7 | async exists(fileName) { 8 | if (this.inMemoryFiles[fileName]) return true 9 | if (!isUrl(fileName)) fileName = this.getBaseUrl() + fileName 10 | return await super.exists(fileName) 11 | } 12 | } 13 | 14 | /* 15 | interface EditorParent { 16 | bufferValue: string 17 | fileName: string 18 | rootUrl: string 19 | } 20 | */ 21 | class ScrollFileEditor { 22 | constructor(defaultParserCode, parent) { 23 | this.parent = parent 24 | this.fakeFs = {} 25 | this.fs = new ScrollFileSystem(this.fakeFs) 26 | this.fs.setDefaultParserFromString(defaultParserCode) 27 | const urlWriter = new UrlWriter(this.fakeFs) 28 | urlWriter.getBaseUrl = () => parent.rootUrl || "" 29 | this.fs._storage = urlWriter 30 | } 31 | async init() { 32 | await this.buildMainProgram() 33 | } 34 | async scrollToHtml(scrollCode) { 35 | const parsed = await this._parseScroll(scrollCode) 36 | return parsed.asHtml 37 | } 38 | async _parseScroll(scrollCode) { 39 | const file = this.fs.newFile(scrollCode) 40 | await file.singlePassFuse() 41 | return file.scrollProgram 42 | } 43 | async makeFusedFile(code, filename) { 44 | const { fs } = this 45 | this.fakeFs[filename] = code 46 | const file = this.fs.newFile(code, filename) 47 | await file.singlePassFuse() 48 | return file 49 | } 50 | async getFusedFile() { 51 | const file = await this.makeFusedFile(this.bufferValue, "/" + this.parent.fileName) 52 | this.fusedFile = file 53 | return file 54 | } 55 | async getFusedCode() { 56 | const fusedFile = await this.getFusedFile() 57 | return fusedFile.scrollProgram.toString() 58 | } 59 | get bufferValue() { 60 | return this.parent.bufferValue 61 | } 62 | get errors() { 63 | const errs = this.mainProgram.getAllErrors() 64 | return new Particle(errs.map((err) => err.toObject())).toFormattedTable(200) 65 | } 66 | async buildMainProgram() { 67 | const fusedFile = await this.getFusedFile() 68 | const fusedCode = await this.getFusedCode() 69 | this.mainProgram = fusedFile.scrollProgram 70 | try { 71 | await this.mainProgram.load() 72 | } catch (err) { 73 | console.error(err) 74 | } 75 | return this.mainProgram 76 | } 77 | async getFormatted() { 78 | const mainDoc = await this.buildMainProgram(false) 79 | return mainDoc.formatted 80 | } 81 | get mainOutput() { 82 | const { mainProgram } = this 83 | const particle = mainProgram.filter((particle) => particle.buildOutput)[0] 84 | if (!particle) 85 | return { 86 | type: "html", 87 | content: mainProgram.buildHtml(), 88 | } 89 | return { 90 | type: particle.extension.toLowerCase(), 91 | content: particle.buildOutput(), 92 | } 93 | } 94 | _cachedCode 95 | _cachedProgram 96 | getParsedProgramForCodeMirror(code) { 97 | // Note: this uses the latest loaded constructor and does a SYNC parse. 98 | // This allows us to use and loaded parsers, but gives sync, real time best 99 | // answers for highlighting and autocomplete. 100 | // It reparses the whole document. Actually seems to be fine for now. 101 | // Ideally we could also just run off mainProgram and not reparse, but 102 | // it gets tricky with the CodeMirror lib and async stuff. Maybe in the 103 | // future we can clean this up. 104 | if (code === this._cachedCode) return this._cachedProgram 105 | 106 | this._cachedCode = code 107 | this._cachedProgram = new this.mainProgram.latestConstructor(code) 108 | return this._cachedProgram 109 | } 110 | } 111 | 112 | if (typeof module !== "undefined" && module.exports) module.exports = { ScrollFileEditor } 113 | -------------------------------------------------------------------------------- /components/CodeEditor.js: -------------------------------------------------------------------------------- 1 | const { Particle } = require("scrollsdk/products/Particle.js") 2 | const { AbstractParticleComponentParser } = require("scrollsdk/products/ParticleComponentFramework.node.js") 3 | 4 | class CodeMirrorShim { 5 | setSize() {} 6 | setValue(value) { 7 | this.value = value 8 | } 9 | getValue() { 10 | return this.value 11 | } 12 | } 13 | 14 | class CodeEditorComponent extends AbstractParticleComponentParser { 15 | toStumpCode() { 16 | return `div 17 | class ${CodeEditorComponent.name} 18 | style width:${this.width}px; 19 | textarea 20 | id EditorTextarea 21 | div   22 | id codeErrorsConsole` 23 | } 24 | 25 | createParserPool() { 26 | return new Particle.ParserPool(undefined, { 27 | value: Particle, 28 | }) 29 | } 30 | 31 | get codeMirrorValue() { 32 | return this.codeMirrorInstance.getValue() 33 | } 34 | 35 | codeWidgets = [] 36 | 37 | async _onCodeKeyUp() { 38 | const { willowBrowser } = this 39 | const code = this.codeMirrorValue 40 | if (this._code === code) return 41 | this._code = code 42 | const root = this.root 43 | root.updateLocalStorage(code) 44 | await root.buildMainProgram() 45 | const errs = root.mainProgram.getAllErrors() 46 | 47 | let errMessage = " " 48 | const errorCount = errs.length 49 | 50 | if (errorCount) { 51 | const plural = errorCount > 1 ? "s" : "" 52 | errMessage = `
${errorCount} error${plural}:
53 | ${errs.map((err, index) => `${index}. ${err}`).join("
")}` 54 | } 55 | 56 | willowBrowser.setHtmlOfElementWithIdHack("codeErrorsConsole", errMessage) 57 | 58 | const cursor = this.codeMirrorInstance.getCursor() 59 | 60 | // todo: what if 2 errors? 61 | this.codeMirrorInstance.operation(() => { 62 | this.codeWidgets.forEach((widget) => this.codeMirrorInstance.removeLineWidget(widget)) 63 | this.codeWidgets.length = 0 64 | 65 | errs 66 | .filter((err) => !err.isBlankLineError()) 67 | .filter((err) => !err.isCursorOnAtom(cursor.line, cursor.ch)) 68 | .slice(0, 1) // Only show 1 error at a time. Otherwise UX is not fun. 69 | .forEach((err) => { 70 | const el = err.getCodeMirrorLineWidgetElement(() => { 71 | this.codeMirrorInstance.setValue(root.mainProgram.asString) 72 | this._onCodeKeyUp() 73 | }) 74 | this.codeWidgets.push( 75 | this.codeMirrorInstance.addLineWidget(err.lineNumber - 1, el, { coverGutter: false, noHScroll: false }), 76 | ) 77 | }) 78 | const info = this.codeMirrorInstance.getScrollInfo() 79 | const after = this.codeMirrorInstance.charCoords({ line: cursor.line + 1, ch: 0 }, "local").top 80 | if (info.top + info.clientHeight < after) this.codeMirrorInstance.scrollTo(null, after - info.clientHeight + 3) 81 | }) 82 | 83 | clearTimeout(this._timeout) 84 | this._timeout = setTimeout(async () => { 85 | await this.root.loadNewDoc(this._code) 86 | }, 20) 87 | } 88 | 89 | get bufferValue() { 90 | return this.codeMirrorInstance ? this.codeMirrorValue : this.getParticle("value").subparticlesToString() 91 | } 92 | 93 | async particleComponentDidMount() { 94 | this._initCodeMirror() 95 | this._updateCodeMirror() 96 | super.particleComponentDidMount() 97 | } 98 | 99 | async particleComponentDidUpdate() { 100 | this._updateCodeMirror() 101 | super.particleComponentDidUpdate() 102 | } 103 | 104 | renderAndGetRenderReport(stumpParticle, index) { 105 | if (!this.isMounted()) return super.renderAndGetRenderReport(stumpParticle, index) 106 | this.setSize() 107 | return "" 108 | } 109 | 110 | setCodeMirrorValue(value) { 111 | this.codeMirrorInstance.setValue(value) 112 | this._code = value 113 | } 114 | 115 | _initCodeMirror() { 116 | if (this.isNodeJs()) return (this.codeMirrorInstance = new CodeMirrorShim()) 117 | this.codeMirrorInstance = new ParsersCodeMirrorMode( 118 | "custom", 119 | () => this.root.scrollFileEditor.getParsedProgramForCodeMirror(this.codeMirrorInstance.getValue()), 120 | CodeMirror, 121 | ) 122 | .register() 123 | .fromTextAreaWithAutocomplete(document.getElementById("EditorTextarea"), { 124 | lineWrapping: false, 125 | lineNumbers: false, 126 | }) 127 | this.codeMirrorInstance.on("keyup", () => this._onCodeKeyUp()) 128 | this.setSize() 129 | } 130 | 131 | get width() { 132 | return parseInt(this.getAtom(1)) 133 | } 134 | 135 | get chromeHeight() { 136 | return parseInt(this.getAtom(2)) 137 | } 138 | 139 | setSize() { 140 | if (this.isNodeJs()) return 141 | this.codeMirrorInstance.setSize(this.width, window.innerHeight - this.chromeHeight) 142 | } 143 | 144 | _updateCodeMirror() { 145 | this.setCodeMirrorValue(this.getParticle("value").subparticlesToString()) 146 | } 147 | } 148 | 149 | module.exports = { CodeEditorComponent } 150 | -------------------------------------------------------------------------------- /components/EditorApp.js: -------------------------------------------------------------------------------- 1 | // prettier-ignore 2 | /*NODE_JS_ONLY*/ const { AbstractParticleComponentParser, ParticleComponentFrameworkDebuggerComponent } = require("scrollsdk/products/ParticleComponentFramework.node.js") 3 | const { Particle } = require("scrollsdk/products/Particle.js") 4 | 5 | const { TopBarComponent } = require("./TopBar.js") 6 | const { CodeEditorComponent } = require("./CodeEditor.js") 7 | const { BottomBarComponent } = require("./BottomBar.js") 8 | const { ShareComponent } = require("./Share.js") 9 | const { ExportComponent } = require("./Export.js") 10 | const { ScrollFileEditor } = require("./ScrollFileEditor.js") 11 | const { EditorHandleComponent } = require("./EditorHandle.js") 12 | const { ShowcaseComponent } = require("./Showcase.js") 13 | const { LocalStorageKeys, UrlKeys } = require("./Types.js") 14 | 15 | // prettier-ignore 16 | /*NODE_JS_ONLY*/ const defaultScrollParser = new (require("scroll-cli").DefaultScrollParser) 17 | 18 | class githubTriangleComponent extends AbstractParticleComponentParser { 19 | githubLink = `https://github.com/breck7/tryscroll` 20 | toHakonCode() { 21 | return `.AbstractGithubTriangleComponent 22 | display block 23 | position absolute 24 | top 0 25 | right 0 26 | z-index 3` 27 | } 28 | toStumpCode() { 29 | return `a 30 | class AbstractGithubTriangleComponent 31 | href ${this.githubLink} 32 | target _blank 33 | img 34 | height 40px 35 | src public/github-fork.svg` 36 | } 37 | } 38 | 39 | class ErrorParticle extends AbstractParticleComponentParser { 40 | _isErrorParser() { 41 | return true 42 | } 43 | toStumpCode() { 44 | console.error(`Warning: EditorApp does not have a Parser for "${this.getLine()}"`) 45 | return `span 46 | style display: none;` 47 | } 48 | } 49 | 50 | let _defaultSeed = Date.now() 51 | const newSeed = () => { 52 | _defaultSeed++ 53 | return _defaultSeed 54 | } 55 | 56 | class EditorApp extends AbstractParticleComponentParser { 57 | createParserPool() { 58 | return new Particle.ParserPool(ErrorParticle, { 59 | TopBarComponent, 60 | githubTriangleComponent, 61 | CodeEditorComponent, 62 | ParticleComponentFrameworkDebuggerComponent, 63 | BottomBarComponent, 64 | EditorHandleComponent, 65 | ShowcaseComponent, 66 | }) 67 | } 68 | 69 | verbose = true 70 | 71 | get leftStartPosition() { 72 | return this.editor.width 73 | } 74 | 75 | get mainOutput() { 76 | return this.scrollFileEditor.mainOutput 77 | } 78 | 79 | get mainProgram() { 80 | return this.scrollFileEditor.mainProgram 81 | } 82 | 83 | get editor() { 84 | return this.getParticle(CodeEditorComponent.name) 85 | } 86 | 87 | get bufferValue() { 88 | return this.editor.bufferValue 89 | } 90 | 91 | async loadNewDoc(bufferValue) { 92 | this.renderAndGetRenderReport() 93 | this.updateLocalStorage(bufferValue) 94 | await this.scrollFileEditor.buildMainProgram() 95 | this.refreshHtml() 96 | } 97 | 98 | async buildMainProgram() { 99 | await this.scrollFileEditor.buildMainProgram() 100 | } 101 | 102 | // todo: cleanup 103 | async pasteCodeCommand(bufferValue) { 104 | this.editor.setCodeMirrorValue(bufferValue) 105 | await this.loadNewDoc(bufferValue) 106 | } 107 | 108 | async formatScrollCommand() { 109 | const bufferValue = await this.scrollFileEditor.getFormatted() 110 | this.editor.setCodeMirrorValue(bufferValue) 111 | await this.loadNewDoc(bufferValue) 112 | await this.scrollFileEditor.buildMainProgram() 113 | } 114 | 115 | updateLocalStorage(bufferValue) { 116 | if (this.isNodeJs()) return // todo: tcf should shim this 117 | localStorage.setItem(LocalStorageKeys.scroll, bufferValue) 118 | console.log("Local storage updated...") 119 | } 120 | 121 | get fileName() { 122 | return "tryscroll.scroll" 123 | } 124 | 125 | async initScrollFileEditor(parsersCode) { 126 | this.scrollFileEditor = new ScrollFileEditor(parsersCode, this) 127 | await this.scrollFileEditor.init() 128 | } 129 | 130 | refreshHtml() { 131 | this.getParticle(`${ShowcaseComponent.name}`).refresh() 132 | // todo: rehighlight? 133 | } 134 | 135 | async start() { 136 | const { willowBrowser } = this 137 | this._bindParticleComponentFrameworkCommandListenersOnBody() 138 | this.renderAndGetRenderReport(willowBrowser.getBodyStumpParticle()) 139 | 140 | const keyboardShortcuts = this._getKeyboardShortcuts() 141 | Object.keys(keyboardShortcuts).forEach((key) => { 142 | willowBrowser.getMousetrap().bind(key, function (evt) { 143 | keyboardShortcuts[key]() 144 | // todo: handle the below when we need to 145 | if (evt.preventDefault) evt.preventDefault() 146 | return false 147 | }) 148 | }) 149 | 150 | this.willowBrowser.setResizeEndHandler(() => { 151 | this.editor.setSize() 152 | }) 153 | 154 | await this.buildMainProgram() 155 | // todo: rehighlight? 156 | } 157 | 158 | log(message) { 159 | if (this.verbose) console.log(message) 160 | } 161 | 162 | get urlHash() { 163 | const particle = new Particle() 164 | particle.appendLineAndSubparticles(UrlKeys.scroll, this.bufferValue ?? "") 165 | return "#" + encodeURIComponent(particle.asString) 166 | } 167 | 168 | _getKeyboardShortcuts() { 169 | return { 170 | d: () => this.toggleParticleComponentFrameworkDebuggerCommand(), 171 | w: () => this.resizeEditorCommand(), 172 | } 173 | } 174 | 175 | resizeEditorCommand(newSize = SIZES.EDITOR_WIDTH) { 176 | this.editor.setAtom(1, newSize) 177 | 178 | if (!this.isNodeJs()) localStorage.setItem(LocalStorageKeys.editorStartWidth, newSize) 179 | this.renderAndGetRenderReport() 180 | } 181 | } 182 | 183 | const SIZES = {} 184 | 185 | SIZES.BOARD_MARGIN = 20 186 | SIZES.TOP_BAR_HEIGHT = 28 187 | SIZES.BOTTOM_BAR_HEIGHT = 40 188 | SIZES.CHROME_HEIGHT = SIZES.TOP_BAR_HEIGHT + SIZES.BOTTOM_BAR_HEIGHT + SIZES.BOARD_MARGIN 189 | SIZES.TITLE_HEIGHT = 20 190 | 191 | SIZES.EDITOR_WIDTH = Math.floor(typeof window !== "undefined" ? window.innerWidth / 2 : 400) 192 | SIZES.RIGHT_BAR_WIDTH = 30 193 | 194 | EditorApp.setupApp = async (bufferValue, parsersCode, windowWidth = 1000, windowHeight = 1000) => { 195 | const editorStartWidth = 196 | typeof localStorage !== "undefined" 197 | ? (localStorage.getItem(LocalStorageKeys.editorStartWidth) ?? SIZES.EDITOR_WIDTH) 198 | : SIZES.EDITOR_WIDTH 199 | const startState = new Particle(`${githubTriangleComponent.name} 200 | ${TopBarComponent.name} 201 | ${ShareComponent.name} 202 | ${ExportComponent.name} 203 | ${BottomBarComponent.name} 204 | ${CodeEditorComponent.name} ${editorStartWidth} ${SIZES.CHROME_HEIGHT} 205 | value 206 | ${bufferValue.replace(/\n/g, "\n ")} 207 | ${EditorHandleComponent.name} 208 | ${ShowcaseComponent.name}`) 209 | 210 | const app = new EditorApp(startState.asString) 211 | await app.initScrollFileEditor(parsersCode) 212 | app.windowWidth = windowWidth 213 | app.windowHeight = windowHeight 214 | return app 215 | } 216 | 217 | module.exports = { EditorApp } 218 | -------------------------------------------------------------------------------- /.sparkline.js: -------------------------------------------------------------------------------- 1 | // https://github.com/mariusGundersen/sparkline 2 | ;(function (root, factory) { 3 | if (typeof define === "function" && define.amd) { 4 | // AMD. Register as an anonymous module. 5 | define(factory) 6 | } else if (typeof exports === "object") { 7 | // Node. Does not work with strict CommonJS, but 8 | // only CommonJS-like enviroments that support module.exports, 9 | // like Node. 10 | module.exports = factory() 11 | } else { 12 | // Browser globals (root is window) 13 | root.Sparkline = factory() 14 | } 15 | })(window, function () { 16 | function extend(specific, general) { 17 | var obj = {} 18 | for (var key in general) { 19 | obj[key] = key in specific ? specific[key] : general[key] 20 | } 21 | return obj 22 | } 23 | 24 | function Sparkline(element, options) { 25 | this.element = element 26 | this.options = extend(options || {}, Sparkline.options) 27 | 28 | init: { 29 | this.element.innerHTML = "" 30 | this.canvas = this.element.firstChild 31 | this.context = this.canvas.getContext("2d") 32 | this.ratio = window.devicePixelRatio || 1 33 | 34 | if (this.options.tooltip) { 35 | this.canvas.style.position = "relative" 36 | this.canvas.onmousemove = showTooltip.bind(this) 37 | } 38 | } 39 | } 40 | 41 | Sparkline.options = { 42 | width: 100, 43 | height: null, 44 | lineColor: "black", 45 | lineWidth: 1.5, 46 | startColor: "transparent", 47 | endColor: "black", 48 | maxColor: "transparent", 49 | minColor: "transparent", 50 | minValue: null, 51 | maxValue: null, 52 | minMaxValue: null, 53 | maxMinValue: null, 54 | dotRadius: 2.5, 55 | tooltip: null, 56 | fillBelow: true, 57 | fillLighten: 0.5, 58 | startLine: false, 59 | endLine: false, 60 | minLine: false, 61 | maxLine: false, 62 | bottomLine: false, 63 | topLine: false, 64 | averageLine: false 65 | } 66 | 67 | Sparkline.init = function (element, options) { 68 | return new Sparkline(element, options) 69 | } 70 | 71 | Sparkline.draw = function (element, points, options) { 72 | var sparkline = new Sparkline(element, options) 73 | sparkline.draw(points) 74 | return sparkline 75 | } 76 | 77 | function getY(minValue, maxValue, offsetY, height, index) { 78 | var range = maxValue - minValue 79 | if (range == 0) { 80 | return offsetY + height / 2 81 | } else { 82 | return offsetY + height - ((this[index] - minValue) / range) * height 83 | } 84 | } 85 | 86 | function drawDot(radius, x1, x2, color, line, x, y) { 87 | this.context.beginPath() 88 | this.context.fillStyle = color 89 | this.context.arc(x, y, radius, 0, Math.PI * 2, false) 90 | this.context.fill() 91 | drawLine.call(this, x1, x2, line, x, y) 92 | } 93 | 94 | function drawLine(x1, x2, style, x, y) { 95 | if (!style) return 96 | 97 | this.context.save() 98 | this.context.strokeStyle = style.color || "black" 99 | this.context.lineWidth = (style.width || 1) * this.ratio 100 | this.context.globalAlpha = style.alpha || 1 101 | this.context.beginPath() 102 | this.context.moveTo(style.direction != "right" ? x1 : x, y) 103 | this.context.lineTo(style.direction != "left" ? x2 : x, y) 104 | this.context.stroke() 105 | this.context.restore() 106 | } 107 | 108 | function showTooltip(e) { 109 | var x = e.offsetX || e.layerX || 0 110 | var delta = (this.options.width - this.options.dotRadius * 2) / (this.points.length - 1) 111 | var index = minmax(0, Math.round((x - this.options.dotRadius) / delta), this.points.length - 1) 112 | 113 | this.canvas.title = this.options.tooltip(this.points[index], index, this.points) 114 | } 115 | 116 | Sparkline.prototype.draw = function (points) { 117 | points = points || [] 118 | this.points = points 119 | 120 | this.canvas.width = this.options.width * this.ratio 121 | this.canvas.style.width = this.options.width + "px" 122 | 123 | var pxHeight = this.options.height || this.element.offsetHeight 124 | this.canvas.height = pxHeight * this.ratio 125 | this.canvas.style.height = pxHeight + "px" 126 | 127 | var lineWidth = this.options.lineWidth * this.ratio 128 | var offsetX = Math.max(this.options.dotRadius * this.ratio, lineWidth / 2) 129 | var offsetY = Math.max(this.options.dotRadius * this.ratio, lineWidth / 2) 130 | var width = this.canvas.width - offsetX * 2 131 | var height = this.canvas.height - offsetY * 2 132 | 133 | var minValue = Math.min.apply(Math, points) 134 | var maxValue = Math.max.apply(Math, points) 135 | var bottomValue = this.options.minValue != undefined ? this.options.minValue : Math.min(minValue, this.options.maxMinValue != undefined ? this.options.maxMinValue : minValue) 136 | var topValue = this.options.maxValue != undefined ? this.options.maxValue : Math.max(maxValue, this.options.minMaxValue != undefined ? this.options.minMaxValue : maxValue) 137 | var minX = offsetX 138 | var maxX = offsetX 139 | 140 | var x = offsetX 141 | var y = getY.bind(points, bottomValue, topValue, offsetY, height) 142 | var delta = width / (points.length - 1) 143 | 144 | var dot = drawDot.bind(this, this.options.dotRadius * this.ratio, offsetX, width + offsetX) 145 | var line = drawLine.bind(this, offsetX, width + offsetX) 146 | 147 | this.context.save() 148 | 149 | this.context.strokeStyle = this.options.lineColor 150 | this.context.fillStyle = this.options.lineColor 151 | this.context.lineWidth = lineWidth 152 | this.context.lineCap = "round" 153 | this.context.lineJoin = "round" 154 | 155 | if (this.options.fillBelow && points.length > 1) { 156 | this.context.save() 157 | this.context.beginPath() 158 | this.context.moveTo(x, y(0)) 159 | for (var i = 1; i < points.length; i++) { 160 | x += delta 161 | 162 | minX = points[i] == minValue ? x : minX 163 | maxX = points[i] == maxValue ? x : maxX 164 | 165 | this.context.lineTo(x, y(i)) 166 | } 167 | this.context.lineTo(width + offsetX, height + offsetY + lineWidth / 2) 168 | this.context.lineTo(offsetX, height + offsetY + lineWidth / 2) 169 | this.context.fill() 170 | if (this.options.fillLighten > 0) { 171 | this.context.fillStyle = "white" 172 | this.context.globalAlpha = this.options.fillLighten 173 | this.context.fill() 174 | this.context.globalAlpha = 1 175 | } else if (this.options.fillLighten < 0) { 176 | this.context.fillStyle = "black" 177 | this.context.globalAlpha = -this.options.fillLighten 178 | this.context.fill() 179 | } 180 | this.context.restore() 181 | } 182 | 183 | x = offsetX 184 | this.context.beginPath() 185 | this.context.moveTo(x, y(0)) 186 | for (var i = 1; i < points.length; i++) { 187 | x += delta 188 | this.context.lineTo(x, y(i)) 189 | } 190 | this.context.stroke() 191 | 192 | this.context.restore() 193 | 194 | line(this.options.bottomLine, 0, offsetY) 195 | line(this.options.topLine, 0, height + offsetY + lineWidth / 2) 196 | 197 | dot(this.options.startColor, this.options.startLine, offsetX + (points.length == 1 ? width / 2 : 0), y(0)) 198 | dot(this.options.endColor, this.options.endLine, offsetX + (points.length == 1 ? width / 2 : width), y(points.length - 1)) 199 | dot(this.options.minColor, this.options.minLine, minX + (points.length == 1 ? width / 2 : 0), y(points.indexOf(minValue))) 200 | dot(this.options.maxColor, this.options.maxLine, maxX + (points.length == 1 ? width / 2 : 0), y(points.indexOf(maxValue))) 201 | 202 | //line(this.options.averageLine, ) 203 | } 204 | 205 | function minmax(a, b, c) { 206 | return Math.max(a, Math.min(b, c)) 207 | } 208 | 209 | return Sparkline 210 | }) 211 | -------------------------------------------------------------------------------- /.tableSearch.js: -------------------------------------------------------------------------------- 1 | class Patch { 2 | constructor( 3 | patchInput = "", 4 | grammar = { 5 | rowDelimiter: "&", 6 | columnDelimiter: "=", 7 | encodedRowDelimiter: "%2E%2E%2E", 8 | encodedColumnDelimiter: "%7E" 9 | } 10 | ) { 11 | // The pipeline of encodings. Operations will be run in order for encoding (and reveresed for decoding). 12 | this.encoders = [ 13 | { 14 | encode: str => encodeURIComponent(str), 15 | decode: str => decodeURIComponent(str) 16 | }, 17 | { 18 | encode: str => this.replaceAll(str, this.grammar.columnDelimiter, this.grammar.encodedColumnDelimiter), 19 | decode: str => this.replaceAll(str, this.grammar.encodedColumnDelimiter, this.grammar.columnDelimiter) 20 | }, 21 | { 22 | encode: str => this.replaceAll(str, this.grammar.rowDelimiter, this.grammar.encodedRowDelimiter), 23 | decode: str => this.replaceAll(str, this.grammar.encodedRowDelimiter, this.grammar.rowDelimiter) 24 | }, 25 | { 26 | // Turn "%20" into "+" for prettier urls. 27 | encode: str => str.replace(/\%20/g, "+"), 28 | decode: str => str.replace(/\+/g, "%20") 29 | } 30 | ] 31 | this.grammar = grammar 32 | if (typeof patchInput === "string") this.uriEncodedString = patchInput 33 | else if (Array.isArray(patchInput)) this.uriEncodedString = this.arrayToEncodedString(patchInput) 34 | else this.uriEncodedString = this.objectToEncodedString(patchInput) 35 | } 36 | replaceAll(str, search, replace) { 37 | return str.split(search).join(replace) 38 | } 39 | objectToEncodedString(obj) { 40 | return Object.keys(obj) 41 | .map(identifierCell => { 42 | const value = obj[identifierCell] 43 | const valueCells = value instanceof Array ? value : [value] 44 | const row = [identifierCell, ...valueCells].map(cell => this.encodeCell(cell)) 45 | return row.join(this.grammar.columnDelimiter) 46 | }) 47 | .join(this.grammar.rowDelimiter) 48 | } 49 | arrayToEncodedString(arr) { 50 | return arr.map(line => line.map(cell => this.encodeCell(cell)).join(this.grammar.columnDelimiter)).join(this.grammar.rowDelimiter) 51 | } 52 | get array() { 53 | return this.uriEncodedString.split(this.grammar.rowDelimiter).map(line => line.split(this.grammar.columnDelimiter).map(cell => this.decodeCell(cell))) 54 | } 55 | get object() { 56 | const patchObj = {} 57 | if (!this.uriEncodedString) return patchObj 58 | this.array.forEach(cells => { 59 | const identifierCell = cells.shift() 60 | patchObj[identifierCell] = cells.length > 1 ? cells : cells[0] // If a single value, collapse to a simple tuple. todo: sure about this design? 61 | }) 62 | return patchObj 63 | } 64 | encodeCell(unencodedCell) { 65 | return this.encoders.reduce((str, encoder) => encoder.encode(str), unencodedCell) 66 | } 67 | decodeCell(encodedCell) { 68 | return this.encoders 69 | .slice() 70 | .reverse() 71 | .reduce((str, encoder) => encoder.decode(str), encodedCell) 72 | } 73 | } 74 | 75 | class TableSearchApp { 76 | constructor() { 77 | this.processTableHeaders() 78 | this.createDatatable() 79 | this.bindToHashChange() 80 | } 81 | 82 | get windowHash() { 83 | return window.location.hash.replace(/^#/, "") 84 | } 85 | 86 | get objectFromHash() { 87 | return new Patch(this.windowHash).object 88 | } 89 | 90 | setObjectOnHash(obj) { 91 | if (obj.order === "0.asc") delete obj.order 92 | Object.keys(obj).forEach(key => { 93 | if (obj[key] === "") delete obj[key] 94 | }) 95 | const newHash = new Patch(obj).uriEncodedString 96 | if (this.windowHash !== newHash) window.location.hash = newHash 97 | } 98 | 99 | processTableHeaders() { 100 | // Store date column information 101 | this.dateColumns = new Map() 102 | 103 | // Process table headers 104 | const table = document.querySelector("table.scrollTable") 105 | const headers = table.querySelectorAll("th") 106 | 107 | headers.forEach((header, index) => { 108 | const originalText = header.textContent 109 | if (originalText.startsWith("last")) { 110 | // Store the column index and the new name (without 'last') 111 | const newName = originalText.slice(4) // Remove 'last' prefix 112 | this.dateColumns.set(index, newName) 113 | header.textContent = newName 114 | } 115 | }) 116 | } 117 | 118 | createColumnDefs() { 119 | const columnDefs = [] 120 | this.dateColumns.forEach((newName, index) => { 121 | columnDefs.push({ 122 | targets: index, 123 | render: (data, type) => { 124 | if (type === "display") { 125 | // Parse the date and return relative time 126 | const timestamp = /^\d+$/.test(data) ? (String(data).length < 9 ? parseInt(data) * 1000 : parseInt(data)) : data 127 | const date = dayjs(timestamp) 128 | if (date.isValid()) { 129 | return `${date.fromNow()}` 130 | } 131 | } 132 | // Return original data for sorting/filtering 133 | return data 134 | } 135 | }) 136 | }) 137 | return columnDefs 138 | } 139 | 140 | bindToHashChange() { 141 | window.addEventListener("hashchange", () => { 142 | this.dataTables.search(this.searchFromHash).order(this.orderFromHash).draw(false) 143 | }) 144 | } 145 | 146 | createDatatable() { 147 | this.dataTables = jQuery("table.scrollTable").DataTable({ 148 | paging: false, 149 | stateSave: true, 150 | columnDefs: this.createColumnDefs(), 151 | stateSaveCallback: (settings, data) => { 152 | const order = data.order.map(([column, direction]) => `${column}.${direction}`).join(this.columnDelimiter) 153 | const patch = { 154 | q: data.search.search, 155 | order 156 | } 157 | this.setObjectOnHash(patch) 158 | }, 159 | layout: { 160 | topStart: { 161 | buttons: ["copy"] 162 | }, 163 | topEnd: { 164 | search: { 165 | placeholder: "Search" 166 | } 167 | } 168 | }, 169 | search: { search: this.searchFromHash }, 170 | order: this.orderFromHash, 171 | stateLoadCallback: settings => { 172 | return { 173 | search: { 174 | order: this.orderFromHash, 175 | search: this.searchFromHash 176 | } 177 | } 178 | } 179 | }) 180 | this.addExpandButtons() 181 | } 182 | 183 | addExpandButtons() { 184 | let buttons = document.querySelectorAll(".buttons-copy") 185 | 186 | buttons.forEach(button => { 187 | let ariaControls = button.getAttribute("aria-controls") 188 | let newHTML = `` 189 | 190 | button.insertAdjacentHTML("afterend", newHTML) 191 | 192 | let newButton = document.getElementById(`expandToggle${ariaControls}`) 193 | 194 | newButton.onclick = function () { 195 | this.innerHTML = this.innerHTML.includes("Expand") ? "Collapse" : "Expand" 196 | document.querySelector("#" + ariaControls).classList.toggle("expandedTable") 197 | } 198 | }) 199 | } 200 | 201 | get orderFromHash() { 202 | const order = this.objectFromHash.order 203 | return order 204 | ? order.split(this.columnDelimiter).map(o => { 205 | const parts = o.split(".") 206 | return [parseInt(parts[0]), parts[1]] 207 | }) 208 | : [] 209 | } 210 | 211 | columnDelimiter = "~" 212 | 213 | get searchFromHash() { 214 | return this.objectFromHash.q || "" 215 | } 216 | } 217 | 218 | document.addEventListener("DOMContentLoaded", () => (window.tableSearchApp = new TableSearchApp())) 219 | -------------------------------------------------------------------------------- /.scroll.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Derived Colors */ 3 | --scrollColorPrimary: rgba(var(--scrollPrimaryRgb), 0.8); 4 | --scrollColorPrimaryHover: rgba(var(--scrollPrimaryRgb), 0.9); 5 | --scrollColorPrimaryActive: rgb(var(--scrollPrimaryRgb)); 6 | 7 | --scrollColorSurface: rgba(var(--scrollSurfaceRgb), 0.4); 8 | --scrollColorSurfaceAlt: rgba(var(--scrollSurfaceRgb), 0.6); 9 | --scrollColorBorder: rgba(var(--scrollSurfaceRgb), 0.8); 10 | } 11 | 12 | html, 13 | body, 14 | div, 15 | span, 16 | p, 17 | ol, 18 | ul, 19 | li, 20 | table, 21 | figure { 22 | margin: 0; 23 | padding: 0; 24 | border: 0; 25 | vertical-align: baseline; 26 | border-spacing: 0; 27 | } 28 | 29 | html { 30 | background-color: var(--scrollColorBackground); 31 | font-family: var(--scrollFontPrimary); 32 | color: var(--scrollColorText); 33 | font-size: var(--scrollBaseFontSize); 34 | hyphens: auto; 35 | height: 100%; 36 | } 37 | 38 | .dropcap:first-letter { 39 | font-size: 3rem; 40 | line-height: 0.9em; 41 | margin-right: 0.125rem; 42 | display: block; 43 | float: left; 44 | } 45 | 46 | .abstractDinkusParser { 47 | text-align: center; 48 | padding: 1rem; 49 | } 50 | 51 | .abstractDinkusParser span { 52 | vertical-align: sub; 53 | } 54 | 55 | details { 56 | margin-top: 10px; 57 | } 58 | 59 | summary { 60 | font-family: var(--scrollFontUi); 61 | cursor: pointer; 62 | } 63 | 64 | .scrollCaptionedFigure { 65 | display: block; 66 | break-inside: avoid; 67 | max-width: 100%; 68 | text-align: center; 69 | } 70 | 71 | .scrollCaptionedFigure img { 72 | max-width: 100%; 73 | height: auto; 74 | margin-top: 0.1875rem; 75 | } 76 | 77 | .scrollCaptionedFigure figcaption { 78 | font-family: var(--scrollFontUi); 79 | font-size: 0.8rem; 80 | } 81 | 82 | .scrollCaptionedFigure figcaption .scrollParagraph { 83 | margin-top: 0; 84 | } 85 | 86 | .scrollCodeBlock { 87 | overflow: auto; 88 | font-size: 0.8rem; 89 | hyphens: none; 90 | white-space: pre; 91 | break-inside: avoid; 92 | display: block; 93 | margin: 0.5rem 0; 94 | padding: 0.5rem; 95 | border-radius: 0; 96 | position: relative; 97 | border-left: 0.5rem solid var(--scrollColorBorder); 98 | } 99 | 100 | .codeWithHeader { 101 | break-inside: avoid-column; 102 | margin: 10px 0; 103 | } 104 | 105 | .codeHeader { 106 | font-size: 80%; 107 | text-align: center; 108 | background: var(--scrollColorSurfaceAlt); 109 | border: 1px solid var(--scrollColorBorder); 110 | border-bottom: 0; 111 | margin-bottom: -7px; 112 | padding: 4px 2px; 113 | border-top-left-radius: 3px; 114 | border-top-right-radius: 3px; 115 | } 116 | 117 | .scrollCodeBlock:hover .scrollCopyButton { 118 | opacity: 0.5; 119 | } 120 | 121 | .scrollCodeBlock:hover .scrollCopyButton:hover { 122 | opacity: 0.8; 123 | } 124 | 125 | .scrollCodeBlock:hover .scrollCopyButton:active { 126 | opacity: 1; 127 | } 128 | 129 | .scrollCopyButton { 130 | position: absolute; 131 | top: 0.125rem; 132 | right: 0.125rem; 133 | font-size: 0.875rem; 134 | cursor: pointer; 135 | opacity: 0; 136 | } 137 | 138 | .scrollCopyButton::after { 139 | content: "[ ]"; 140 | } 141 | 142 | .scrollCopiedButton::after { 143 | content: "[✓]"; 144 | } 145 | 146 | ol, 147 | ul { 148 | padding-left: 1rem; 149 | } 150 | 151 | li { 152 | margin-top: 0.4rem; 153 | line-height: 1.4; 154 | } 155 | 156 | a { 157 | text-decoration-color: transparent; 158 | color: var(--scrollColorLink); 159 | } 160 | 161 | a:hover { 162 | text-decoration-color: initial; 163 | } 164 | 165 | .scrollButton { 166 | background: linear-gradient(180deg, var(--scrollColorPrimary) 0%, color-mix(in srgb, var(--scrollColorPrimary), black 15%) 100%); 167 | border-radius: 6px; 168 | color: white; 169 | padding: 10px 20px; 170 | display: inline-block; 171 | border: 0; 172 | cursor: pointer; 173 | transition: all 0.2s ease; 174 | transform: translateY(0); 175 | /* Halved shadow distances */ 176 | box-shadow: 177 | 0 1px 2px rgba(0, 0, 0, 0.1), 178 | /* Ambient shadow (halved) */ 0 1px 0 rgba(255, 255, 255, 0.2) inset, 179 | /* Top highlight */ 0 -1px 0 rgba(0, 0, 0, 0.2) inset, 180 | /* Bottom shadow (halved) */ 0 1.5px 0 color-mix(in srgb, var(--scrollColorPrimary), black 30%); /* 3D base (halved) */ 181 | } 182 | 183 | .scrollButton a { 184 | color: white; 185 | text-decoration: none; 186 | text-shadow: 0 0.5px 0.5px rgba(0, 0, 0, 0.2); /* Text depth (halved) */ 187 | } 188 | 189 | .scrollButton:hover { 190 | background: linear-gradient(180deg, color-mix(in srgb, var(--scrollColorPrimary), white 10%) 0%, var(--scrollColorPrimary) 100%); 191 | transform: translateY(-1px); /* Halved */ 192 | box-shadow: 193 | 0 2px 4px rgba(0, 0, 0, 0.15), 194 | /* Halved */ 0 1px 0 rgba(255, 255, 255, 0.2) inset, 195 | 0 -1px 0 rgba(0, 0, 0, 0.2) inset, 196 | 0 2.5px 0 color-mix(in srgb, var(--scrollColorPrimary), black 30%); /* Halved */ 197 | } 198 | 199 | .scrollButton:active { 200 | background: linear-gradient(180deg, color-mix(in srgb, var(--scrollColorPrimary), black 10%) 0%, var(--scrollColorPrimary) 100%); 201 | transform: translateY(1px); /* Halved */ 202 | box-shadow: 203 | 0 0.5px 1px rgba(0, 0, 0, 0.1), 204 | /* Halved */ 0 1px 0 rgba(255, 255, 255, 0.15) inset, 205 | 0 -0.5px 0 rgba(0, 0, 0, 0.2) inset, 206 | 0 0.5px 0 color-mix(in srgb, var(--scrollColorPrimary), black 30%); /* Halved */ 207 | } 208 | sup, 209 | sub { 210 | vertical-align: baseline; 211 | position: relative; 212 | top: -0.375rem; 213 | } 214 | 215 | sub { 216 | top: 0.375rem; 217 | } 218 | 219 | p { 220 | margin-top: 0.4rem; 221 | line-height: 1.4rem; 222 | } 223 | 224 | .scrollQuote { 225 | break-inside: avoid; 226 | display: block; 227 | margin: 0.5rem 0; 228 | padding: 0.5rem; 229 | background: var(--scrollColorSurface); 230 | white-space: pre-line; 231 | border-left: 0.5rem solid var(--scrollColorBorder); 232 | } 233 | 234 | .scrollInlineCode { 235 | font-family: var(--scrollFontMono); 236 | font-size: 0.9rem; 237 | background-color: var(--scrollColorSurface); 238 | padding: 0.125rem 0.25rem; 239 | border-radius: 0.25rem; 240 | } 241 | 242 | .scrollParagraph { 243 | text-align: justify; 244 | } 245 | 246 | center .scrollParagraph { 247 | text-align: center; 248 | } 249 | 250 | .subdued { 251 | color: var(--scrollColorSubdued); 252 | } 253 | 254 | .scrollColumns { 255 | column-count: auto; 256 | column-fill: balance; 257 | column-width: 35ch; 258 | column-gap: 1.5rem; 259 | padding-left: 1.25rem; 260 | padding-right: 1.25rem; 261 | margin: auto; 262 | } 263 | 264 | .scrollSnippetContainer { 265 | padding: 1ch 0; 266 | break-inside: avoid; 267 | text-align: justify; 268 | } 269 | 270 | .scrollContainerParser { 271 | padding: 0 1rem; 272 | } 273 | 274 | 275 | h1, 276 | h2, 277 | h3, 278 | h4 { 279 | margin: 0.625rem 0; 280 | } 281 | 282 | h1 { 283 | font-size: 1.25rem; 284 | } 285 | 286 | h2 { 287 | font-size: 1.125rem; 288 | } 289 | 290 | h3, 291 | h4 { 292 | font-size: 1rem; 293 | } 294 | 295 | h1.printTitleParser { 296 | text-align: center; 297 | margin: auto; 298 | margin-bottom: 0.15625rem; 299 | margin-top: 0; 300 | font-size: 1.75rem; 301 | max-width: calc(100vw - 2 * (1.5625rem + 1.875rem)); 302 | } 303 | 304 | h1.printTitleParser a { 305 | color: var(--scrollColorText); 306 | } 307 | 308 | .printDateParser { 309 | text-align: center; 310 | } 311 | .scrollDateline, 312 | .printDateParser { 313 | font-style: italic; 314 | line-height: 1.4rem; 315 | font-size: 0.75rem; 316 | } 317 | 318 | .scrollSection { 319 | break-inside: avoid; 320 | } 321 | 322 | .scrollSection h1, 323 | .scrollSection h2, 324 | .scrollSection h3, 325 | .scrollSection h4 { 326 | text-align: center; 327 | } 328 | 329 | h4.scrollQuestion { 330 | text-align: left; 331 | margin: 1.4rem 0 0 0; 332 | } 333 | 334 | .scrollSection:first-child h1, 335 | .scrollSection:first-child h2, 336 | .scrollSection:first-child h3, 337 | .scrollSection:first-child h4 { 338 | margin-top: 0; 339 | } 340 | 341 | .scrollSection:first-child h4.scrollQuestion { 342 | margin-top: 0; 343 | } 344 | 345 | .scrollNoteLink { 346 | opacity: 0.4; 347 | text-decoration: none; 348 | } 349 | 350 | .scrollNoteLink:hover { 351 | opacity: 1; 352 | } 353 | 354 | .scrollFootNoteUsageLink { 355 | opacity: 0.7; 356 | text-decoration: none; 357 | } 358 | 359 | .scrollFootNoteUsageLink:hover { 360 | opacity: 1; 361 | } 362 | 363 | .scrollHoverNote { 364 | text-decoration: underline dashed 1px rgba(0, 0, 0, 0.1); 365 | cursor: default; 366 | } 367 | 368 | .scrollTable { 369 | table-layout: fixed; 370 | font-family: var(--scrollFontUi); 371 | margin: 0.5rem 0; 372 | overflow: hidden; 373 | font-size: 0.8rem; 374 | width: 100%; 375 | hyphens: none; 376 | border: 1px solid var(--scrollColorBorder); 377 | } 378 | 379 | .scrollTable td, 380 | .scrollTable th { 381 | padding: 0.1875rem; 382 | overflow: hidden; 383 | white-space: nowrap; 384 | } 385 | 386 | .scrollTable th { 387 | text-transform: capitalize; 388 | border-bottom: 2px solid rgba(0, 0, 0, 0.6); 389 | text-align: left; 390 | } 391 | 392 | .scrollTable tr:nth-child(even) { 393 | background: var(--scrollColorSurface); 394 | } 395 | 396 | .scrollTable pre { 397 | white-space: nowrap; 398 | overflow: hidden; 399 | margin: 0; 400 | } 401 | 402 | .scrollTable.expandedTable { 403 | table-layout: unset; 404 | background: white; 405 | position: relative; 406 | z-index: 10; 407 | overflow: unset; 408 | } 409 | 410 | .scrollTable.expandedTable pre { 411 | white-space: unset; 412 | overflow: unset; 413 | } 414 | 415 | .scrollTable.expandedTable td, 416 | .scrollTable.expandedTable th { 417 | overflow: unset; 418 | white-space: unset; 419 | } 420 | 421 | .printAuthorsParser { 422 | font-size: 0.875rem; 423 | font-style: italic; 424 | margin: 0.25rem 0; 425 | text-align: center; 426 | } 427 | 428 | .abstractTextLinkParser { 429 | text-align: center; 430 | margin: 0.5em auto; 431 | font-family: Verdana; 432 | font-weight: 100; 433 | } 434 | 435 | .abstractTextLinkParser a { 436 | color: var(--scrollColorBorder); 437 | } 438 | 439 | .abstractTextLinkParser a:hover { 440 | color: #333; 441 | } 442 | 443 | .scrollContinueReadingLink { 444 | display: block; 445 | text-align: center; 446 | } 447 | 448 | .scrollDashboard { 449 | width: 100%; 450 | font-size: 1.875rem; 451 | text-align: center; 452 | font-weight: bold; 453 | break-inside: avoid; 454 | margin-top: 0.5rem; 455 | margin-bottom: 0.5rem; 456 | } 457 | 458 | .scrollDashboard td { 459 | width: 33.3%; 460 | border: 1px solid #e8e8e8; 461 | } 462 | 463 | .scrollDashboard span { 464 | font-size: 1.25rem; 465 | display: block; 466 | } 467 | 468 | .scrollChat span { 469 | font-family: Verdana; 470 | margin-top: 0.3125rem; 471 | padding: 0.3125rem 1.25rem; 472 | border-radius: 0.9375rem; 473 | display: inline-block; 474 | } 475 | 476 | .scrollChatLeft span { 477 | background: var(--scrollColorSurface); 478 | } 479 | 480 | .scrollChatRight span { 481 | color: white; 482 | background: rgb(0, 132, 255); 483 | } 484 | 485 | .scrollYouTubeHolder { 486 | position: relative; 487 | width: 100%; 488 | height: 0; 489 | padding-bottom: 56.25%; 490 | } 491 | 492 | .scrollYouTubeEmbed { 493 | position: absolute; 494 | top: 0; 495 | left: 0; 496 | width: 100%; 497 | height: 100%; 498 | } 499 | nav ul { 500 | list-style: none; 501 | margin: 0; 502 | padding: 0; 503 | display: flex; 504 | justify-content: center; 505 | } 506 | nav li { 507 | padding: 0 10px; 508 | } 509 | -------------------------------------------------------------------------------- /public/codeMirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | color: black; 8 | direction: ltr; 9 | } 10 | 11 | /* PADDING */ 12 | 13 | .CodeMirror-lines { 14 | padding: 4px 0; /* Vertical padding around content */ 15 | } 16 | .CodeMirror pre { 17 | padding: 0 4px; /* Horizontal padding of content */ 18 | } 19 | 20 | .CodeMirror-scrollbar-filler, 21 | .CodeMirror-gutter-filler { 22 | background-color: white; /* The little square between H and V scrollbars */ 23 | } 24 | 25 | /* GUTTER */ 26 | 27 | .CodeMirror-gutters { 28 | border-right: 1px solid #ddd; 29 | background-color: #f7f7f7; 30 | white-space: nowrap; 31 | } 32 | .CodeMirror-linenumbers { 33 | } 34 | .CodeMirror-linenumber { 35 | padding: 0 3px 0 5px; 36 | min-width: 20px; 37 | text-align: right; 38 | color: #999; 39 | white-space: nowrap; 40 | } 41 | 42 | .CodeMirror-guttermarker { 43 | color: black; 44 | } 45 | .CodeMirror-guttermarker-subtle { 46 | color: #999; 47 | } 48 | 49 | /* CURSOR */ 50 | 51 | .CodeMirror-cursor { 52 | border-left: 1px solid black; 53 | border-right: none; 54 | width: 0; 55 | } 56 | /* Shown when moving in bi-directional text */ 57 | .CodeMirror div.CodeMirror-secondarycursor { 58 | border-left: 1px solid silver; 59 | } 60 | .cm-fat-cursor .CodeMirror-cursor { 61 | width: auto; 62 | border: 0 !important; 63 | background: #7e7; 64 | } 65 | .cm-fat-cursor div.CodeMirror-cursors { 66 | z-index: 1; 67 | } 68 | .cm-fat-cursor-mark { 69 | background-color: rgba(20, 255, 20, 0.5); 70 | -webkit-animation: blink 1.06s steps(1) infinite; 71 | -moz-animation: blink 1.06s steps(1) infinite; 72 | animation: blink 1.06s steps(1) infinite; 73 | } 74 | .cm-animate-fat-cursor { 75 | width: auto; 76 | border: 0; 77 | -webkit-animation: blink 1.06s steps(1) infinite; 78 | -moz-animation: blink 1.06s steps(1) infinite; 79 | animation: blink 1.06s steps(1) infinite; 80 | background-color: #7e7; 81 | } 82 | @-moz-keyframes blink { 83 | 0% { 84 | } 85 | 50% { 86 | background-color: transparent; 87 | } 88 | 100% { 89 | } 90 | } 91 | @-webkit-keyframes blink { 92 | 0% { 93 | } 94 | 50% { 95 | background-color: transparent; 96 | } 97 | 100% { 98 | } 99 | } 100 | @keyframes blink { 101 | 0% { 102 | } 103 | 50% { 104 | background-color: transparent; 105 | } 106 | 100% { 107 | } 108 | } 109 | 110 | /* Can style cursor different in overwrite (non-insert) mode */ 111 | .CodeMirror-overwrite .CodeMirror-cursor { 112 | } 113 | 114 | .cm-tab { 115 | display: inline-block; 116 | text-decoration: inherit; 117 | } 118 | 119 | .CodeMirror-rulers { 120 | position: absolute; 121 | left: 0; 122 | right: 0; 123 | top: -50px; 124 | bottom: -20px; 125 | overflow: hidden; 126 | } 127 | .CodeMirror-ruler { 128 | border-left: 1px solid #ccc; 129 | top: 0; 130 | bottom: 0; 131 | position: absolute; 132 | } 133 | 134 | /* DEFAULT THEME */ 135 | 136 | .cm-s-default .cm-header { 137 | color: blue; 138 | } 139 | .cm-s-default .cm-quote { 140 | color: #090; 141 | } 142 | .cm-negative { 143 | color: #d44; 144 | } 145 | .cm-positive { 146 | color: #292; 147 | } 148 | .cm-header, 149 | .cm-strong { 150 | font-weight: bold; 151 | } 152 | .cm-em { 153 | font-style: italic; 154 | } 155 | .cm-link { 156 | text-decoration: underline; 157 | } 158 | .cm-strikethrough { 159 | text-decoration: line-through; 160 | } 161 | 162 | .cm-s-default .cm-keyword { 163 | color: #708; 164 | } 165 | .cm-s-default .cm-atom { 166 | color: #219; 167 | } 168 | .cm-s-default .cm-number { 169 | color: #164; 170 | } 171 | .cm-s-default .cm-def { 172 | color: #00f; 173 | } 174 | .cm-s-default .cm-variable, 175 | .cm-s-default .cm-punctuation, 176 | .cm-s-default .cm-property, 177 | .cm-s-default .cm-operator { 178 | } 179 | .cm-s-default .cm-variable-2 { 180 | color: #05a; 181 | } 182 | .cm-s-default .cm-variable-3, 183 | .cm-s-default .cm-type { 184 | color: #085; 185 | } 186 | .cm-s-default .cm-comment { 187 | color: #a50; 188 | } 189 | .cm-s-default .cm-string { 190 | color: #a11; 191 | } 192 | .cm-s-default .cm-string-2 { 193 | color: #f50; 194 | } 195 | .cm-s-default .cm-meta { 196 | color: #555; 197 | } 198 | .cm-s-default .cm-qualifier { 199 | color: #555; 200 | } 201 | .cm-s-default .cm-builtin { 202 | color: #30a; 203 | } 204 | .cm-s-default .cm-bracket { 205 | color: #997; 206 | } 207 | .cm-s-default .cm-tag { 208 | color: #170; 209 | } 210 | .cm-s-default .cm-attribute { 211 | color: #00c; 212 | } 213 | .cm-s-default .cm-hr { 214 | color: #999; 215 | } 216 | .cm-s-default .cm-link { 217 | color: #00c; 218 | } 219 | 220 | .cm-s-default .cm-error { 221 | color: #f00; 222 | } 223 | .cm-invalidchar { 224 | color: #f00; 225 | } 226 | 227 | .CodeMirror-composing { 228 | border-bottom: 2px solid; 229 | } 230 | 231 | /* Default styles for common addons */ 232 | 233 | div.CodeMirror span.CodeMirror-matchingbracket { 234 | color: #0b0; 235 | } 236 | div.CodeMirror span.CodeMirror-nonmatchingbracket { 237 | color: #a22; 238 | } 239 | .CodeMirror-matchingtag { 240 | background: rgba(255, 150, 0, 0.3); 241 | } 242 | .CodeMirror-activeline-background { 243 | background: #e8f2ff; 244 | } 245 | 246 | /* STOP */ 247 | 248 | /* The rest of this file contains styles related to the mechanics of 249 | the editor. You probably shouldn't touch them. */ 250 | 251 | .CodeMirror { 252 | position: relative; 253 | overflow: hidden; 254 | background: white; 255 | } 256 | 257 | .CodeMirror-scroll { 258 | overflow: scroll !important; /* Things will break if this is overridden */ 259 | /* 30px is the magic margin used to hide the element's real scrollbars */ 260 | /* See overflow: hidden in .CodeMirror */ 261 | margin-bottom: -30px; 262 | margin-right: -30px; 263 | padding-bottom: 30px; 264 | height: 100%; 265 | outline: none; /* Prevent dragging from highlighting the element */ 266 | position: relative; 267 | } 268 | .CodeMirror-sizer { 269 | position: relative; 270 | border-right: 30px solid transparent; 271 | } 272 | 273 | /* The fake, visible scrollbars. Used to force redraw during scrolling 274 | before actual scrolling happens, thus preventing shaking and 275 | flickering artifacts. */ 276 | .CodeMirror-vscrollbar, 277 | .CodeMirror-hscrollbar, 278 | .CodeMirror-scrollbar-filler, 279 | .CodeMirror-gutter-filler { 280 | position: absolute; 281 | z-index: 6; 282 | display: none; 283 | } 284 | .CodeMirror-vscrollbar { 285 | right: 0; 286 | top: 0; 287 | overflow-x: hidden; 288 | overflow-y: scroll; 289 | } 290 | .CodeMirror-hscrollbar { 291 | bottom: 0; 292 | left: 0; 293 | overflow-y: hidden; 294 | overflow-x: scroll; 295 | } 296 | .CodeMirror-scrollbar-filler { 297 | right: 0; 298 | bottom: 0; 299 | } 300 | .CodeMirror-gutter-filler { 301 | left: 0; 302 | bottom: 0; 303 | } 304 | 305 | .CodeMirror-gutters { 306 | position: absolute; 307 | left: 0; 308 | top: 0; 309 | min-height: 100%; 310 | z-index: 3; 311 | } 312 | .CodeMirror-gutter { 313 | white-space: normal; 314 | height: 100%; 315 | display: inline-block; 316 | vertical-align: top; 317 | margin-bottom: -30px; 318 | } 319 | .CodeMirror-gutter-wrapper { 320 | position: absolute; 321 | z-index: 4; 322 | background: none !important; 323 | border: none !important; 324 | } 325 | .CodeMirror-gutter-background { 326 | position: absolute; 327 | top: 0; 328 | bottom: 0; 329 | z-index: 4; 330 | } 331 | .CodeMirror-gutter-elt { 332 | position: absolute; 333 | cursor: default; 334 | z-index: 4; 335 | } 336 | .CodeMirror-gutter-wrapper ::selection { 337 | background-color: transparent; 338 | } 339 | .CodeMirror-gutter-wrapper ::-moz-selection { 340 | background-color: transparent; 341 | } 342 | 343 | .CodeMirror-lines { 344 | cursor: text; 345 | min-height: 1px; /* prevents collapsing before first draw */ 346 | } 347 | .CodeMirror pre { 348 | /* Reset some styles that the rest of the page might have set */ 349 | -moz-border-radius: 0; 350 | -webkit-border-radius: 0; 351 | border-radius: 0; 352 | border-width: 0; 353 | background: transparent; 354 | font-family: inherit; 355 | font-size: inherit; 356 | margin: 0; 357 | white-space: pre; 358 | word-wrap: normal; 359 | line-height: inherit; 360 | color: inherit; 361 | z-index: 2; 362 | position: relative; 363 | overflow: visible; 364 | -webkit-tap-highlight-color: transparent; 365 | -webkit-font-variant-ligatures: contextual; 366 | font-variant-ligatures: contextual; 367 | } 368 | .CodeMirror-wrap pre { 369 | word-wrap: break-word; 370 | white-space: pre-wrap; 371 | word-break: normal; 372 | } 373 | 374 | .CodeMirror-linebackground { 375 | position: absolute; 376 | left: 0; 377 | right: 0; 378 | top: 0; 379 | bottom: 0; 380 | z-index: 0; 381 | } 382 | 383 | .CodeMirror-linewidget { 384 | position: relative; 385 | z-index: 2; 386 | padding: 0.1px; /* Force widget margins to stay inside of the container */ 387 | } 388 | 389 | .CodeMirror-widget { 390 | } 391 | 392 | .CodeMirror-rtl pre { 393 | direction: rtl; 394 | } 395 | 396 | .CodeMirror-code { 397 | outline: none; 398 | } 399 | 400 | /* Force content-box sizing for the elements where we expect it */ 401 | .CodeMirror-scroll, 402 | .CodeMirror-sizer, 403 | .CodeMirror-gutter, 404 | .CodeMirror-gutters, 405 | .CodeMirror-linenumber { 406 | -moz-box-sizing: content-box; 407 | box-sizing: content-box; 408 | } 409 | 410 | .CodeMirror-measure { 411 | position: absolute; 412 | width: 100%; 413 | height: 0; 414 | overflow: hidden; 415 | visibility: hidden; 416 | } 417 | 418 | .CodeMirror-cursor { 419 | position: absolute; 420 | pointer-events: none; 421 | } 422 | .CodeMirror-measure pre { 423 | position: static; 424 | } 425 | 426 | div.CodeMirror-cursors { 427 | visibility: hidden; 428 | position: relative; 429 | z-index: 3; 430 | } 431 | div.CodeMirror-dragcursors { 432 | visibility: visible; 433 | } 434 | 435 | .CodeMirror-focused div.CodeMirror-cursors { 436 | visibility: visible; 437 | } 438 | 439 | .CodeMirror-selected { 440 | background: #d9d9d9; 441 | } 442 | .CodeMirror-focused .CodeMirror-selected { 443 | background: #d7d4f0; 444 | } 445 | .CodeMirror-crosshair { 446 | cursor: crosshair; 447 | } 448 | .CodeMirror-line::selection, 449 | .CodeMirror-line > span::selection, 450 | .CodeMirror-line > span > span::selection { 451 | background: #d7d4f0; 452 | } 453 | .CodeMirror-line::-moz-selection, 454 | .CodeMirror-line > span::-moz-selection, 455 | .CodeMirror-line > span > span::-moz-selection { 456 | background: #d7d4f0; 457 | } 458 | 459 | .cm-searching { 460 | background-color: #ffa; 461 | background-color: rgba(255, 255, 0, 0.4); 462 | } 463 | 464 | /* Used to force a border model for a node */ 465 | .cm-force-border { 466 | padding-right: 0.1px; 467 | } 468 | 469 | @media print { 470 | /* Hide the cursor when printing */ 471 | .CodeMirror div.CodeMirror-cursors { 472 | visibility: hidden; 473 | } 474 | } 475 | 476 | /* See issue #2901 */ 477 | .cm-tab-wrap-hack:after { 478 | content: ""; 479 | } 480 | 481 | /* Help users use markselection to safely style text background */ 482 | span.CodeMirror-selectedtext { 483 | background: none; 484 | } 485 | 486 | .CodeMirror-hints { 487 | position: absolute; 488 | z-index: 10; 489 | overflow: hidden; 490 | list-style: none; 491 | 492 | margin: 0; 493 | padding: 2px; 494 | 495 | -webkit-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2); 496 | -moz-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2); 497 | box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2); 498 | border-radius: 3px; 499 | border: 1px solid silver; 500 | 501 | background: white; 502 | font-size: 90%; 503 | font-family: monospace; 504 | 505 | max-height: 20em; 506 | overflow-y: auto; 507 | } 508 | 509 | .CodeMirror-hint { 510 | margin: 0; 511 | padding: 0 4px; 512 | border-radius: 2px; 513 | white-space: pre; 514 | color: black; 515 | cursor: pointer; 516 | } 517 | 518 | li.CodeMirror-hint-active { 519 | background: #08f; 520 | color: white; 521 | } 522 | 523 | /* BASICS */ 524 | .CodeMirror { 525 | /* Modern font stack optimized for code */ 526 | font-family: "JetBrains Mono", "Fira Code", "IBM Plex Mono", "Source Code Pro", Consolas, monospace; 527 | color: #2a2a2a; 528 | } 529 | 530 | /* Enhancing the general UI elements */ 531 | .CodeMirror-gutters { 532 | border-right: 1px solid #e1e4e8; 533 | background-color: #f6f8fa; 534 | } 535 | 536 | .CodeMirror-linenumber { 537 | color: #6e7781; 538 | } 539 | 540 | /* Selection colors */ 541 | .CodeMirror-selected { 542 | background: #e3e8f3; 543 | } 544 | 545 | .CodeMirror-focused .CodeMirror-selected { 546 | background: #d0d8eb; 547 | } 548 | 549 | /* Cursor styling */ 550 | .CodeMirror-cursor { 551 | border-left: 2px solid #4a89dc; 552 | } 553 | 554 | /* Syntax highlighting colors */ 555 | .cm-s-default .cm-keyword { 556 | color: #9333ea; /* Purple for keywords */ 557 | } 558 | 559 | .cm-s-default .cm-atom { 560 | color: #0891b2; /* Cyan for atoms */ 561 | } 562 | 563 | .cm-s-default .cm-number { 564 | color: #0d9488; /* Teal for numbers */ 565 | } 566 | 567 | .cm-s-default .cm-def { 568 | color: #2563eb; /* Blue for definitions */ 569 | } 570 | 571 | .cm-s-default .cm-variable { 572 | color: #334155; /* Slate for variables */ 573 | } 574 | 575 | .cm-s-default .cm-variable-2 { 576 | color: #3b82f6; /* Lighter blue for special variables */ 577 | } 578 | 579 | .cm-s-default .cm-variable-3, 580 | .cm-s-default .cm-type { 581 | color: #0d9488; /* Teal for types */ 582 | } 583 | 584 | .cm-s-default .cm-comment { 585 | color: #64748b; /* Slate for comments */ 586 | font-style: italic; 587 | } 588 | 589 | .cm-s-default .cm-string { 590 | color: #333; 591 | } 592 | 593 | .cm-s-default .cm-string-2 { 594 | color: #15803d; /* Darker green for special strings */ 595 | } 596 | 597 | .cm-s-default .cm-meta { 598 | color: #6b7280; /* Gray for metadata */ 599 | } 600 | 601 | .cm-s-default .cm-builtin { 602 | color: #7c3aed; /* Violet for built-in functions */ 603 | } 604 | 605 | .cm-noPaintDefinedInParsers { 606 | color: #888; 607 | } 608 | 609 | .cm-s-default .cm-tag { 610 | color: #ea580c; /* Orange for tags */ 611 | } 612 | 613 | .cm-s-default .cm-attribute { 614 | color: #0284c7; /* Sky blue for attributes */ 615 | } 616 | 617 | /* Error highlighting */ 618 | .cm-s-default .cm-error { 619 | color: #dc2626; /* Red for errors */ 620 | text-decoration: underline wavy #dc2626; 621 | } 622 | -------------------------------------------------------------------------------- /.dayjs.min.js: -------------------------------------------------------------------------------- 1 | !(function (t, e) { 2 | "object" == typeof exports && "undefined" != typeof module ? (module.exports = e()) : "function" == typeof define && define.amd ? define(e) : ((t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs = e()) 3 | })(this, function () { 4 | "use strict" 5 | var t = 1e3, 6 | e = 6e4, 7 | n = 36e5, 8 | r = "millisecond", 9 | i = "second", 10 | s = "minute", 11 | u = "hour", 12 | a = "day", 13 | o = "week", 14 | c = "month", 15 | f = "quarter", 16 | h = "year", 17 | d = "date", 18 | l = "Invalid Date", 19 | $ = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/, 20 | y = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g, 21 | M = { 22 | name: "en", 23 | weekdays: "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), 24 | months: "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), 25 | ordinal: function (t) { 26 | var e = ["th", "st", "nd", "rd"], 27 | n = t % 100 28 | return "[" + t + (e[(n - 20) % 10] || e[n] || e[0]) + "]" 29 | } 30 | }, 31 | m = function (t, e, n) { 32 | var r = String(t) 33 | return !r || r.length >= e ? t : "" + Array(e + 1 - r.length).join(n) + t 34 | }, 35 | v = { 36 | s: m, 37 | z: function (t) { 38 | var e = -t.utcOffset(), 39 | n = Math.abs(e), 40 | r = Math.floor(n / 60), 41 | i = n % 60 42 | return (e <= 0 ? "+" : "-") + m(r, 2, "0") + ":" + m(i, 2, "0") 43 | }, 44 | m: function t(e, n) { 45 | if (e.date() < n.date()) return -t(n, e) 46 | var r = 12 * (n.year() - e.year()) + (n.month() - e.month()), 47 | i = e.clone().add(r, c), 48 | s = n - i < 0, 49 | u = e.clone().add(r + (s ? -1 : 1), c) 50 | return +(-(r + (n - i) / (s ? i - u : u - i)) || 0) 51 | }, 52 | a: function (t) { 53 | return t < 0 ? Math.ceil(t) || 0 : Math.floor(t) 54 | }, 55 | p: function (t) { 56 | return ( 57 | { M: c, y: h, w: o, d: a, D: d, h: u, m: s, s: i, ms: r, Q: f }[t] || 58 | String(t || "") 59 | .toLowerCase() 60 | .replace(/s$/, "") 61 | ) 62 | }, 63 | u: function (t) { 64 | return void 0 === t 65 | } 66 | }, 67 | g = "en", 68 | D = {} 69 | D[g] = M 70 | var p = "$isDayjsObject", 71 | S = function (t) { 72 | return t instanceof _ || !(!t || !t[p]) 73 | }, 74 | w = function t(e, n, r) { 75 | var i 76 | if (!e) return g 77 | if ("string" == typeof e) { 78 | var s = e.toLowerCase() 79 | D[s] && (i = s), n && ((D[s] = n), (i = s)) 80 | var u = e.split("-") 81 | if (!i && u.length > 1) return t(u[0]) 82 | } else { 83 | var a = e.name 84 | ;(D[a] = e), (i = a) 85 | } 86 | return !r && i && (g = i), i || (!r && g) 87 | }, 88 | O = function (t, e) { 89 | if (S(t)) return t.clone() 90 | var n = "object" == typeof e ? e : {} 91 | return (n.date = t), (n.args = arguments), new _(n) 92 | }, 93 | b = v 94 | ;(b.l = w), 95 | (b.i = S), 96 | (b.w = function (t, e) { 97 | return O(t, { locale: e.$L, utc: e.$u, x: e.$x, $offset: e.$offset }) 98 | }) 99 | var _ = (function () { 100 | function M(t) { 101 | ;(this.$L = w(t.locale, null, !0)), this.parse(t), (this.$x = this.$x || t.x || {}), (this[p] = !0) 102 | } 103 | var m = M.prototype 104 | return ( 105 | (m.parse = function (t) { 106 | ;(this.$d = (function (t) { 107 | var e = t.date, 108 | n = t.utc 109 | if (null === e) return new Date(NaN) 110 | if (b.u(e)) return new Date() 111 | if (e instanceof Date) return new Date(e) 112 | if ("string" == typeof e && !/Z$/i.test(e)) { 113 | var r = e.match($) 114 | if (r) { 115 | var i = r[2] - 1 || 0, 116 | s = (r[7] || "0").substring(0, 3) 117 | return n ? new Date(Date.UTC(r[1], i, r[3] || 1, r[4] || 0, r[5] || 0, r[6] || 0, s)) : new Date(r[1], i, r[3] || 1, r[4] || 0, r[5] || 0, r[6] || 0, s) 118 | } 119 | } 120 | return new Date(e) 121 | })(t)), 122 | this.init() 123 | }), 124 | (m.init = function () { 125 | var t = this.$d 126 | ;(this.$y = t.getFullYear()), (this.$M = t.getMonth()), (this.$D = t.getDate()), (this.$W = t.getDay()), (this.$H = t.getHours()), (this.$m = t.getMinutes()), (this.$s = t.getSeconds()), (this.$ms = t.getMilliseconds()) 127 | }), 128 | (m.$utils = function () { 129 | return b 130 | }), 131 | (m.isValid = function () { 132 | return !(this.$d.toString() === l) 133 | }), 134 | (m.isSame = function (t, e) { 135 | var n = O(t) 136 | return this.startOf(e) <= n && n <= this.endOf(e) 137 | }), 138 | (m.isAfter = function (t, e) { 139 | return O(t) < this.startOf(e) 140 | }), 141 | (m.isBefore = function (t, e) { 142 | return this.endOf(e) < O(t) 143 | }), 144 | (m.$g = function (t, e, n) { 145 | return b.u(t) ? this[e] : this.set(n, t) 146 | }), 147 | (m.unix = function () { 148 | return Math.floor(this.valueOf() / 1e3) 149 | }), 150 | (m.valueOf = function () { 151 | return this.$d.getTime() 152 | }), 153 | (m.startOf = function (t, e) { 154 | var n = this, 155 | r = !!b.u(e) || e, 156 | f = b.p(t), 157 | l = function (t, e) { 158 | var i = b.w(n.$u ? Date.UTC(n.$y, e, t) : new Date(n.$y, e, t), n) 159 | return r ? i : i.endOf(a) 160 | }, 161 | $ = function (t, e) { 162 | return b.w(n.toDate()[t].apply(n.toDate("s"), (r ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(e)), n) 163 | }, 164 | y = this.$W, 165 | M = this.$M, 166 | m = this.$D, 167 | v = "set" + (this.$u ? "UTC" : "") 168 | switch (f) { 169 | case h: 170 | return r ? l(1, 0) : l(31, 11) 171 | case c: 172 | return r ? l(1, M) : l(0, M + 1) 173 | case o: 174 | var g = this.$locale().weekStart || 0, 175 | D = (y < g ? y + 7 : y) - g 176 | return l(r ? m - D : m + (6 - D), M) 177 | case a: 178 | case d: 179 | return $(v + "Hours", 0) 180 | case u: 181 | return $(v + "Minutes", 1) 182 | case s: 183 | return $(v + "Seconds", 2) 184 | case i: 185 | return $(v + "Milliseconds", 3) 186 | default: 187 | return this.clone() 188 | } 189 | }), 190 | (m.endOf = function (t) { 191 | return this.startOf(t, !1) 192 | }), 193 | (m.$set = function (t, e) { 194 | var n, 195 | o = b.p(t), 196 | f = "set" + (this.$u ? "UTC" : ""), 197 | l = ((n = {}), (n[a] = f + "Date"), (n[d] = f + "Date"), (n[c] = f + "Month"), (n[h] = f + "FullYear"), (n[u] = f + "Hours"), (n[s] = f + "Minutes"), (n[i] = f + "Seconds"), (n[r] = f + "Milliseconds"), n)[o], 198 | $ = o === a ? this.$D + (e - this.$W) : e 199 | if (o === c || o === h) { 200 | var y = this.clone().set(d, 1) 201 | y.$d[l]($), y.init(), (this.$d = y.set(d, Math.min(this.$D, y.daysInMonth())).$d) 202 | } else l && this.$d[l]($) 203 | return this.init(), this 204 | }), 205 | (m.set = function (t, e) { 206 | return this.clone().$set(t, e) 207 | }), 208 | (m.get = function (t) { 209 | return this[b.p(t)]() 210 | }), 211 | (m.add = function (r, f) { 212 | var d, 213 | l = this 214 | r = Number(r) 215 | var $ = b.p(f), 216 | y = function (t) { 217 | var e = O(l) 218 | return b.w(e.date(e.date() + Math.round(t * r)), l) 219 | } 220 | if ($ === c) return this.set(c, this.$M + r) 221 | if ($ === h) return this.set(h, this.$y + r) 222 | if ($ === a) return y(1) 223 | if ($ === o) return y(7) 224 | var M = ((d = {}), (d[s] = e), (d[u] = n), (d[i] = t), d)[$] || 1, 225 | m = this.$d.getTime() + r * M 226 | return b.w(m, this) 227 | }), 228 | (m.subtract = function (t, e) { 229 | return this.add(-1 * t, e) 230 | }), 231 | (m.format = function (t) { 232 | var e = this, 233 | n = this.$locale() 234 | if (!this.isValid()) return n.invalidDate || l 235 | var r = t || "YYYY-MM-DDTHH:mm:ssZ", 236 | i = b.z(this), 237 | s = this.$H, 238 | u = this.$m, 239 | a = this.$M, 240 | o = n.weekdays, 241 | c = n.months, 242 | f = n.meridiem, 243 | h = function (t, n, i, s) { 244 | return (t && (t[n] || t(e, r))) || i[n].slice(0, s) 245 | }, 246 | d = function (t) { 247 | return b.s(s % 12 || 12, t, "0") 248 | }, 249 | $ = 250 | f || 251 | function (t, e, n) { 252 | var r = t < 12 ? "AM" : "PM" 253 | return n ? r.toLowerCase() : r 254 | } 255 | return r.replace(y, function (t, r) { 256 | return ( 257 | r || 258 | (function (t) { 259 | switch (t) { 260 | case "YY": 261 | return String(e.$y).slice(-2) 262 | case "YYYY": 263 | return b.s(e.$y, 4, "0") 264 | case "M": 265 | return a + 1 266 | case "MM": 267 | return b.s(a + 1, 2, "0") 268 | case "MMM": 269 | return h(n.monthsShort, a, c, 3) 270 | case "MMMM": 271 | return h(c, a) 272 | case "D": 273 | return e.$D 274 | case "DD": 275 | return b.s(e.$D, 2, "0") 276 | case "d": 277 | return String(e.$W) 278 | case "dd": 279 | return h(n.weekdaysMin, e.$W, o, 2) 280 | case "ddd": 281 | return h(n.weekdaysShort, e.$W, o, 3) 282 | case "dddd": 283 | return o[e.$W] 284 | case "H": 285 | return String(s) 286 | case "HH": 287 | return b.s(s, 2, "0") 288 | case "h": 289 | return d(1) 290 | case "hh": 291 | return d(2) 292 | case "a": 293 | return $(s, u, !0) 294 | case "A": 295 | return $(s, u, !1) 296 | case "m": 297 | return String(u) 298 | case "mm": 299 | return b.s(u, 2, "0") 300 | case "s": 301 | return String(e.$s) 302 | case "ss": 303 | return b.s(e.$s, 2, "0") 304 | case "SSS": 305 | return b.s(e.$ms, 3, "0") 306 | case "Z": 307 | return i 308 | } 309 | return null 310 | })(t) || 311 | i.replace(":", "") 312 | ) 313 | }) 314 | }), 315 | (m.utcOffset = function () { 316 | return 15 * -Math.round(this.$d.getTimezoneOffset() / 15) 317 | }), 318 | (m.diff = function (r, d, l) { 319 | var $, 320 | y = this, 321 | M = b.p(d), 322 | m = O(r), 323 | v = (m.utcOffset() - this.utcOffset()) * e, 324 | g = this - m, 325 | D = function () { 326 | return b.m(y, m) 327 | } 328 | switch (M) { 329 | case h: 330 | $ = D() / 12 331 | break 332 | case c: 333 | $ = D() 334 | break 335 | case f: 336 | $ = D() / 3 337 | break 338 | case o: 339 | $ = (g - v) / 6048e5 340 | break 341 | case a: 342 | $ = (g - v) / 864e5 343 | break 344 | case u: 345 | $ = g / n 346 | break 347 | case s: 348 | $ = g / e 349 | break 350 | case i: 351 | $ = g / t 352 | break 353 | default: 354 | $ = g 355 | } 356 | return l ? $ : b.a($) 357 | }), 358 | (m.daysInMonth = function () { 359 | return this.endOf(c).$D 360 | }), 361 | (m.$locale = function () { 362 | return D[this.$L] 363 | }), 364 | (m.locale = function (t, e) { 365 | if (!t) return this.$L 366 | var n = this.clone(), 367 | r = w(t, e, !0) 368 | return r && (n.$L = r), n 369 | }), 370 | (m.clone = function () { 371 | return b.w(this.$d, this) 372 | }), 373 | (m.toDate = function () { 374 | return new Date(this.valueOf()) 375 | }), 376 | (m.toJSON = function () { 377 | return this.isValid() ? this.toISOString() : null 378 | }), 379 | (m.toISOString = function () { 380 | return this.$d.toISOString() 381 | }), 382 | (m.toString = function () { 383 | return this.$d.toUTCString() 384 | }), 385 | M 386 | ) 387 | })(), 388 | k = _.prototype 389 | return ( 390 | (O.prototype = k), 391 | [ 392 | ["$ms", r], 393 | ["$s", i], 394 | ["$m", s], 395 | ["$H", u], 396 | ["$W", a], 397 | ["$M", c], 398 | ["$y", h], 399 | ["$D", d] 400 | ].forEach(function (t) { 401 | k[t[1]] = function (e) { 402 | return this.$g(e, t[0], t[1]) 403 | } 404 | }), 405 | (O.extend = function (t, e) { 406 | return t.$i || (t(e, _, O), (t.$i = !0)), O 407 | }), 408 | (O.locale = w), 409 | (O.isDayjs = S), 410 | (O.unix = function (t) { 411 | return O(1e3 * t) 412 | }), 413 | (O.en = D[g]), 414 | (O.Ls = D), 415 | (O.p = {}), 416 | O 417 | ) 418 | }) 419 | 420 | !function(r,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(r="undefined"!=typeof globalThis?globalThis:r||self).dayjs_plugin_relativeTime=e()}(this,(function(){"use strict";return function(r,e,t){r=r||{};var n=e.prototype,o={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"};function i(r,e,t,o){return n.fromToBase(r,e,t,o)}t.en.relativeTime=o,n.fromToBase=function(e,n,i,d,u){for(var f,a,s,l=i.$locale().relativeTime||o,h=r.thresholds||[{l:"s",r:44,d:"second"},{l:"m",r:89},{l:"mm",r:44,d:"minute"},{l:"h",r:89},{l:"hh",r:21,d:"hour"},{l:"d",r:35},{l:"dd",r:25,d:"day"},{l:"M",r:45},{l:"MM",r:10,d:"month"},{l:"y",r:17},{l:"yy",d:"year"}],m=h.length,c=0;c0,p<=y.r||!y.r){p<=1&&c>0&&(y=h[c-1]);var v=l[y.l];u&&(p=u(""+p)),a="string"==typeof v?v.replace("%d",p):v(p,n,y.l,s);break}}if(n)return a;var M=s?l.future:l.past;return"function"==typeof M?M(a):M.replace("%s",a)},n.to=function(r,e){return i(r,e,this,!0)},n.from=function(r,e){return i(r,e,this)};var d=function(r){return r.$u?t.utc():t()};n.toNow=function(r){return this.to(d(this),r)},n.fromNow=function(r){return this.from(d(this),r)}}})); 421 | dayjs.extend(window.dayjs_plugin_relativeTime); 422 | 423 | -------------------------------------------------------------------------------- /.leaflet.css: -------------------------------------------------------------------------------- 1 | /* required styles */ 2 | 3 | .leaflet-pane, 4 | .leaflet-tile, 5 | .leaflet-marker-icon, 6 | .leaflet-marker-shadow, 7 | .leaflet-tile-container, 8 | .leaflet-pane > svg, 9 | .leaflet-pane > canvas, 10 | .leaflet-zoom-box, 11 | .leaflet-image-layer, 12 | .leaflet-layer { 13 | position: absolute; 14 | left: 0; 15 | top: 0; 16 | } 17 | .leaflet-container { 18 | overflow: hidden; 19 | } 20 | .leaflet-tile, 21 | .leaflet-marker-icon, 22 | .leaflet-marker-shadow { 23 | -webkit-user-select: none; 24 | -moz-user-select: none; 25 | user-select: none; 26 | -webkit-user-drag: none; 27 | } 28 | /* Prevents IE11 from highlighting tiles in blue */ 29 | .leaflet-tile::selection { 30 | background: transparent; 31 | } 32 | /* Safari renders non-retina tile on retina better with this, but Chrome is worse */ 33 | .leaflet-safari .leaflet-tile { 34 | image-rendering: -webkit-optimize-contrast; 35 | } 36 | /* hack that prevents hw layers "stretching" when loading new tiles */ 37 | .leaflet-safari .leaflet-tile-container { 38 | width: 1600px; 39 | height: 1600px; 40 | -webkit-transform-origin: 0 0; 41 | } 42 | .leaflet-marker-icon, 43 | .leaflet-marker-shadow { 44 | display: block; 45 | } 46 | /* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ 47 | /* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ 48 | .leaflet-container .leaflet-overlay-pane svg, 49 | .leaflet-container .leaflet-marker-pane img, 50 | .leaflet-container .leaflet-shadow-pane img, 51 | .leaflet-container .leaflet-tile-pane img, 52 | .leaflet-container img.leaflet-image-layer, 53 | .leaflet-container .leaflet-tile { 54 | max-width: none !important; 55 | max-height: none !important; 56 | } 57 | 58 | .leaflet-container.leaflet-touch-zoom { 59 | -ms-touch-action: pan-x pan-y; 60 | touch-action: pan-x pan-y; 61 | } 62 | .leaflet-container.leaflet-touch-drag { 63 | -ms-touch-action: pinch-zoom; 64 | /* Fallback for FF which doesn't support pinch-zoom */ 65 | touch-action: none; 66 | touch-action: pinch-zoom; 67 | } 68 | .leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { 69 | -ms-touch-action: none; 70 | touch-action: none; 71 | } 72 | .leaflet-container { 73 | -webkit-tap-highlight-color: transparent; 74 | } 75 | .leaflet-container a { 76 | -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); 77 | } 78 | .leaflet-tile { 79 | filter: inherit; 80 | visibility: hidden; 81 | } 82 | .leaflet-tile-loaded { 83 | visibility: inherit; 84 | } 85 | .leaflet-zoom-box { 86 | width: 0; 87 | height: 0; 88 | -moz-box-sizing: border-box; 89 | box-sizing: border-box; 90 | z-index: 800; 91 | } 92 | /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ 93 | .leaflet-overlay-pane svg { 94 | -moz-user-select: none; 95 | } 96 | 97 | .leaflet-pane { z-index: 400; } 98 | 99 | .leaflet-tile-pane { z-index: 200; } 100 | .leaflet-overlay-pane { z-index: 400; } 101 | .leaflet-shadow-pane { z-index: 500; } 102 | .leaflet-marker-pane { z-index: 600; } 103 | .leaflet-tooltip-pane { z-index: 650; } 104 | .leaflet-popup-pane { z-index: 700; } 105 | 106 | .leaflet-map-pane canvas { z-index: 100; } 107 | .leaflet-map-pane svg { z-index: 200; } 108 | 109 | .leaflet-vml-shape { 110 | width: 1px; 111 | height: 1px; 112 | } 113 | .lvml { 114 | behavior: url(#default#VML); 115 | display: inline-block; 116 | position: absolute; 117 | } 118 | 119 | 120 | /* control positioning */ 121 | 122 | .leaflet-control { 123 | position: relative; 124 | z-index: 800; 125 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ 126 | pointer-events: auto; 127 | } 128 | .leaflet-top, 129 | .leaflet-bottom { 130 | position: absolute; 131 | z-index: 1000; 132 | pointer-events: none; 133 | } 134 | .leaflet-top { 135 | top: 0; 136 | } 137 | .leaflet-right { 138 | right: 0; 139 | } 140 | .leaflet-bottom { 141 | bottom: 0; 142 | } 143 | .leaflet-left { 144 | left: 0; 145 | } 146 | .leaflet-control { 147 | float: left; 148 | clear: both; 149 | } 150 | .leaflet-right .leaflet-control { 151 | float: right; 152 | } 153 | .leaflet-top .leaflet-control { 154 | margin-top: 10px; 155 | } 156 | .leaflet-bottom .leaflet-control { 157 | margin-bottom: 10px; 158 | } 159 | .leaflet-left .leaflet-control { 160 | margin-left: 10px; 161 | } 162 | .leaflet-right .leaflet-control { 163 | margin-right: 10px; 164 | } 165 | 166 | 167 | /* zoom and fade animations */ 168 | 169 | .leaflet-fade-anim .leaflet-tile { 170 | will-change: opacity; 171 | } 172 | .leaflet-fade-anim .leaflet-popup { 173 | opacity: 0; 174 | -webkit-transition: opacity 0.2s linear; 175 | -moz-transition: opacity 0.2s linear; 176 | transition: opacity 0.2s linear; 177 | } 178 | .leaflet-fade-anim .leaflet-map-pane .leaflet-popup { 179 | opacity: 1; 180 | } 181 | .leaflet-zoom-animated { 182 | -webkit-transform-origin: 0 0; 183 | -ms-transform-origin: 0 0; 184 | transform-origin: 0 0; 185 | } 186 | .leaflet-zoom-anim .leaflet-zoom-animated { 187 | will-change: transform; 188 | } 189 | .leaflet-zoom-anim .leaflet-zoom-animated { 190 | -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); 191 | -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); 192 | transition: transform 0.25s cubic-bezier(0,0,0.25,1); 193 | } 194 | .leaflet-zoom-anim .leaflet-tile, 195 | .leaflet-pan-anim .leaflet-tile { 196 | -webkit-transition: none; 197 | -moz-transition: none; 198 | transition: none; 199 | } 200 | 201 | .leaflet-zoom-anim .leaflet-zoom-hide { 202 | visibility: hidden; 203 | } 204 | 205 | 206 | /* cursors */ 207 | 208 | .leaflet-interactive { 209 | cursor: pointer; 210 | } 211 | .leaflet-grab { 212 | cursor: -webkit-grab; 213 | cursor: -moz-grab; 214 | cursor: grab; 215 | } 216 | .leaflet-crosshair, 217 | .leaflet-crosshair .leaflet-interactive { 218 | cursor: crosshair; 219 | } 220 | .leaflet-popup-pane, 221 | .leaflet-control { 222 | cursor: auto; 223 | } 224 | .leaflet-dragging .leaflet-grab, 225 | .leaflet-dragging .leaflet-grab .leaflet-interactive, 226 | .leaflet-dragging .leaflet-marker-draggable { 227 | cursor: move; 228 | cursor: -webkit-grabbing; 229 | cursor: -moz-grabbing; 230 | cursor: grabbing; 231 | } 232 | 233 | /* marker & overlays interactivity */ 234 | .leaflet-marker-icon, 235 | .leaflet-marker-shadow, 236 | .leaflet-image-layer, 237 | .leaflet-pane > svg path, 238 | .leaflet-tile-container { 239 | pointer-events: none; 240 | } 241 | 242 | .leaflet-marker-icon.leaflet-interactive, 243 | .leaflet-image-layer.leaflet-interactive, 244 | .leaflet-pane > svg path.leaflet-interactive, 245 | svg.leaflet-image-layer.leaflet-interactive path { 246 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ 247 | pointer-events: auto; 248 | } 249 | 250 | /* visual tweaks */ 251 | 252 | .leaflet-container { 253 | background: #ddd; 254 | outline: 0; 255 | } 256 | .leaflet-container a { 257 | color: #0078A8; 258 | } 259 | .leaflet-container a.leaflet-active { 260 | outline: 2px solid orange; 261 | } 262 | .leaflet-zoom-box { 263 | border: 2px dotted #38f; 264 | background: rgba(255,255,255,0.5); 265 | } 266 | 267 | 268 | /* general typography */ 269 | .leaflet-container { 270 | font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; 271 | } 272 | 273 | 274 | /* general toolbar styles */ 275 | 276 | .leaflet-bar { 277 | box-shadow: 0 1px 5px rgba(0,0,0,0.65); 278 | border-radius: 4px; 279 | } 280 | .leaflet-bar a, 281 | .leaflet-bar a:hover { 282 | background-color: #fff; 283 | border-bottom: 1px solid #ccc; 284 | width: 26px; 285 | height: 26px; 286 | line-height: 26px; 287 | display: block; 288 | text-align: center; 289 | text-decoration: none; 290 | color: black; 291 | } 292 | .leaflet-bar a, 293 | .leaflet-control-layers-toggle { 294 | background-position: 50% 50%; 295 | background-repeat: no-repeat; 296 | display: block; 297 | } 298 | .leaflet-bar a:hover { 299 | background-color: #f4f4f4; 300 | } 301 | .leaflet-bar a:first-child { 302 | border-top-left-radius: 4px; 303 | border-top-right-radius: 4px; 304 | } 305 | .leaflet-bar a:last-child { 306 | border-bottom-left-radius: 4px; 307 | border-bottom-right-radius: 4px; 308 | border-bottom: none; 309 | } 310 | .leaflet-bar a.leaflet-disabled { 311 | cursor: default; 312 | background-color: #f4f4f4; 313 | color: #bbb; 314 | } 315 | 316 | .leaflet-touch .leaflet-bar a { 317 | width: 30px; 318 | height: 30px; 319 | line-height: 30px; 320 | } 321 | .leaflet-touch .leaflet-bar a:first-child { 322 | border-top-left-radius: 2px; 323 | border-top-right-radius: 2px; 324 | } 325 | .leaflet-touch .leaflet-bar a:last-child { 326 | border-bottom-left-radius: 2px; 327 | border-bottom-right-radius: 2px; 328 | } 329 | 330 | /* zoom control */ 331 | 332 | .leaflet-control-zoom-in, 333 | .leaflet-control-zoom-out { 334 | font: bold 18px 'Lucida Console', Monaco, monospace; 335 | text-indent: 1px; 336 | } 337 | 338 | .leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { 339 | font-size: 22px; 340 | } 341 | 342 | 343 | /* layers control */ 344 | 345 | .leaflet-control-layers { 346 | box-shadow: 0 1px 5px rgba(0,0,0,0.4); 347 | background: #fff; 348 | border-radius: 5px; 349 | } 350 | .leaflet-control-layers-toggle { 351 | background-image: url(images/layers.png); 352 | width: 36px; 353 | height: 36px; 354 | } 355 | .leaflet-retina .leaflet-control-layers-toggle { 356 | background-image: url(images/layers-2x.png); 357 | background-size: 26px 26px; 358 | } 359 | .leaflet-touch .leaflet-control-layers-toggle { 360 | width: 44px; 361 | height: 44px; 362 | } 363 | .leaflet-control-layers .leaflet-control-layers-list, 364 | .leaflet-control-layers-expanded .leaflet-control-layers-toggle { 365 | display: none; 366 | } 367 | .leaflet-control-layers-expanded .leaflet-control-layers-list { 368 | display: block; 369 | position: relative; 370 | } 371 | .leaflet-control-layers-expanded { 372 | padding: 6px 10px 6px 6px; 373 | color: #333; 374 | background: #fff; 375 | } 376 | .leaflet-control-layers-scrollbar { 377 | overflow-y: scroll; 378 | overflow-x: hidden; 379 | padding-right: 5px; 380 | } 381 | .leaflet-control-layers-selector { 382 | margin-top: 2px; 383 | position: relative; 384 | top: 1px; 385 | } 386 | .leaflet-control-layers label { 387 | display: block; 388 | } 389 | .leaflet-control-layers-separator { 390 | height: 0; 391 | border-top: 1px solid #ddd; 392 | margin: 5px -10px 5px -6px; 393 | } 394 | 395 | /* Default icon URLs */ 396 | .leaflet-default-icon-path { 397 | background-image: url(images/marker-icon.png); 398 | } 399 | 400 | 401 | /* attribution and scale controls */ 402 | 403 | .leaflet-container .leaflet-control-attribution { 404 | background: #fff; 405 | background: rgba(255, 255, 255, 0.7); 406 | margin: 0; 407 | } 408 | .leaflet-control-attribution, 409 | .leaflet-control-scale-line { 410 | padding: 0 5px; 411 | color: #333; 412 | } 413 | .leaflet-control-attribution a { 414 | text-decoration: none; 415 | } 416 | .leaflet-control-attribution a:hover { 417 | text-decoration: underline; 418 | } 419 | .leaflet-container .leaflet-control-attribution, 420 | .leaflet-container .leaflet-control-scale { 421 | font-size: 11px; 422 | } 423 | .leaflet-left .leaflet-control-scale { 424 | margin-left: 5px; 425 | } 426 | .leaflet-bottom .leaflet-control-scale { 427 | margin-bottom: 5px; 428 | } 429 | .leaflet-control-scale-line { 430 | border: 2px solid #777; 431 | border-top: none; 432 | line-height: 1.1; 433 | padding: 2px 5px 1px; 434 | font-size: 11px; 435 | white-space: nowrap; 436 | overflow: hidden; 437 | -moz-box-sizing: border-box; 438 | box-sizing: border-box; 439 | 440 | background: #fff; 441 | background: rgba(255, 255, 255, 0.5); 442 | } 443 | .leaflet-control-scale-line:not(:first-child) { 444 | border-top: 2px solid #777; 445 | border-bottom: none; 446 | margin-top: -2px; 447 | } 448 | .leaflet-control-scale-line:not(:first-child):not(:last-child) { 449 | border-bottom: 2px solid #777; 450 | } 451 | 452 | .leaflet-touch .leaflet-control-attribution, 453 | .leaflet-touch .leaflet-control-layers, 454 | .leaflet-touch .leaflet-bar { 455 | box-shadow: none; 456 | } 457 | .leaflet-touch .leaflet-control-layers, 458 | .leaflet-touch .leaflet-bar { 459 | border: 2px solid rgba(0,0,0,0.2); 460 | background-clip: padding-box; 461 | } 462 | 463 | 464 | /* popup */ 465 | 466 | .leaflet-popup { 467 | position: absolute; 468 | text-align: center; 469 | margin-bottom: 20px; 470 | } 471 | .leaflet-popup-content-wrapper { 472 | padding: 1px; 473 | text-align: left; 474 | border-radius: 12px; 475 | } 476 | .leaflet-popup-content { 477 | margin: 13px 19px; 478 | line-height: 1.4; 479 | } 480 | .leaflet-popup-content p { 481 | margin: 18px 0; 482 | } 483 | .leaflet-popup-tip-container { 484 | width: 40px; 485 | height: 20px; 486 | position: absolute; 487 | left: 50%; 488 | margin-left: -20px; 489 | overflow: hidden; 490 | pointer-events: none; 491 | } 492 | .leaflet-popup-tip { 493 | width: 17px; 494 | height: 17px; 495 | padding: 1px; 496 | 497 | margin: -10px auto 0; 498 | 499 | -webkit-transform: rotate(45deg); 500 | -moz-transform: rotate(45deg); 501 | -ms-transform: rotate(45deg); 502 | transform: rotate(45deg); 503 | } 504 | .leaflet-popup-content-wrapper, 505 | .leaflet-popup-tip { 506 | background: white; 507 | color: #333; 508 | box-shadow: 0 3px 14px rgba(0,0,0,0.4); 509 | } 510 | .leaflet-container a.leaflet-popup-close-button { 511 | position: absolute; 512 | top: 0; 513 | right: 0; 514 | padding: 4px 4px 0 0; 515 | border: none; 516 | text-align: center; 517 | width: 18px; 518 | height: 14px; 519 | font: 16px/14px Tahoma, Verdana, sans-serif; 520 | color: #c3c3c3; 521 | text-decoration: none; 522 | font-weight: bold; 523 | background: transparent; 524 | } 525 | .leaflet-container a.leaflet-popup-close-button:hover { 526 | color: #999; 527 | } 528 | .leaflet-popup-scrolled { 529 | overflow: auto; 530 | border-bottom: 1px solid #ddd; 531 | border-top: 1px solid #ddd; 532 | } 533 | 534 | .leaflet-oldie .leaflet-popup-content-wrapper { 535 | -ms-zoom: 1; 536 | } 537 | .leaflet-oldie .leaflet-popup-tip { 538 | width: 24px; 539 | margin: 0 auto; 540 | 541 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; 542 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); 543 | } 544 | .leaflet-oldie .leaflet-popup-tip-container { 545 | margin-top: -1px; 546 | } 547 | 548 | .leaflet-oldie .leaflet-control-zoom, 549 | .leaflet-oldie .leaflet-control-layers, 550 | .leaflet-oldie .leaflet-popup-content-wrapper, 551 | .leaflet-oldie .leaflet-popup-tip { 552 | border: 1px solid #999; 553 | } 554 | 555 | 556 | /* div icon */ 557 | 558 | .leaflet-div-icon { 559 | background: #fff; 560 | border: 1px solid #666; 561 | } 562 | 563 | 564 | /* Tooltip */ 565 | /* Base styles for the element that has a tooltip */ 566 | .leaflet-tooltip { 567 | position: absolute; 568 | padding: 6px; 569 | background-color: #fff; 570 | border: 1px solid #fff; 571 | border-radius: 3px; 572 | color: #222; 573 | white-space: nowrap; 574 | -webkit-user-select: none; 575 | -moz-user-select: none; 576 | -ms-user-select: none; 577 | user-select: none; 578 | pointer-events: none; 579 | box-shadow: 0 1px 3px rgba(0,0,0,0.4); 580 | } 581 | .leaflet-tooltip.leaflet-clickable { 582 | cursor: pointer; 583 | pointer-events: auto; 584 | } 585 | .leaflet-tooltip-top:before, 586 | .leaflet-tooltip-bottom:before, 587 | .leaflet-tooltip-left:before, 588 | .leaflet-tooltip-right:before { 589 | position: absolute; 590 | pointer-events: none; 591 | border: 6px solid transparent; 592 | background: transparent; 593 | content: ""; 594 | } 595 | 596 | /* Directions */ 597 | 598 | .leaflet-tooltip-bottom { 599 | margin-top: 6px; 600 | } 601 | .leaflet-tooltip-top { 602 | margin-top: -6px; 603 | } 604 | .leaflet-tooltip-bottom:before, 605 | .leaflet-tooltip-top:before { 606 | left: 50%; 607 | margin-left: -6px; 608 | } 609 | .leaflet-tooltip-top:before { 610 | bottom: 0; 611 | margin-bottom: -12px; 612 | border-top-color: #fff; 613 | } 614 | .leaflet-tooltip-bottom:before { 615 | top: 0; 616 | margin-top: -12px; 617 | margin-left: -6px; 618 | border-bottom-color: #fff; 619 | } 620 | .leaflet-tooltip-left { 621 | margin-left: -6px; 622 | } 623 | .leaflet-tooltip-right { 624 | margin-left: 6px; 625 | } 626 | .leaflet-tooltip-left:before, 627 | .leaflet-tooltip-right:before { 628 | top: 50%; 629 | margin-top: -6px; 630 | } 631 | .leaflet-tooltip-left:before { 632 | right: 0; 633 | margin-right: -12px; 634 | border-left-color: #fff; 635 | } 636 | .leaflet-tooltip-right:before { 637 | left: 0; 638 | margin-left: -12px; 639 | border-right-color: #fff; 640 | } 641 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Try Scroll - a language for scientists of all ages 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 780 | 781 | 782 | 783 | 784 | 785 | 787 | 788 | -------------------------------------------------------------------------------- /.katex.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:KaTeX_AMS;font-style:normal;font-weight:400;src:url(fonts/KaTeX_AMS-Regular.woff2) format("woff2"),url(fonts/KaTeX_AMS-Regular.woff) format("woff"),url(fonts/KaTeX_AMS-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Caligraphic;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Caligraphic-Bold.woff2) format("woff2"),url(fonts/KaTeX_Caligraphic-Bold.woff) format("woff"),url(fonts/KaTeX_Caligraphic-Bold.ttf) format("truetype")}@font-face{font-family:KaTeX_Caligraphic;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Caligraphic-Regular.woff2) format("woff2"),url(fonts/KaTeX_Caligraphic-Regular.woff) format("woff"),url(fonts/KaTeX_Caligraphic-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Fraktur;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Fraktur-Bold.woff2) format("woff2"),url(fonts/KaTeX_Fraktur-Bold.woff) format("woff"),url(fonts/KaTeX_Fraktur-Bold.ttf) format("truetype")}@font-face{font-family:KaTeX_Fraktur;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Fraktur-Regular.woff2) format("woff2"),url(fonts/KaTeX_Fraktur-Regular.woff) format("woff"),url(fonts/KaTeX_Fraktur-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Main;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Main-Bold.woff2) format("woff2"),url(fonts/KaTeX_Main-Bold.woff) format("woff"),url(fonts/KaTeX_Main-Bold.ttf) format("truetype")}@font-face{font-family:KaTeX_Main;font-style:italic;font-weight:700;src:url(fonts/KaTeX_Main-BoldItalic.woff2) format("woff2"),url(fonts/KaTeX_Main-BoldItalic.woff) format("woff"),url(fonts/KaTeX_Main-BoldItalic.ttf) format("truetype")}@font-face{font-family:KaTeX_Main;font-style:italic;font-weight:400;src:url(fonts/KaTeX_Main-Italic.woff2) format("woff2"),url(fonts/KaTeX_Main-Italic.woff) format("woff"),url(fonts/KaTeX_Main-Italic.ttf) format("truetype")}@font-face{font-family:KaTeX_Main;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Main-Regular.woff2) format("woff2"),url(fonts/KaTeX_Main-Regular.woff) format("woff"),url(fonts/KaTeX_Main-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Math;font-style:italic;font-weight:700;src:url(fonts/KaTeX_Math-BoldItalic.woff2) format("woff2"),url(fonts/KaTeX_Math-BoldItalic.woff) format("woff"),url(fonts/KaTeX_Math-BoldItalic.ttf) format("truetype")}@font-face{font-family:KaTeX_Math;font-style:italic;font-weight:400;src:url(fonts/KaTeX_Math-Italic.woff2) format("woff2"),url(fonts/KaTeX_Math-Italic.woff) format("woff"),url(fonts/KaTeX_Math-Italic.ttf) format("truetype")}@font-face{font-family:"KaTeX_SansSerif";font-style:normal;font-weight:700;src:url(fonts/KaTeX_SansSerif-Bold.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Bold.woff) format("woff"),url(fonts/KaTeX_SansSerif-Bold.ttf) format("truetype")}@font-face{font-family:"KaTeX_SansSerif";font-style:italic;font-weight:400;src:url(fonts/KaTeX_SansSerif-Italic.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Italic.woff) format("woff"),url(fonts/KaTeX_SansSerif-Italic.ttf) format("truetype")}@font-face{font-family:"KaTeX_SansSerif";font-style:normal;font-weight:400;src:url(fonts/KaTeX_SansSerif-Regular.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Regular.woff) format("woff"),url(fonts/KaTeX_SansSerif-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Script;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Script-Regular.woff2) format("woff2"),url(fonts/KaTeX_Script-Regular.woff) format("woff"),url(fonts/KaTeX_Script-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Size1;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size1-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size1-Regular.woff) format("woff"),url(fonts/KaTeX_Size1-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Size2;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size2-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size2-Regular.woff) format("woff"),url(fonts/KaTeX_Size2-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Size3;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size3-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size3-Regular.woff) format("woff"),url(fonts/KaTeX_Size3-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Size4;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size4-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size4-Regular.woff) format("woff"),url(fonts/KaTeX_Size4-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Typewriter;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Typewriter-Regular.woff2) format("woff2"),url(fonts/KaTeX_Typewriter-Regular.woff) format("woff"),url(fonts/KaTeX_Typewriter-Regular.ttf) format("truetype")}.katex{text-rendering:auto;font:normal 1.21em KaTeX_Main,Times New Roman,serif;line-height:1.2;text-indent:0}.katex *{-ms-high-contrast-adjust:none!important;border-color:currentColor}.katex .katex-version:after{content:"0.16.6"}.katex .katex-mathml{clip:rect(1px,1px,1px,1px);border:0;height:1px;overflow:hidden;padding:0;position:absolute;width:1px}.katex .katex-html>.newline{display:block}.katex .base{position:relative;white-space:nowrap;width:-webkit-min-content;width:-moz-min-content;width:min-content}.katex .base,.katex .strut{display:inline-block}.katex .textbf{font-weight:700}.katex .textit{font-style:italic}.katex .textrm{font-family:KaTeX_Main}.katex .textsf{font-family:KaTeX_SansSerif}.katex .texttt{font-family:KaTeX_Typewriter}.katex .mathnormal{font-family:KaTeX_Math;font-style:italic}.katex .mathit{font-family:KaTeX_Main;font-style:italic}.katex .mathrm{font-style:normal}.katex .mathbf{font-family:KaTeX_Main;font-weight:700}.katex .boldsymbol{font-family:KaTeX_Math;font-style:italic;font-weight:700}.katex .amsrm,.katex .mathbb,.katex .textbb{font-family:KaTeX_AMS}.katex .mathcal{font-family:KaTeX_Caligraphic}.katex .mathfrak,.katex .textfrak{font-family:KaTeX_Fraktur}.katex .mathtt{font-family:KaTeX_Typewriter}.katex .mathscr,.katex .textscr{font-family:KaTeX_Script}.katex .mathsf,.katex .textsf{font-family:KaTeX_SansSerif}.katex .mathboldsf,.katex .textboldsf{font-family:KaTeX_SansSerif;font-weight:700}.katex .mathitsf,.katex .textitsf{font-family:KaTeX_SansSerif;font-style:italic}.katex .mainrm{font-family:KaTeX_Main;font-style:normal}.katex .vlist-t{border-collapse:collapse;display:inline-table;table-layout:fixed}.katex .vlist-r{display:table-row}.katex .vlist{display:table-cell;position:relative;vertical-align:bottom}.katex .vlist>span{display:block;height:0;position:relative}.katex .vlist>span>span{display:inline-block}.katex .vlist>span>.pstrut{overflow:hidden;width:0}.katex .vlist-t2{margin-right:-2px}.katex .vlist-s{display:table-cell;font-size:1px;min-width:2px;vertical-align:bottom;width:2px}.katex .vbox{align-items:baseline;display:inline-flex;flex-direction:column}.katex .hbox{width:100%}.katex .hbox,.katex .thinbox{display:inline-flex;flex-direction:row}.katex .thinbox{max-width:0;width:0}.katex .msupsub{text-align:left}.katex .mfrac>span>span{text-align:center}.katex .mfrac .frac-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .hdashline,.katex .hline,.katex .mfrac .frac-line,.katex .overline .overline-line,.katex .rule,.katex .underline .underline-line{min-height:1px}.katex .mspace{display:inline-block}.katex .clap,.katex .llap,.katex .rlap{position:relative;width:0}.katex .clap>.inner,.katex .llap>.inner,.katex .rlap>.inner{position:absolute}.katex .clap>.fix,.katex .llap>.fix,.katex .rlap>.fix{display:inline-block}.katex .llap>.inner{right:0}.katex .clap>.inner,.katex .rlap>.inner{left:0}.katex .clap>.inner>span{margin-left:-50%;margin-right:50%}.katex .rule{border:0 solid;display:inline-block;position:relative}.katex .hline,.katex .overline .overline-line,.katex .underline .underline-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .hdashline{border-bottom-style:dashed;display:inline-block;width:100%}.katex .sqrt>.root{margin-left:.27777778em;margin-right:-.55555556em}.katex .fontsize-ensurer.reset-size1.size1,.katex .sizing.reset-size1.size1{font-size:1em}.katex .fontsize-ensurer.reset-size1.size2,.katex .sizing.reset-size1.size2{font-size:1.2em}.katex .fontsize-ensurer.reset-size1.size3,.katex .sizing.reset-size1.size3{font-size:1.4em}.katex .fontsize-ensurer.reset-size1.size4,.katex .sizing.reset-size1.size4{font-size:1.6em}.katex .fontsize-ensurer.reset-size1.size5,.katex .sizing.reset-size1.size5{font-size:1.8em}.katex .fontsize-ensurer.reset-size1.size6,.katex .sizing.reset-size1.size6{font-size:2em}.katex .fontsize-ensurer.reset-size1.size7,.katex .sizing.reset-size1.size7{font-size:2.4em}.katex .fontsize-ensurer.reset-size1.size8,.katex .sizing.reset-size1.size8{font-size:2.88em}.katex .fontsize-ensurer.reset-size1.size9,.katex .sizing.reset-size1.size9{font-size:3.456em}.katex .fontsize-ensurer.reset-size1.size10,.katex .sizing.reset-size1.size10{font-size:4.148em}.katex .fontsize-ensurer.reset-size1.size11,.katex .sizing.reset-size1.size11{font-size:4.976em}.katex .fontsize-ensurer.reset-size2.size1,.katex .sizing.reset-size2.size1{font-size:.83333333em}.katex .fontsize-ensurer.reset-size2.size2,.katex .sizing.reset-size2.size2{font-size:1em}.katex .fontsize-ensurer.reset-size2.size3,.katex .sizing.reset-size2.size3{font-size:1.16666667em}.katex .fontsize-ensurer.reset-size2.size4,.katex .sizing.reset-size2.size4{font-size:1.33333333em}.katex .fontsize-ensurer.reset-size2.size5,.katex .sizing.reset-size2.size5{font-size:1.5em}.katex .fontsize-ensurer.reset-size2.size6,.katex .sizing.reset-size2.size6{font-size:1.66666667em}.katex .fontsize-ensurer.reset-size2.size7,.katex .sizing.reset-size2.size7{font-size:2em}.katex .fontsize-ensurer.reset-size2.size8,.katex .sizing.reset-size2.size8{font-size:2.4em}.katex .fontsize-ensurer.reset-size2.size9,.katex .sizing.reset-size2.size9{font-size:2.88em}.katex .fontsize-ensurer.reset-size2.size10,.katex .sizing.reset-size2.size10{font-size:3.45666667em}.katex .fontsize-ensurer.reset-size2.size11,.katex .sizing.reset-size2.size11{font-size:4.14666667em}.katex .fontsize-ensurer.reset-size3.size1,.katex .sizing.reset-size3.size1{font-size:.71428571em}.katex .fontsize-ensurer.reset-size3.size2,.katex .sizing.reset-size3.size2{font-size:.85714286em}.katex .fontsize-ensurer.reset-size3.size3,.katex .sizing.reset-size3.size3{font-size:1em}.katex .fontsize-ensurer.reset-size3.size4,.katex .sizing.reset-size3.size4{font-size:1.14285714em}.katex .fontsize-ensurer.reset-size3.size5,.katex .sizing.reset-size3.size5{font-size:1.28571429em}.katex .fontsize-ensurer.reset-size3.size6,.katex .sizing.reset-size3.size6{font-size:1.42857143em}.katex .fontsize-ensurer.reset-size3.size7,.katex .sizing.reset-size3.size7{font-size:1.71428571em}.katex .fontsize-ensurer.reset-size3.size8,.katex .sizing.reset-size3.size8{font-size:2.05714286em}.katex .fontsize-ensurer.reset-size3.size9,.katex .sizing.reset-size3.size9{font-size:2.46857143em}.katex .fontsize-ensurer.reset-size3.size10,.katex .sizing.reset-size3.size10{font-size:2.96285714em}.katex .fontsize-ensurer.reset-size3.size11,.katex .sizing.reset-size3.size11{font-size:3.55428571em}.katex .fontsize-ensurer.reset-size4.size1,.katex .sizing.reset-size4.size1{font-size:.625em}.katex .fontsize-ensurer.reset-size4.size2,.katex .sizing.reset-size4.size2{font-size:.75em}.katex .fontsize-ensurer.reset-size4.size3,.katex .sizing.reset-size4.size3{font-size:.875em}.katex .fontsize-ensurer.reset-size4.size4,.katex .sizing.reset-size4.size4{font-size:1em}.katex .fontsize-ensurer.reset-size4.size5,.katex .sizing.reset-size4.size5{font-size:1.125em}.katex .fontsize-ensurer.reset-size4.size6,.katex .sizing.reset-size4.size6{font-size:1.25em}.katex .fontsize-ensurer.reset-size4.size7,.katex .sizing.reset-size4.size7{font-size:1.5em}.katex .fontsize-ensurer.reset-size4.size8,.katex .sizing.reset-size4.size8{font-size:1.8em}.katex .fontsize-ensurer.reset-size4.size9,.katex .sizing.reset-size4.size9{font-size:2.16em}.katex .fontsize-ensurer.reset-size4.size10,.katex .sizing.reset-size4.size10{font-size:2.5925em}.katex .fontsize-ensurer.reset-size4.size11,.katex .sizing.reset-size4.size11{font-size:3.11em}.katex .fontsize-ensurer.reset-size5.size1,.katex .sizing.reset-size5.size1{font-size:.55555556em}.katex .fontsize-ensurer.reset-size5.size2,.katex .sizing.reset-size5.size2{font-size:.66666667em}.katex .fontsize-ensurer.reset-size5.size3,.katex .sizing.reset-size5.size3{font-size:.77777778em}.katex .fontsize-ensurer.reset-size5.size4,.katex .sizing.reset-size5.size4{font-size:.88888889em}.katex .fontsize-ensurer.reset-size5.size5,.katex .sizing.reset-size5.size5{font-size:1em}.katex .fontsize-ensurer.reset-size5.size6,.katex .sizing.reset-size5.size6{font-size:1.11111111em}.katex .fontsize-ensurer.reset-size5.size7,.katex .sizing.reset-size5.size7{font-size:1.33333333em}.katex .fontsize-ensurer.reset-size5.size8,.katex .sizing.reset-size5.size8{font-size:1.6em}.katex .fontsize-ensurer.reset-size5.size9,.katex .sizing.reset-size5.size9{font-size:1.92em}.katex .fontsize-ensurer.reset-size5.size10,.katex .sizing.reset-size5.size10{font-size:2.30444444em}.katex .fontsize-ensurer.reset-size5.size11,.katex .sizing.reset-size5.size11{font-size:2.76444444em}.katex .fontsize-ensurer.reset-size6.size1,.katex .sizing.reset-size6.size1{font-size:.5em}.katex .fontsize-ensurer.reset-size6.size2,.katex .sizing.reset-size6.size2{font-size:.6em}.katex .fontsize-ensurer.reset-size6.size3,.katex .sizing.reset-size6.size3{font-size:.7em}.katex .fontsize-ensurer.reset-size6.size4,.katex .sizing.reset-size6.size4{font-size:.8em}.katex .fontsize-ensurer.reset-size6.size5,.katex .sizing.reset-size6.size5{font-size:.9em}.katex .fontsize-ensurer.reset-size6.size6,.katex .sizing.reset-size6.size6{font-size:1em}.katex .fontsize-ensurer.reset-size6.size7,.katex .sizing.reset-size6.size7{font-size:1.2em}.katex .fontsize-ensurer.reset-size6.size8,.katex .sizing.reset-size6.size8{font-size:1.44em}.katex .fontsize-ensurer.reset-size6.size9,.katex .sizing.reset-size6.size9{font-size:1.728em}.katex .fontsize-ensurer.reset-size6.size10,.katex .sizing.reset-size6.size10{font-size:2.074em}.katex .fontsize-ensurer.reset-size6.size11,.katex .sizing.reset-size6.size11{font-size:2.488em}.katex .fontsize-ensurer.reset-size7.size1,.katex .sizing.reset-size7.size1{font-size:.41666667em}.katex .fontsize-ensurer.reset-size7.size2,.katex .sizing.reset-size7.size2{font-size:.5em}.katex .fontsize-ensurer.reset-size7.size3,.katex .sizing.reset-size7.size3{font-size:.58333333em}.katex .fontsize-ensurer.reset-size7.size4,.katex .sizing.reset-size7.size4{font-size:.66666667em}.katex .fontsize-ensurer.reset-size7.size5,.katex .sizing.reset-size7.size5{font-size:.75em}.katex .fontsize-ensurer.reset-size7.size6,.katex .sizing.reset-size7.size6{font-size:.83333333em}.katex .fontsize-ensurer.reset-size7.size7,.katex .sizing.reset-size7.size7{font-size:1em}.katex .fontsize-ensurer.reset-size7.size8,.katex .sizing.reset-size7.size8{font-size:1.2em}.katex .fontsize-ensurer.reset-size7.size9,.katex .sizing.reset-size7.size9{font-size:1.44em}.katex .fontsize-ensurer.reset-size7.size10,.katex .sizing.reset-size7.size10{font-size:1.72833333em}.katex .fontsize-ensurer.reset-size7.size11,.katex .sizing.reset-size7.size11{font-size:2.07333333em}.katex .fontsize-ensurer.reset-size8.size1,.katex .sizing.reset-size8.size1{font-size:.34722222em}.katex .fontsize-ensurer.reset-size8.size2,.katex .sizing.reset-size8.size2{font-size:.41666667em}.katex .fontsize-ensurer.reset-size8.size3,.katex .sizing.reset-size8.size3{font-size:.48611111em}.katex .fontsize-ensurer.reset-size8.size4,.katex .sizing.reset-size8.size4{font-size:.55555556em}.katex .fontsize-ensurer.reset-size8.size5,.katex .sizing.reset-size8.size5{font-size:.625em}.katex .fontsize-ensurer.reset-size8.size6,.katex .sizing.reset-size8.size6{font-size:.69444444em}.katex .fontsize-ensurer.reset-size8.size7,.katex .sizing.reset-size8.size7{font-size:.83333333em}.katex .fontsize-ensurer.reset-size8.size8,.katex .sizing.reset-size8.size8{font-size:1em}.katex .fontsize-ensurer.reset-size8.size9,.katex .sizing.reset-size8.size9{font-size:1.2em}.katex .fontsize-ensurer.reset-size8.size10,.katex .sizing.reset-size8.size10{font-size:1.44027778em}.katex .fontsize-ensurer.reset-size8.size11,.katex .sizing.reset-size8.size11{font-size:1.72777778em}.katex .fontsize-ensurer.reset-size9.size1,.katex .sizing.reset-size9.size1{font-size:.28935185em}.katex .fontsize-ensurer.reset-size9.size2,.katex .sizing.reset-size9.size2{font-size:.34722222em}.katex .fontsize-ensurer.reset-size9.size3,.katex .sizing.reset-size9.size3{font-size:.40509259em}.katex .fontsize-ensurer.reset-size9.size4,.katex .sizing.reset-size9.size4{font-size:.46296296em}.katex .fontsize-ensurer.reset-size9.size5,.katex .sizing.reset-size9.size5{font-size:.52083333em}.katex .fontsize-ensurer.reset-size9.size6,.katex .sizing.reset-size9.size6{font-size:.5787037em}.katex .fontsize-ensurer.reset-size9.size7,.katex .sizing.reset-size9.size7{font-size:.69444444em}.katex .fontsize-ensurer.reset-size9.size8,.katex .sizing.reset-size9.size8{font-size:.83333333em}.katex .fontsize-ensurer.reset-size9.size9,.katex .sizing.reset-size9.size9{font-size:1em}.katex .fontsize-ensurer.reset-size9.size10,.katex .sizing.reset-size9.size10{font-size:1.20023148em}.katex .fontsize-ensurer.reset-size9.size11,.katex .sizing.reset-size9.size11{font-size:1.43981481em}.katex .fontsize-ensurer.reset-size10.size1,.katex .sizing.reset-size10.size1{font-size:.24108004em}.katex .fontsize-ensurer.reset-size10.size2,.katex .sizing.reset-size10.size2{font-size:.28929605em}.katex .fontsize-ensurer.reset-size10.size3,.katex .sizing.reset-size10.size3{font-size:.33751205em}.katex .fontsize-ensurer.reset-size10.size4,.katex .sizing.reset-size10.size4{font-size:.38572806em}.katex .fontsize-ensurer.reset-size10.size5,.katex .sizing.reset-size10.size5{font-size:.43394407em}.katex .fontsize-ensurer.reset-size10.size6,.katex .sizing.reset-size10.size6{font-size:.48216008em}.katex .fontsize-ensurer.reset-size10.size7,.katex .sizing.reset-size10.size7{font-size:.57859209em}.katex .fontsize-ensurer.reset-size10.size8,.katex .sizing.reset-size10.size8{font-size:.69431051em}.katex .fontsize-ensurer.reset-size10.size9,.katex .sizing.reset-size10.size9{font-size:.83317261em}.katex .fontsize-ensurer.reset-size10.size10,.katex .sizing.reset-size10.size10{font-size:1em}.katex .fontsize-ensurer.reset-size10.size11,.katex .sizing.reset-size10.size11{font-size:1.19961427em}.katex .fontsize-ensurer.reset-size11.size1,.katex .sizing.reset-size11.size1{font-size:.20096463em}.katex .fontsize-ensurer.reset-size11.size2,.katex .sizing.reset-size11.size2{font-size:.24115756em}.katex .fontsize-ensurer.reset-size11.size3,.katex .sizing.reset-size11.size3{font-size:.28135048em}.katex .fontsize-ensurer.reset-size11.size4,.katex .sizing.reset-size11.size4{font-size:.32154341em}.katex .fontsize-ensurer.reset-size11.size5,.katex .sizing.reset-size11.size5{font-size:.36173633em}.katex .fontsize-ensurer.reset-size11.size6,.katex .sizing.reset-size11.size6{font-size:.40192926em}.katex .fontsize-ensurer.reset-size11.size7,.katex .sizing.reset-size11.size7{font-size:.48231511em}.katex .fontsize-ensurer.reset-size11.size8,.katex .sizing.reset-size11.size8{font-size:.57877814em}.katex .fontsize-ensurer.reset-size11.size9,.katex .sizing.reset-size11.size9{font-size:.69453376em}.katex .fontsize-ensurer.reset-size11.size10,.katex .sizing.reset-size11.size10{font-size:.83360129em}.katex .fontsize-ensurer.reset-size11.size11,.katex .sizing.reset-size11.size11{font-size:1em}.katex .delimsizing.size1{font-family:KaTeX_Size1}.katex .delimsizing.size2{font-family:KaTeX_Size2}.katex .delimsizing.size3{font-family:KaTeX_Size3}.katex .delimsizing.size4{font-family:KaTeX_Size4}.katex .delimsizing.mult .delim-size1>span{font-family:KaTeX_Size1}.katex .delimsizing.mult .delim-size4>span{font-family:KaTeX_Size4}.katex .nulldelimiter{display:inline-block;width:.12em}.katex .delimcenter,.katex .op-symbol{position:relative}.katex .op-symbol.small-op{font-family:KaTeX_Size1}.katex .op-symbol.large-op{font-family:KaTeX_Size2}.katex .accent>.vlist-t,.katex .op-limits>.vlist-t{text-align:center}.katex .accent .accent-body{position:relative}.katex .accent .accent-body:not(.accent-full){width:0}.katex .overlay{display:block}.katex .mtable .vertical-separator{display:inline-block;min-width:1px}.katex .mtable .arraycolsep{display:inline-block}.katex .mtable .col-align-c>.vlist-t{text-align:center}.katex .mtable .col-align-l>.vlist-t{text-align:left}.katex .mtable .col-align-r>.vlist-t{text-align:right}.katex .svg-align{text-align:left}.katex svg{fill:currentColor;stroke:currentColor;fill-rule:nonzero;fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:block;height:inherit;position:absolute;width:100%}.katex svg path{stroke:none}.katex img{border-style:none;max-height:none;max-width:none;min-height:0;min-width:0}.katex .stretchy{display:block;overflow:hidden;position:relative;width:100%}.katex .stretchy:after,.katex .stretchy:before{content:""}.katex .hide-tail{overflow:hidden;position:relative;width:100%}.katex .halfarrow-left{left:0;overflow:hidden;position:absolute;width:50.2%}.katex .halfarrow-right{overflow:hidden;position:absolute;right:0;width:50.2%}.katex .brace-left{left:0;overflow:hidden;position:absolute;width:25.1%}.katex .brace-center{left:25%;overflow:hidden;position:absolute;width:50%}.katex .brace-right{overflow:hidden;position:absolute;right:0;width:25.1%}.katex .x-arrow-pad{padding:0 .5em}.katex .cd-arrow-pad{padding:0 .55556em 0 .27778em}.katex .mover,.katex .munder,.katex .x-arrow{text-align:center}.katex .boxpad{padding:0 .3em}.katex .fbox,.katex .fcolorbox{border:.04em solid;box-sizing:border-box}.katex .cancel-pad{padding:0 .2em}.katex .cancel-lap{margin-left:-.2em;margin-right:-.2em}.katex .sout{border-bottom-style:solid;border-bottom-width:.08em}.katex .angl{border-right:.049em solid;border-top:.049em solid;box-sizing:border-box;margin-right:.03889em}.katex .anglpad{padding:0 .03889em}.katex .eqn-num:before{content:"(" counter(katexEqnNo) ")";counter-increment:katexEqnNo}.katex .mml-eqn-num:before{content:"(" counter(mmlEqnNo) ")";counter-increment:mmlEqnNo}.katex .mtr-glue{width:50%}.katex .cd-vert-arrow{display:inline-block;position:relative}.katex .cd-label-left{display:inline-block;position:absolute;right:calc(50% + .3em);text-align:left}.katex .cd-label-right{display:inline-block;left:calc(50% + .3em);position:absolute;text-align:right}.katex-display{display:block;margin:1em 0;text-align:center}.katex-display>.katex{display:block;text-align:center;white-space:nowrap}.katex-display>.katex>.katex-html{display:block;position:relative}.katex-display>.katex>.katex-html>.tag{position:absolute;right:0}.katex-display.leqno>.katex>.katex-html>.tag{left:0;right:auto}.katex-display.fleqn>.katex{padding-left:2em;text-align:left}body{counter-reset:katexEqnNo mmlEqnNo} 2 | -------------------------------------------------------------------------------- /dist/app.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | class BottomBarComponent extends AbstractParticleComponentParser { 5 | createParserPool() { 6 | return new Particle.ParserPool(undefined, {}) 7 | } 8 | } 9 | 10 | window.BottomBarComponent = BottomBarComponent 11 | 12 | 13 | 14 | 15 | 16 | class CodeMirrorShim { 17 | setSize() {} 18 | setValue(value) { 19 | this.value = value 20 | } 21 | getValue() { 22 | return this.value 23 | } 24 | } 25 | 26 | class CodeEditorComponent extends AbstractParticleComponentParser { 27 | toStumpCode() { 28 | return `div 29 | class ${CodeEditorComponent.name} 30 | style width:${this.width}px; 31 | textarea 32 | id EditorTextarea 33 | div   34 | id codeErrorsConsole` 35 | } 36 | 37 | createParserPool() { 38 | return new Particle.ParserPool(undefined, { 39 | value: Particle, 40 | }) 41 | } 42 | 43 | get codeMirrorValue() { 44 | return this.codeMirrorInstance.getValue() 45 | } 46 | 47 | codeWidgets = [] 48 | 49 | async _onCodeKeyUp() { 50 | const { willowBrowser } = this 51 | const code = this.codeMirrorValue 52 | if (this._code === code) return 53 | this._code = code 54 | const root = this.root 55 | root.updateLocalStorage(code) 56 | await root.buildMainProgram() 57 | const errs = root.mainProgram.getAllErrors() 58 | 59 | let errMessage = " " 60 | const errorCount = errs.length 61 | 62 | if (errorCount) { 63 | const plural = errorCount > 1 ? "s" : "" 64 | errMessage = `
${errorCount} error${plural}:
65 | ${errs.map((err, index) => `${index}. ${err}`).join("
")}` 66 | } 67 | 68 | willowBrowser.setHtmlOfElementWithIdHack("codeErrorsConsole", errMessage) 69 | 70 | const cursor = this.codeMirrorInstance.getCursor() 71 | 72 | // todo: what if 2 errors? 73 | this.codeMirrorInstance.operation(() => { 74 | this.codeWidgets.forEach((widget) => this.codeMirrorInstance.removeLineWidget(widget)) 75 | this.codeWidgets.length = 0 76 | 77 | errs 78 | .filter((err) => !err.isBlankLineError()) 79 | .filter((err) => !err.isCursorOnAtom(cursor.line, cursor.ch)) 80 | .slice(0, 1) // Only show 1 error at a time. Otherwise UX is not fun. 81 | .forEach((err) => { 82 | const el = err.getCodeMirrorLineWidgetElement(() => { 83 | this.codeMirrorInstance.setValue(root.mainProgram.asString) 84 | this._onCodeKeyUp() 85 | }) 86 | this.codeWidgets.push( 87 | this.codeMirrorInstance.addLineWidget(err.lineNumber - 1, el, { coverGutter: false, noHScroll: false }), 88 | ) 89 | }) 90 | const info = this.codeMirrorInstance.getScrollInfo() 91 | const after = this.codeMirrorInstance.charCoords({ line: cursor.line + 1, ch: 0 }, "local").top 92 | if (info.top + info.clientHeight < after) this.codeMirrorInstance.scrollTo(null, after - info.clientHeight + 3) 93 | }) 94 | 95 | clearTimeout(this._timeout) 96 | this._timeout = setTimeout(async () => { 97 | await this.root.loadNewDoc(this._code) 98 | }, 20) 99 | } 100 | 101 | get bufferValue() { 102 | return this.codeMirrorInstance ? this.codeMirrorValue : this.getParticle("value").subparticlesToString() 103 | } 104 | 105 | async particleComponentDidMount() { 106 | this._initCodeMirror() 107 | this._updateCodeMirror() 108 | super.particleComponentDidMount() 109 | } 110 | 111 | async particleComponentDidUpdate() { 112 | this._updateCodeMirror() 113 | super.particleComponentDidUpdate() 114 | } 115 | 116 | renderAndGetRenderReport(stumpParticle, index) { 117 | if (!this.isMounted()) return super.renderAndGetRenderReport(stumpParticle, index) 118 | this.setSize() 119 | return "" 120 | } 121 | 122 | setCodeMirrorValue(value) { 123 | this.codeMirrorInstance.setValue(value) 124 | this._code = value 125 | } 126 | 127 | _initCodeMirror() { 128 | if (this.isNodeJs()) return (this.codeMirrorInstance = new CodeMirrorShim()) 129 | this.codeMirrorInstance = new ParsersCodeMirrorMode( 130 | "custom", 131 | () => this.root.scrollFileEditor.getParsedProgramForCodeMirror(this.codeMirrorInstance.getValue()), 132 | CodeMirror, 133 | ) 134 | .register() 135 | .fromTextAreaWithAutocomplete(document.getElementById("EditorTextarea"), { 136 | lineWrapping: false, 137 | lineNumbers: false, 138 | }) 139 | this.codeMirrorInstance.on("keyup", () => this._onCodeKeyUp()) 140 | this.setSize() 141 | } 142 | 143 | get width() { 144 | return parseInt(this.getAtom(1)) 145 | } 146 | 147 | get chromeHeight() { 148 | return parseInt(this.getAtom(2)) 149 | } 150 | 151 | setSize() { 152 | if (this.isNodeJs()) return 153 | this.codeMirrorInstance.setSize(this.width, window.innerHeight - this.chromeHeight) 154 | } 155 | 156 | _updateCodeMirror() { 157 | this.setCodeMirrorValue(this.getParticle("value").subparticlesToString()) 158 | } 159 | } 160 | 161 | window.CodeEditorComponent = CodeEditorComponent 162 | 163 | 164 | // prettier-ignore 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | // prettier-ignore 178 | 179 | class githubTriangleComponent extends AbstractParticleComponentParser { 180 | githubLink = `https://github.com/breck7/tryscroll` 181 | toHakonCode() { 182 | return `.AbstractGithubTriangleComponent 183 | display block 184 | position absolute 185 | top 0 186 | right 0 187 | z-index 3` 188 | } 189 | toStumpCode() { 190 | return `a 191 | class AbstractGithubTriangleComponent 192 | href ${this.githubLink} 193 | target _blank 194 | img 195 | height 40px 196 | src public/github-fork.svg` 197 | } 198 | } 199 | 200 | class ErrorParticle extends AbstractParticleComponentParser { 201 | _isErrorParser() { 202 | return true 203 | } 204 | toStumpCode() { 205 | console.error(`Warning: EditorApp does not have a Parser for "${this.getLine()}"`) 206 | return `span 207 | style display: none;` 208 | } 209 | } 210 | 211 | let _defaultSeed = Date.now() 212 | const newSeed = () => { 213 | _defaultSeed++ 214 | return _defaultSeed 215 | } 216 | 217 | class EditorApp extends AbstractParticleComponentParser { 218 | createParserPool() { 219 | return new Particle.ParserPool(ErrorParticle, { 220 | TopBarComponent, 221 | githubTriangleComponent, 222 | CodeEditorComponent, 223 | ParticleComponentFrameworkDebuggerComponent, 224 | BottomBarComponent, 225 | EditorHandleComponent, 226 | ShowcaseComponent, 227 | }) 228 | } 229 | 230 | verbose = true 231 | 232 | get leftStartPosition() { 233 | return this.editor.width 234 | } 235 | 236 | get mainOutput() { 237 | return this.scrollFileEditor.mainOutput 238 | } 239 | 240 | get mainProgram() { 241 | return this.scrollFileEditor.mainProgram 242 | } 243 | 244 | get editor() { 245 | return this.getParticle(CodeEditorComponent.name) 246 | } 247 | 248 | get bufferValue() { 249 | return this.editor.bufferValue 250 | } 251 | 252 | async loadNewDoc(bufferValue) { 253 | this.renderAndGetRenderReport() 254 | this.updateLocalStorage(bufferValue) 255 | await this.scrollFileEditor.buildMainProgram() 256 | this.refreshHtml() 257 | } 258 | 259 | async buildMainProgram() { 260 | await this.scrollFileEditor.buildMainProgram() 261 | } 262 | 263 | // todo: cleanup 264 | async pasteCodeCommand(bufferValue) { 265 | this.editor.setCodeMirrorValue(bufferValue) 266 | await this.loadNewDoc(bufferValue) 267 | } 268 | 269 | async formatScrollCommand() { 270 | const bufferValue = await this.scrollFileEditor.getFormatted() 271 | this.editor.setCodeMirrorValue(bufferValue) 272 | await this.loadNewDoc(bufferValue) 273 | await this.scrollFileEditor.buildMainProgram() 274 | } 275 | 276 | updateLocalStorage(bufferValue) { 277 | if (this.isNodeJs()) return // todo: tcf should shim this 278 | localStorage.setItem(LocalStorageKeys.scroll, bufferValue) 279 | console.log("Local storage updated...") 280 | } 281 | 282 | get fileName() { 283 | return "tryscroll.scroll" 284 | } 285 | 286 | async initScrollFileEditor(parsersCode) { 287 | this.scrollFileEditor = new ScrollFileEditor(parsersCode, this) 288 | await this.scrollFileEditor.init() 289 | } 290 | 291 | refreshHtml() { 292 | this.getParticle(`${ShowcaseComponent.name}`).refresh() 293 | // todo: rehighlight? 294 | } 295 | 296 | async start() { 297 | const { willowBrowser } = this 298 | this._bindParticleComponentFrameworkCommandListenersOnBody() 299 | this.renderAndGetRenderReport(willowBrowser.getBodyStumpParticle()) 300 | 301 | const keyboardShortcuts = this._getKeyboardShortcuts() 302 | Object.keys(keyboardShortcuts).forEach((key) => { 303 | willowBrowser.getMousetrap().bind(key, function (evt) { 304 | keyboardShortcuts[key]() 305 | // todo: handle the below when we need to 306 | if (evt.preventDefault) evt.preventDefault() 307 | return false 308 | }) 309 | }) 310 | 311 | this.willowBrowser.setResizeEndHandler(() => { 312 | this.editor.setSize() 313 | }) 314 | 315 | await this.buildMainProgram() 316 | // todo: rehighlight? 317 | } 318 | 319 | log(message) { 320 | if (this.verbose) console.log(message) 321 | } 322 | 323 | get urlHash() { 324 | const particle = new Particle() 325 | particle.appendLineAndSubparticles(UrlKeys.scroll, this.bufferValue ?? "") 326 | return "#" + encodeURIComponent(particle.asString) 327 | } 328 | 329 | _getKeyboardShortcuts() { 330 | return { 331 | d: () => this.toggleParticleComponentFrameworkDebuggerCommand(), 332 | w: () => this.resizeEditorCommand(), 333 | } 334 | } 335 | 336 | resizeEditorCommand(newSize = SIZES.EDITOR_WIDTH) { 337 | this.editor.setAtom(1, newSize) 338 | 339 | if (!this.isNodeJs()) localStorage.setItem(LocalStorageKeys.editorStartWidth, newSize) 340 | this.renderAndGetRenderReport() 341 | } 342 | } 343 | 344 | const SIZES = {} 345 | 346 | SIZES.BOARD_MARGIN = 20 347 | SIZES.TOP_BAR_HEIGHT = 28 348 | SIZES.BOTTOM_BAR_HEIGHT = 40 349 | SIZES.CHROME_HEIGHT = SIZES.TOP_BAR_HEIGHT + SIZES.BOTTOM_BAR_HEIGHT + SIZES.BOARD_MARGIN 350 | SIZES.TITLE_HEIGHT = 20 351 | 352 | SIZES.EDITOR_WIDTH = Math.floor(typeof window !== "undefined" ? window.innerWidth / 2 : 400) 353 | SIZES.RIGHT_BAR_WIDTH = 30 354 | 355 | EditorApp.setupApp = async (bufferValue, parsersCode, windowWidth = 1000, windowHeight = 1000) => { 356 | const editorStartWidth = 357 | typeof localStorage !== "undefined" 358 | ? (localStorage.getItem(LocalStorageKeys.editorStartWidth) ?? SIZES.EDITOR_WIDTH) 359 | : SIZES.EDITOR_WIDTH 360 | const startState = new Particle(`${githubTriangleComponent.name} 361 | ${TopBarComponent.name} 362 | ${ShareComponent.name} 363 | ${ExportComponent.name} 364 | ${BottomBarComponent.name} 365 | ${CodeEditorComponent.name} ${editorStartWidth} ${SIZES.CHROME_HEIGHT} 366 | value 367 | ${bufferValue.replace(/\n/g, "\n ")} 368 | ${EditorHandleComponent.name} 369 | ${ShowcaseComponent.name}`) 370 | 371 | const app = new EditorApp(startState.asString) 372 | await app.initScrollFileEditor(parsersCode) 373 | app.windowWidth = windowWidth 374 | app.windowHeight = windowHeight 375 | return app 376 | } 377 | 378 | window.EditorApp = EditorApp 379 | 380 | 381 | 382 | 383 | class EditorHandleComponent extends AbstractParticleComponentParser { 384 | get left() { 385 | return this.root.editor.width 386 | } 387 | 388 | makeDraggable() { 389 | if (this.isNodeJs()) return 390 | 391 | const root = this.root 392 | const handle = this.getStumpParticle().getShadow().element 393 | jQuery(handle).draggable({ 394 | axis: "x", 395 | drag: function (event, ui) { 396 | if ("ontouchend" in document) return // do not update live on a touch device. otherwise buggy. 397 | root.resizeEditorCommand(Math.max(ui.offset.left, 5) + "") 398 | jQuery(".EditorHandleComponent").addClass("rightBorder") 399 | }, 400 | start: function (event, ui) { 401 | jQuery(".EditorHandleComponent").addClass("rightBorder") 402 | }, 403 | stop: function (event, ui) { 404 | root.resizeEditorCommand(Math.max(ui.offset.left, 5) + "") 405 | window.location = window.location 406 | jQuery(".EditorHandleComponent").removeClass("rightBorder") 407 | }, 408 | }) 409 | jQuery(this.getStumpParticle().getShadow().element).on("dblclick", () => { 410 | root.resizeEditorCommand() 411 | window.location = window.location 412 | }) 413 | } 414 | 415 | particleComponentDidMount() { 416 | this.makeDraggable() 417 | } 418 | 419 | particleComponentDidUpdate() { 420 | this.makeDraggable() 421 | } 422 | 423 | toStumpCode() { 424 | return `div 425 | class ${EditorHandleComponent.name} 426 | style left:${this.left}px;` 427 | } 428 | 429 | getDependencies() { 430 | return [this.root.editor] 431 | } 432 | } 433 | 434 | window.EditorHandleComponent = EditorHandleComponent 435 | 436 | 437 | 438 | 439 | class ExportComponent extends AbstractParticleComponentParser { 440 | toStumpCode() { 441 | return `div 442 | class ExportComponent 443 | a Format 444 | clickCommand formatScrollCommand 445 | span | 446 | a Tutorial 447 | target _blank 448 | href index.html#${encodeURIComponent("url https://scroll.pub/tutorial.scroll")} 449 | span | 450 | a Reset 451 | clickCommand resetCommand 452 | span | 453 | a Copy Output 454 | clickCommand copyOutputToClipboardCommand 455 | span | 456 | a Download Output 457 | clickCommand downloadOutputCommand` 458 | } 459 | 460 | resetCommand() { 461 | if (!confirm("Are you sure you want to reset?")) return 462 | localStorage.clear() 463 | window.location = "" 464 | } 465 | 466 | copyOutputToClipboardCommand() { 467 | this.root.willowBrowser.copyTextToClipboard(this.root.mainOutput.content) 468 | } 469 | 470 | formatScrollCommand() { 471 | this.root.formatScrollCommand() 472 | } 473 | 474 | downloadOutputCommand() { 475 | const program = this.root.mainProgram 476 | let mainOutput = this.root.mainOutput 477 | const filename = program.permalink 478 | let type = "text/" + mainOutput.type 479 | this.root.willowBrowser.downloadFile(mainOutput.content, filename, type) 480 | } 481 | 482 | get app() { 483 | return this.root 484 | } 485 | } 486 | 487 | window.ExportComponent = ExportComponent 488 | 489 | 490 | class UrlWriter extends MemoryWriter { 491 | async read(fileName) { 492 | if (this.inMemoryFiles[fileName]) return this.inMemoryFiles[fileName] 493 | if (!isUrl(fileName)) fileName = this.getBaseUrl() + fileName 494 | return await super.read(fileName) 495 | } 496 | async exists(fileName) { 497 | if (this.inMemoryFiles[fileName]) return true 498 | if (!isUrl(fileName)) fileName = this.getBaseUrl() + fileName 499 | return await super.exists(fileName) 500 | } 501 | } 502 | 503 | /* 504 | interface EditorParent { 505 | bufferValue: string 506 | fileName: string 507 | rootUrl: string 508 | } 509 | */ 510 | class ScrollFileEditor { 511 | constructor(defaultParserCode, parent) { 512 | this.parent = parent 513 | this.fakeFs = {} 514 | this.fs = new ScrollFileSystem(this.fakeFs) 515 | this.fs.setDefaultParserFromString(defaultParserCode) 516 | const urlWriter = new UrlWriter(this.fakeFs) 517 | urlWriter.getBaseUrl = () => parent.rootUrl || "" 518 | this.fs._storage = urlWriter 519 | } 520 | async init() { 521 | await this.buildMainProgram() 522 | } 523 | async scrollToHtml(scrollCode) { 524 | const parsed = await this._parseScroll(scrollCode) 525 | return parsed.asHtml 526 | } 527 | async _parseScroll(scrollCode) { 528 | const file = this.fs.newFile(scrollCode) 529 | await file.singlePassFuse() 530 | return file.scrollProgram 531 | } 532 | async makeFusedFile(code, filename) { 533 | const { fs } = this 534 | this.fakeFs[filename] = code 535 | const file = this.fs.newFile(code, filename) 536 | await file.singlePassFuse() 537 | return file 538 | } 539 | async getFusedFile() { 540 | const file = await this.makeFusedFile(this.bufferValue, "/" + this.parent.fileName) 541 | this.fusedFile = file 542 | return file 543 | } 544 | async getFusedCode() { 545 | const fusedFile = await this.getFusedFile() 546 | return fusedFile.scrollProgram.toString() 547 | } 548 | get bufferValue() { 549 | return this.parent.bufferValue 550 | } 551 | get errors() { 552 | const errs = this.mainProgram.getAllErrors() 553 | return new Particle(errs.map((err) => err.toObject())).toFormattedTable(200) 554 | } 555 | async buildMainProgram() { 556 | const fusedFile = await this.getFusedFile() 557 | const fusedCode = await this.getFusedCode() 558 | this.mainProgram = fusedFile.scrollProgram 559 | try { 560 | await this.mainProgram.load() 561 | } catch (err) { 562 | console.error(err) 563 | } 564 | return this.mainProgram 565 | } 566 | async getFormatted() { 567 | const mainDoc = await this.buildMainProgram(false) 568 | return mainDoc.formatted 569 | } 570 | get mainOutput() { 571 | const { mainProgram } = this 572 | const particle = mainProgram.filter((particle) => particle.buildOutput)[0] 573 | if (!particle) 574 | return { 575 | type: "html", 576 | content: mainProgram.buildHtml(), 577 | } 578 | return { 579 | type: particle.extension.toLowerCase(), 580 | content: particle.buildOutput(), 581 | } 582 | } 583 | _cachedCode 584 | _cachedProgram 585 | getParsedProgramForCodeMirror(code) { 586 | // Note: this uses the latest loaded constructor and does a SYNC parse. 587 | // This allows us to use and loaded parsers, but gives sync, real time best 588 | // answers for highlighting and autocomplete. 589 | // It reparses the whole document. Actually seems to be fine for now. 590 | // Ideally we could also just run off mainProgram and not reparse, but 591 | // it gets tricky with the CodeMirror lib and async stuff. Maybe in the 592 | // future we can clean this up. 593 | if (code === this._cachedCode) return this._cachedProgram 594 | 595 | this._cachedCode = code 596 | this._cachedProgram = new this.mainProgram.latestConstructor(code) 597 | return this._cachedProgram 598 | } 599 | } 600 | 601 | if (typeof module !== "undefined" && module.exports) module.exports = { ScrollFileEditor } 602 | 603 | 604 | 605 | 606 | class ShareComponent extends AbstractParticleComponentParser { 607 | toStumpCode() { 608 | return `div 609 | class ShareComponent 610 | input 611 | readonly 612 | title ${this.link} 613 | value ${this.link}` 614 | } 615 | 616 | getDependencies() { 617 | return [this.root.mainProgram] 618 | } 619 | 620 | get link() { 621 | const url = new URL(typeof location === "undefined" ? "http://localhost/" : location.href) // todo: TCF should provide shim for this 622 | url.hash = "" 623 | return url.toString() + this.root.urlHash 624 | } 625 | } 626 | 627 | window.ShareComponent = ShareComponent 628 | 629 | 630 | 631 | 632 | class ShowcaseComponent extends AbstractParticleComponentParser { 633 | // Track which iframe is currently visible 634 | activeIframeId = "theIframe1" 635 | 636 | async refresh() { 637 | // Get both iframes 638 | const iframe1 = document.getElementById("theIframe1") 639 | const iframe2 = document.getElementById("theIframe2") 640 | 641 | // Determine active and buffer iframes 642 | const activeIframe = document.getElementById(this.activeIframeId) 643 | const bufferIframe = this.activeIframeId === "theIframe1" ? iframe2 : iframe1 644 | 645 | // Store scroll position from active iframe 646 | const scrollTop = activeIframe.contentWindow ? activeIframe.contentWindow.scrollY : 0 647 | const scrollLeft = activeIframe.contentWindow ? activeIframe.contentWindow.scrollX : 0 648 | 649 | // Prepare content 650 | await this.root.buildMainProgram() 651 | const { mainOutput } = this.root 652 | let content = mainOutput.content 653 | if (mainOutput.type !== "html") { 654 | content = `
${content}
` 655 | } 656 | 657 | // Add scroll restoration script 658 | const scrollScript = ` 659 | 665 | ` 666 | 667 | // Update the hidden buffer iframe 668 | bufferIframe.srcdoc = content + scrollScript 669 | 670 | // Set up message listener for one-time swap 671 | const swapHandler = (event) => { 672 | if (event.data === "iframeReady") { 673 | // Swap visibility 674 | activeIframe.style.display = "none" 675 | bufferIframe.style.display = "block" 676 | 677 | // Update active iframe tracking 678 | this.activeIframeId = bufferIframe.id 679 | 680 | // Force all links to open in a new tab. 681 | // todo: perhaps handle differently for # links? 682 | jQuery(bufferIframe).contents().find("a:not([target])").attr("target", "_blank") 683 | 684 | // Remove the message listener 685 | window.removeEventListener("message", swapHandler) 686 | } 687 | } 688 | window.addEventListener("message", swapHandler) 689 | } 690 | 691 | particleComponentDidMount() { 692 | this.refresh() 693 | } 694 | 695 | toStumpCode() { 696 | return `div 697 | class ${ShowcaseComponent.name} 698 | style left:${this.root.leftStartPosition + 10}px; 699 | iframe 700 | id theIframe1 701 | style display:block;width:100%;height:100%;border:none; 702 | srcdoc   703 | iframe 704 | id theIframe2 705 | style display:none;width:100%;height:100%;border:none; 706 | srcdoc  ` 707 | } 708 | } 709 | 710 | window.ShowcaseComponent = ShowcaseComponent 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | class TopBarComponent extends AbstractParticleComponentParser { 719 | createParserPool() { 720 | return new Particle.ParserPool(undefined, { 721 | ShareComponent, 722 | ExportComponent, 723 | }) 724 | } 725 | } 726 | 727 | window.TopBarComponent = TopBarComponent 728 | 729 | 730 | const LocalStorageKeys = {} 731 | 732 | LocalStorageKeys.scroll = "scroll" 733 | LocalStorageKeys.editorStartWidth = "editorStartWidth" 734 | 735 | const UrlKeys = {} 736 | 737 | UrlKeys.scroll = "scroll" 738 | UrlKeys.url = "url" 739 | 740 | window.LocalStorageKeys = LocalStorageKeys 741 | 742 | window.UrlKeys = UrlKeys 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | const DEFAULT_PROGRAM = `title Scroll is a language for scientists of all ages 751 | printTitle 752 | 753 | theme gazette 754 | 755 | # Refine, share and collaborate on ideas 756 | 757 | ## Build html files, CSV files, text files, and more. 758 | 759 | ### Scroll is an extensible alternative to Markdown. 760 | https://scroll.pub Scroll 761 | 762 | *** 763 | 764 | // You can have multiple columns 765 | thinColumns 2 766 | 767 | ## Links are different 768 | You put links _after_ the text, like this one. The code: 769 | https://scroll.pub one. 770 | aboveAsCode 771 | 772 | ? What's the benefit for using today? 773 | - A very flat plain text format 774 | - Keeps your thoughts and data organized 775 | - Build fully documented and cited CSV files 776 | - Ready to _grow_ with _you_. 777 | - Designed to last. 778 | 779 | # Scroll supports tables 780 | datatable 781 | printTable 782 | delimiter , 783 | data 784 | Language,Types 785 | HTML,~142 786 | Markdown,~192 787 | Scroll,~202 + yours 788 | 789 | # Images in Scroll 790 | // Just put the filename or URL: 791 | https://scroll.pub/blog/screenshot.png 792 | caption This is a screenshot of a blog 793 | https://breckyunits.com/ blog 794 | aboveAsCode 795 | 796 | expander Click me. 797 | You can easily add collapsed content. 798 | 799 | ## Scroll has footnotes^note 800 | 801 | ^note Sometimes called endnotes 802 | ` 803 | 804 | class BrowserGlue extends AbstractParticleComponentParser { 805 | async fetchAndLoadScrollCodeFromUrlCommand(url) { 806 | const code = await this.fetchText(url) 807 | return code 808 | } 809 | 810 | async fetchText(url) { 811 | const result = await fetch(url) 812 | const text = await result.text() 813 | return text 814 | } 815 | 816 | getFromLocalStorage() { 817 | return localStorage.getItem(LocalStorageKeys.scroll) 818 | } 819 | 820 | async fetchCode() { 821 | const hash = this.willowBrowser.getHash().substr(1) 822 | const deepLink = new Particle(decodeURIComponent(hash)) 823 | const fromUrl = deepLink.get(UrlKeys.url) 824 | const code = deepLink.getParticle(UrlKeys.scroll) 825 | 826 | // Clear hash 827 | history.pushState("", document.title, window.location.pathname) 828 | 829 | if (fromUrl) return this.fetchAndLoadScrollCodeFromUrlCommand(fromUrl) 830 | if (code) return code.subparticlesToString() 831 | 832 | const localStorageCode = this.getFromLocalStorage() 833 | if (localStorageCode) return localStorageCode 834 | 835 | return DEFAULT_PROGRAM 836 | } 837 | 838 | async init(parsersCode) { 839 | const scrollCode = await this.fetchCode() 840 | 841 | window.app = await EditorApp.setupApp(scrollCode, parsersCode, window.innerWidth, window.innerHeight) 842 | window.app.start() 843 | return window.app 844 | } 845 | } 846 | 847 | window.BrowserGlue = BrowserGlue 848 | -------------------------------------------------------------------------------- /lib/jquery-ui.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.12.1 - 2021-08-17 2 | * http://jqueryui.com 3 | * Includes: widget.js, data.js, scroll-parent.js, widgets/draggable.js, widgets/mouse.js 4 | * Copyright jQuery Foundation and other contributors; Licensed MIT */ 5 | 6 | !function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)}(function(b){b.ui=b.ui||{};b.ui.version="1.12.1";var o,s=0,a=Array.prototype.slice;b.cleanData=(o=b.cleanData,function(t){for(var e,s,i=0;null!=(s=t[i]);i++)try{(e=b._data(s,"events"))&&e.remove&&b(s).triggerHandler("remove")}catch(t){}o(t)}),b.widget=function(t,s,e){var i,o,n,r={},a=t.split(".")[0],l=a+"-"+(t=t.split(".")[1]);return e||(e=s,s=b.Widget),b.isArray(e)&&(e=b.extend.apply(null,[{}].concat(e))),b.expr[":"][l.toLowerCase()]=function(t){return!!b.data(t,l)},b[a]=b[a]||{},i=b[a][t],o=b[a][t]=function(t,e){if(!this._createWidget)return new o(t,e);arguments.length&&this._createWidget(t,e)},b.extend(o,i,{version:e.version,_proto:b.extend({},e),_childConstructors:[]}),(n=new s).options=b.widget.extend({},n.options),b.each(e,function(e,i){function o(){return s.prototype[e].apply(this,arguments)}function n(t){return s.prototype[e].apply(this,t)}b.isFunction(i)?r[e]=function(){var t,e=this._super,s=this._superApply;return this._super=o,this._superApply=n,t=i.apply(this,arguments),this._super=e,this._superApply=s,t}:r[e]=i}),o.prototype=b.widget.extend(n,{widgetEventPrefix:i&&n.widgetEventPrefix||t},r,{constructor:o,namespace:a,widgetName:t,widgetFullName:l}),i?(b.each(i._childConstructors,function(t,e){var s=e.prototype;b.widget(s.namespace+"."+s.widgetName,o,e._proto)}),delete i._childConstructors):s._childConstructors.push(o),b.widget.bridge(t,o),o},b.widget.extend=function(t){for(var e,s,i=a.call(arguments,1),o=0,n=i.length;o",options:{classes:{},disabled:!1,create:null},_createWidget:function(t,e){e=b(e||this.defaultElement||this)[0],this.element=b(e),this.uuid=s++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=b(),this.hoverable=b(),this.focusable=b(),this.classesElementLookup={},e!==this&&(b.data(e,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===e&&this.destroy()}}),this.document=b(e.style?e.ownerDocument:e.document||e),this.window=b(this.document[0].defaultView||this.document[0].parentWindow)),this.options=b.widget.extend({},this.options,this._getCreateOptions(),t),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:b.noop,_create:b.noop,_init:b.noop,destroy:function(){var s=this;this._destroy(),b.each(this.classesElementLookup,function(t,e){s._removeClass(e,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:b.noop,widget:function(){return this.element},option:function(t,e){var s,i,o,n=t;if(0===arguments.length)return b.widget.extend({},this.options);if("string"==typeof t)if(n={},t=(s=t.split(".")).shift(),s.length){for(i=n[t]=b.widget.extend({},this.options[t]),o=0;o=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),b.ui.plugin={add:function(t,e,s){var i,o=b.ui[t].prototype;for(i in s)o.plugins[i]=o.plugins[i]||[],o.plugins[i].push([e,s[i]])},call:function(t,e,s,i){var o,n=t.plugins[e];if(n&&(i||t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType))for(o=0;o").css("position","absolute").appendTo(t.parent()).outerWidth(t.outerWidth()).outerHeight(t.outerHeight()).offset(t.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_blurActiveElement:function(t){var e=b.ui.safeActiveElement(this.document[0]);b(t.target).closest(e).length||b.ui.safeBlur(e)},_mouseStart:function(t){var e=this.options;return this.helper=this._createHelper(t),this._addClass(this.helper,"ui-draggable-dragging"),this._cacheHelperProportions(),b.ui.ddmanager&&(b.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(!0),this.offsetParent=this.helper.offsetParent(),this.hasFixedAncestor=0s[2]&&(n=s[2]+this.offset.click.left),t.pageY-this.offset.click.top>s[3]&&(r=s[3]+this.offset.click.top)),i.grid&&(t=i.grid[1]?this.originalPageY+Math.round((r-this.originalPageY)/i.grid[1])*i.grid[1]:this.originalPageY,r=!s||t-this.offset.click.top>=s[1]||t-this.offset.click.top>s[3]?t:t-this.offset.click.top>=s[1]?t-i.grid[1]:t+i.grid[1],t=i.grid[0]?this.originalPageX+Math.round((n-this.originalPageX)/i.grid[0])*i.grid[0]:this.originalPageX,n=!s||t-this.offset.click.left>=s[0]||t-this.offset.click.left>s[2]?t:t-this.offset.click.left>=s[0]?t-i.grid[0]:t+i.grid[0]),"y"===i.axis&&(n=this.originalPageX),"x"===i.axis&&(r=this.originalPageY)),{top:r-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.offset.scroll.top:o?0:this.offset.scroll.top),left:n-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.offset.scroll.left:o?0:this.offset.scroll.left)}},_clear:function(){this._removeClass(this.helper,"ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1,this.destroyOnClear&&this.destroy()},_trigger:function(t,e,s){return s=s||this._uiHash(),b.ui.plugin.call(this,t,[e,s,this],!0),/^(drag|start|stop)/.test(t)&&(this.positionAbs=this._convertPositionTo("absolute"),s.offset=this.positionAbs),b.Widget.prototype._trigger.call(this,t,e,s)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),b.ui.plugin.add("draggable","connectToSortable",{start:function(e,t,s){var i=b.extend({},t,{item:s.element});s.sortables=[],b(s.options.connectToSortable).each(function(){var t=b(this).sortable("instance");t&&!t.options.disabled&&(s.sortables.push(t),t.refreshPositions(),t._trigger("activate",e,i))})},stop:function(e,t,s){var i=b.extend({},t,{item:s.element});s.cancelHelperRemoval=!1,b.each(s.sortables,function(){var t=this;t.isOver?(t.isOver=0,s.cancelHelperRemoval=!0,t.cancelHelperRemoval=!1,t._storedCSS={position:t.placeholder.css("position"),top:t.placeholder.css("top"),left:t.placeholder.css("left")},t._mouseStop(e),t.options.helper=t.options._helper):(t.cancelHelperRemoval=!0,t._trigger("deactivate",e,i))})},drag:function(s,i,o){b.each(o.sortables,function(){var t=!1,e=this;e.positionAbs=o.positionAbs,e.helperProportions=o.helperProportions,e.offset.click=o.offset.click,e._intersectsWith(e.containerCache)&&(t=!0,b.each(o.sortables,function(){return this.positionAbs=o.positionAbs,this.helperProportions=o.helperProportions,this.offset.click=o.offset.click,t=this!==e&&this._intersectsWith(this.containerCache)&&b.contains(e.element[0],this.element[0])?!1:t})),t?(e.isOver||(e.isOver=1,o._parent=i.helper.parent(),e.currentItem=i.helper.appendTo(e.element).data("ui-sortable-item",!0),e.options._helper=e.options.helper,e.options.helper=function(){return i.helper[0]},s.target=e.currentItem[0],e._mouseCapture(s,!0),e._mouseStart(s,!0,!0),e.offset.click.top=o.offset.click.top,e.offset.click.left=o.offset.click.left,e.offset.parent.left-=o.offset.parent.left-e.offset.parent.left,e.offset.parent.top-=o.offset.parent.top-e.offset.parent.top,o._trigger("toSortable",s),o.dropped=e.element,b.each(o.sortables,function(){this.refreshPositions()}),o.currentItem=o.element,e.fromOutside=o),e.currentItem&&(e._mouseDrag(s),i.position=e.position)):e.isOver&&(e.isOver=0,e.cancelHelperRemoval=!0,e.options._revert=e.options.revert,e.options.revert=!1,e._trigger("out",s,e._uiHash(e)),e._mouseStop(s,!0),e.options.revert=e.options._revert,e.options.helper=e.options._helper,e.placeholder&&e.placeholder.remove(),i.helper.appendTo(o._parent),o._refreshOffsets(s),i.position=o._generatePosition(s,!0),o._trigger("fromSortable",s),o.dropped=!1,b.each(o.sortables,function(){this.refreshPositions()}))})}}),b.ui.plugin.add("draggable","cursor",{start:function(t,e,s){var i=b("body"),s=s.options;i.css("cursor")&&(s._cursor=i.css("cursor")),i.css("cursor",s.cursor)},stop:function(t,e,s){s=s.options;s._cursor&&b("body").css("cursor",s._cursor)}}),b.ui.plugin.add("draggable","opacity",{start:function(t,e,s){e=b(e.helper),s=s.options;e.css("opacity")&&(s._opacity=e.css("opacity")),e.css("opacity",s.opacity)},stop:function(t,e,s){s=s.options;s._opacity&&b(e.helper).css("opacity",s._opacity)}}),b.ui.plugin.add("draggable","scroll",{start:function(t,e,s){s.scrollParentNotHidden||(s.scrollParentNotHidden=s.helper.scrollParent(!1)),s.scrollParentNotHidden[0]!==s.document[0]&&"HTML"!==s.scrollParentNotHidden[0].tagName&&(s.overflowOffset=s.scrollParentNotHidden.offset())},drag:function(t,e,s){var i=s.options,o=!1,n=s.scrollParentNotHidden[0],r=s.document[0];n!==r&&"HTML"!==n.tagName?(i.axis&&"x"===i.axis||(s.overflowOffset.top+n.offsetHeight-t.pageY