├── .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 |
--------------------------------------------------------------------------------
/.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