Rink-web 3 (#165)
Kind of silly that I'm already on the third iteration of the website. But the previous one was a bit over-engineered and hard to maintain. I actually can't even get it to compile anymore, it errors out while trying to build the wasm binary. So it was either migrate from sapper (which is deprecated and broken) to svelte-kit (the successor to sapper), or to simplify. I simplified. The new tech jenga tower is: - Soupault for building the website - AsciiDoctor for the pages - cmark-gfm for processing the markdown in the github releases - Several plugins and scripts copied from my personal website - Vite for compiling the javascript - wasm-pack for building the wasm binary Rink's website will now be completely static, so operating it is easier. The javascript and wasm is quarantined to the index page where a rink repl is active, the other pages are plain html. The currency fetching is now a totally separate piece of code from the website. It makes it simpler and requires less code to be loaded during the regular cron job. The javascript is way simpler now. It only manipulates the part of the page that need to be dynamic. It also uses the token format machinery instead of directly converting rink's output AST to html. Pros: - Way lighter, faster page load times. - Easier to maintain. - Site contains more information now. An about page, the manual, and a releases tab with DL links. - No more URL spam that makes search engines think the site is a content farm. - Actually acts as a REPL now so you can run multiple queries, use `ans`, up/down arrows to re-run previous queries. - Proper sandboxing - queries that timeout cause the worker to be killed and restarted. - Now has a progress indicator on downloading the wasm blob, which is good on slow connections. Cons: - Even more of a custom setup than before. - It doesn't work offline anymore. At least not until I add a new service worker, which will be a real pain to debug. - The interactive parts (the rink REPL) require javascript to work now. - A lot of the old URLs like `/query/abc` and `/units/meter` are broken. I think this is fine though. I can add redirects in nginx that point them to `/?q=abc` and `/?q=meter`. ## Screenshots ### REPL ![image](https://github.com/tiffany352/rink-rs/assets/1254344/7e43fe58-134f-425c-b975-997797fc1af4) ### About page ![image](https://github.com/tiffany352/rink-rs/assets/1254344/d6a34ce5-a357-434c-b7e9-9ddee3884223)
3
.editorconfig
Normal file
|
@ -0,0 +1,3 @@
|
|||
[*.{svelte,js,ts,html,svg,json,lua,css,xml}]
|
||||
indent_size = 4
|
||||
indent_style = tab
|
135
Cargo.lock
generated
|
@ -88,7 +88,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener 5.2.0",
|
||||
"event-listener 5.3.0",
|
||||
"event-listener-strategy 0.5.1",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
|
@ -106,9 +106,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-executor"
|
||||
version = "1.9.1"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10b3e585719c2358d2660232671ca8ca4ddb4be4ce8a1842d6c2dc8685303316"
|
||||
checksum = "5f98c37cf288e302c16ef6c8472aad1e034c6c84ce5ea7b8101c98eb4a802fee"
|
||||
dependencies = [
|
||||
"async-lock 3.3.0",
|
||||
"async-task",
|
||||
|
@ -349,9 +349,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.15.4"
|
||||
version = "3.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
|
||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
|
@ -361,9 +361,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.90"
|
||||
version = "1.0.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
|
||||
checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
@ -394,7 +394,7 @@ dependencies = [
|
|||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.52.4",
|
||||
"windows-targets 0.52.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -602,7 +602,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -673,9 +673,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.2.0"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91"
|
||||
checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"parking",
|
||||
|
@ -698,7 +698,7 @@ version = "0.5.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "332f51cb23d20b0de8458b86580878211da09bcd4503cb579c225b3d124cabb3"
|
||||
dependencies = [
|
||||
"event-listener 5.2.0",
|
||||
"event-listener 5.3.0",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
|
@ -789,9 +789,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.12"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
|
||||
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
|
@ -966,13 +966,12 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
|||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.0.1"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
|
||||
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1138,9 +1137,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
|||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.101"
|
||||
version = "0.9.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff"
|
||||
checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
@ -1183,9 +1182,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.13"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
|
@ -1252,9 +1251,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
@ -1269,20 +1268,11 @@ dependencies = [
|
|||
"nibble_vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.4"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
|
||||
checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
|
@ -1510,7 +1500,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1544,9 +1534,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "similar"
|
||||
version = "2.4.0"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21"
|
||||
checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"unicode-segmentation",
|
||||
|
@ -1622,9 +1612,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.55"
|
||||
version = "2.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0"
|
||||
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1675,7 +1665,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1716,7 +1706,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1834,7 +1824,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
|
@ -1868,7 +1858,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
@ -1901,7 +1891,7 @@ checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1963,7 +1953,7 @@ version = "0.52.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.4",
|
||||
"windows-targets 0.52.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1981,7 +1971,7 @@ version = "0.52.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.4",
|
||||
"windows-targets 0.52.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2001,17 +1991,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.4"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
|
||||
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.4",
|
||||
"windows_aarch64_msvc 0.52.4",
|
||||
"windows_i686_gnu 0.52.4",
|
||||
"windows_i686_msvc 0.52.4",
|
||||
"windows_x86_64_gnu 0.52.4",
|
||||
"windows_x86_64_gnullvm 0.52.4",
|
||||
"windows_x86_64_msvc 0.52.4",
|
||||
"windows_aarch64_gnullvm 0.52.5",
|
||||
"windows_aarch64_msvc 0.52.5",
|
||||
"windows_i686_gnu 0.52.5",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc 0.52.5",
|
||||
"windows_x86_64_gnu 0.52.5",
|
||||
"windows_x86_64_gnullvm 0.52.5",
|
||||
"windows_x86_64_msvc 0.52.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2022,9 +2013,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
|||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.4"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
|
||||
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
|
@ -2034,9 +2025,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
|||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.4"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
|
||||
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
|
@ -2046,9 +2037,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
|||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.4"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
|
||||
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
|
@ -2058,9 +2055,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
|||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.4"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
|
||||
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
|
@ -2070,9 +2067,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
|||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.4"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
|
||||
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
|
@ -2082,9 +2079,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
|||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.4"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
|
||||
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
|
@ -2094,6 +2091,6 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
|||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.4"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
|
||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||
|
|
|
@ -66,3 +66,8 @@ pub fn simple_context() -> Result<Context, String> {
|
|||
|
||||
Ok(ctx)
|
||||
}
|
||||
|
||||
// Returns `env!("CARGO_PKG_VERSION")`, a string in `x.y.z` format.
|
||||
pub fn version() -> &'static str {
|
||||
env!("CARGO_PKG_VERSION")
|
||||
}
|
||||
|
|
|
@ -49,4 +49,6 @@ mod helpers;
|
|||
|
||||
pub use crate::loader::Context;
|
||||
pub use crate::runtime::Value;
|
||||
pub use helpers::{eval, one_line, simple_context, CURRENCY_FILE, DATES_FILE, DEFAULT_FILE};
|
||||
pub use helpers::{
|
||||
eval, one_line, simple_context, version, CURRENCY_FILE, DATES_FILE, DEFAULT_FILE,
|
||||
};
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
use std::{borrow::Cow, fmt, iter::Peekable};
|
||||
|
||||
use serde_derive::Serialize;
|
||||
|
||||
/// Represents a node in a token tree. Each token is tagged with a hint
|
||||
/// for how it should be displayed.
|
||||
///
|
||||
|
@ -125,7 +127,8 @@ impl<'a> fmt::Debug for Span<'a> {
|
|||
|
||||
/// Provides a hint for how a string should be displayed, based on its
|
||||
/// contents.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum FmtToken {
|
||||
/// Indicator text that isn't based on user input. Generally displayed without any formatting.
|
||||
Plain,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
ifndef::website[]
|
||||
= rink-dates(5)
|
||||
:manmanual: Rink Manual
|
||||
:mansource: Rink Manual
|
||||
|
@ -9,6 +10,7 @@ rink-dates - Rink file format for date patterns
|
|||
|
||||
Synposis
|
||||
--------
|
||||
endif::[]
|
||||
|
||||
Rink allows specifying datetimes using **\#date#** syntax in queries. This
|
||||
file defines the patterns that are used to try to match the specified
|
||||
|
@ -113,7 +115,9 @@ Rink searches the following locations:
|
|||
* `__$XDG_CONFIG_DIR__/rink/datepatterns.txt`
|
||||
* `/usr/share/rink/datepatterns.txt`
|
||||
|
||||
ifndef::website[]
|
||||
See also
|
||||
--------
|
||||
xref:rink.1.adoc[rink(1)], xref:rink.5.adoc[rink(5)],
|
||||
xref:rink.7.adoc[rink(7)], xref:rink-defs.5.adoc[rink-defs(5)]
|
||||
endif::[]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
ifndef::website[]
|
||||
= rink-defs(5)
|
||||
:manmanual: Rink Manual
|
||||
:mansource: Rink Manual
|
||||
|
@ -6,6 +7,7 @@ Name
|
|||
----
|
||||
|
||||
rink-defs - Rink's `definition.units` format
|
||||
endif::[]
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
@ -272,7 +274,9 @@ Notable differences include:
|
|||
- Addition of documentation comments starting with `??`.
|
||||
- Addition of substances.
|
||||
|
||||
ifndef::website[]
|
||||
See also
|
||||
--------
|
||||
xref:rink.1.adoc[rink(1)], xref:rink.5.adoc[rink(5)],
|
||||
xref:rink.7.adoc[rink(7)], xref:rink-dates.5.adoc[rink-dates(5)]
|
||||
endif::[]
|
||||
|
|
|
@ -63,8 +63,10 @@ Bugs
|
|||
|
||||
<https://github.com/tiffany352/rink-rs/issues>
|
||||
|
||||
ifndef::website[]
|
||||
See also
|
||||
--------
|
||||
xref:rink.5.adoc[rink(5)], xref:rink.7.adoc[rink(7)],
|
||||
xref:rink-defs.5.adoc[rink-defs(5)],
|
||||
xref:rink-dates.5.adoc[rink-dates(5)]
|
||||
endif::[]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
ifndef::website[]
|
||||
= rink(5)
|
||||
:manmanual: Rink Manual
|
||||
:mansource: Rink Manual
|
||||
|
@ -5,6 +6,7 @@
|
|||
Name
|
||||
----
|
||||
rink - TOML configuration file format.
|
||||
endif::[]
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
@ -146,8 +148,10 @@ Windows::
|
|||
macOS::
|
||||
++__$HOME__/Library/Application Support/rink/config.toml++
|
||||
|
||||
ifndef::website[]
|
||||
See also
|
||||
--------
|
||||
xref:rink.1.adoc[rink(1)], xref:rink.7.adoc[rink(7)],
|
||||
xref:rink-defs.5.adoc[rink-defs(5)],
|
||||
xref:rink-dates.5.adoc[rink-dates(5)]
|
||||
endif::[]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
ifndef::website[]
|
||||
= rink(7)
|
||||
:manmanual: Rink Manual
|
||||
:mansource: Rink Manual
|
||||
|
@ -8,6 +9,8 @@ rink - Query language documentation
|
|||
|
||||
Synposis
|
||||
--------
|
||||
endif::[]
|
||||
|
||||
Rink's query language follows a somewhat natural language style for its
|
||||
syntax. Many expressions like *3.5 meters to feet* will work without any
|
||||
modification. This manual is meant for those who want to get a deeper
|
||||
|
@ -515,9 +518,9 @@ Reference
|
|||
---------
|
||||
|
||||
The full list of units is specified in the file `definitions.units` (see
|
||||
xref:rink-defs.5.adoc[rink-defs(5)]), but a small list of the most
|
||||
helpful ones will be listed here. It is intended that most units should
|
||||
be easy to guess the names of.
|
||||
rink-defs(5)), but a small list of the most helpful ones will be listed
|
||||
here. It is intended that most units should be easy to guess the names
|
||||
of.
|
||||
|
||||
SI derived
|
||||
~~~~~~~~~~
|
||||
|
@ -708,8 +711,10 @@ rationals.
|
|||
- `acosh(x)`: Inverse hyperbolic cosine function.
|
||||
- `atanh(x)`: Inverse hyperbolic tangent function.
|
||||
|
||||
ifndef::website[]
|
||||
See also
|
||||
--------
|
||||
xref:rink.1.adoc[rink(1)], xref:rink.5.adoc[rink(5)],
|
||||
xref:rink-defs.5.adoc[rink-defs(5)],
|
||||
xref:rink-dates.5.adoc[rink-dates(5)]
|
||||
endif::[]
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
use chrono::{Local, TimeZone};
|
||||
use js_sys::Date;
|
||||
use rink_core::ast;
|
||||
use rink_core::output::fmt::{FmtToken, Span, TokenFmt};
|
||||
use rink_core::output::QueryReply;
|
||||
use rink_core::parsing::text_query;
|
||||
use serde_derive::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
@ -68,6 +70,35 @@ pub struct Context {
|
|||
context: rink_core::Context,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct Token {
|
||||
pub text: String,
|
||||
pub fmt: FmtToken,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
enum SpanOrList {
|
||||
Span(Token),
|
||||
List { children: Vec<SpanOrList> },
|
||||
}
|
||||
|
||||
fn visit_tokens(spans: &[Span]) -> Vec<SpanOrList> {
|
||||
spans
|
||||
.iter()
|
||||
.map(|span| match span {
|
||||
Span::Content { text, token } => SpanOrList::Span(Token {
|
||||
text: text.to_string(),
|
||||
fmt: *token,
|
||||
}),
|
||||
Span::Child(child) => SpanOrList::List {
|
||||
children: visit_tokens(&child.to_spans()),
|
||||
},
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Context {
|
||||
#[wasm_bindgen(constructor)]
|
||||
|
@ -79,6 +110,11 @@ impl Context {
|
|||
Context { context }
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = setSavePreviousResult)]
|
||||
pub fn set_save_previous_result(&mut self, value: bool) {
|
||||
self.context.save_previous_result = value;
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = setTime)]
|
||||
pub fn set_time(&mut self, date: Date) {
|
||||
self.context
|
||||
|
@ -109,10 +145,45 @@ impl Context {
|
|||
|
||||
#[wasm_bindgen]
|
||||
pub fn eval(&mut self, expr: &Query) -> JsValue {
|
||||
let value = Success::from(self.context.eval_query(&expr.query));
|
||||
let value = self.context.eval_query(&expr.query);
|
||||
if self.context.save_previous_result {
|
||||
if let Ok(QueryReply::Number(ref number_parts)) = value {
|
||||
if let Some(ref raw) = number_parts.raw_value {
|
||||
self.context.previous_result = Some(raw.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
let value = Success::from(value);
|
||||
match serde_wasm_bindgen::to_value(&value) {
|
||||
Ok(value) => value,
|
||||
Err(err) => format!("Failed to serialize: {}\n{:#?}", err, value).into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn eval_tokens(&mut self, expr: &Query) -> JsValue {
|
||||
let value = self.context.eval_query(&expr.query);
|
||||
if self.context.save_previous_result {
|
||||
if let Ok(QueryReply::Number(ref number_parts)) = value {
|
||||
if let Some(ref raw) = number_parts.raw_value {
|
||||
self.context.previous_result = Some(raw.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
let spans = match value {
|
||||
Ok(ref value) => value.to_spans(),
|
||||
Err(ref value) => value.to_spans(),
|
||||
};
|
||||
let tokens = visit_tokens(&spans);
|
||||
|
||||
match serde_wasm_bindgen::to_value(&tokens) {
|
||||
Ok(value) => value,
|
||||
Err(err) => format!("Failed to serialize: {}\n{:#?}", err, tokens).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn version() -> String {
|
||||
rink_core::version().to_owned()
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[*.{svelte,js,ts,html,svg}]
|
||||
indent_size = 2
|
||||
indent_style = space
|
34
web/README.adoc
Normal file
|
@ -0,0 +1,34 @@
|
|||
= Rink web
|
||||
|
||||
This is the sources required to build https://rinkcalc.app, which is
|
||||
a statically generated site.
|
||||
|
||||
== Requirements
|
||||
|
||||
* `wasm-pack` (`pacman -S wasm-pack` or cargo install)
|
||||
* `cmark-gfm` (`pacman -S cmark-gfm`)
|
||||
* https://soupault.app/install/[soupault]
|
||||
* Node.JS (21.7.1 known to work)
|
||||
|
||||
== Instructions
|
||||
|
||||
1. Run `wasm-pack build --target web rink-js`. This only needs to be
|
||||
re-run after making changes to rust code.
|
||||
2. Enter `web/repl/` and run `npm install`
|
||||
3. Enter `web/` and run `npm install`
|
||||
4. Enter `web/` and run `soupault --verbose`
|
||||
5. Enter `web/currency/` and run `npm install`
|
||||
6. Enter `web/build/` and run `node ../currency/update-currency.js`
|
||||
7. To view, enter `web/build/` and run
|
||||
`python -m http.server -b localhost 3000 --protocol HTTP/1.1`
|
||||
|
||||
Github releases are only fetched once. To re-fetch them, delete
|
||||
`web/build/releases.json` and re-run soupault.
|
||||
|
||||
== Deployment
|
||||
|
||||
Copy the contents of `web/build/` to your server.
|
||||
|
||||
Copy `web/currency/`, including its `node_modules`, to your server.
|
||||
Setup a cron job to run the currency script to update the `data/`
|
||||
directory, about once an hour is good.
|
|
@ -1,45 +0,0 @@
|
|||
# Rink Web
|
||||
|
||||
This is a web interface to Rink that works by compiling Rink to
|
||||
WebAssembly. The frontend is written in TypeScript using Svelte +
|
||||
Sapper.
|
||||
|
||||
This is a progressive web app, and also supports server side rendering.
|
||||
|
||||
## Development
|
||||
|
||||
Make sure you have Rustup and the latest Stable Rust toolchain.
|
||||
|
||||
All the dependencies need to be installed by running:
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
Once that's done, you can start the dev server by running:
|
||||
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
|
||||
This will automatically compile Rink to wasm for you. It will detect
|
||||
changes made to rink-js and recompile, but changes to rink-core will
|
||||
require restarting the dev server.
|
||||
|
||||
## Deployment
|
||||
|
||||
To deploy rink-web, there's a couple of steps you'll need to do:
|
||||
|
||||
1. Run `npx sapper build build`. This will compile the app and put it
|
||||
into `build/`.
|
||||
2. Move your `node_modules` out of the way and run `npm install --production` to get just the production dependencies.
|
||||
3. Copy `build/`, `node_modules/` and `static/` to your web server.
|
||||
4. Run `node build/` to start the server. It will respect the `PORT` and
|
||||
`NODE_ENV` environment variables, which default to `3000` and
|
||||
`production` respectively.
|
||||
|
||||
The following directories can be served statically, as long as there's
|
||||
still a fallback that reverse proxies the node server:
|
||||
|
||||
- `static/` is mounted on `/`.
|
||||
- `build/client/` is mounted on `/client/`.
|
177
web/currency/package-lock.json
generated
Normal file
|
@ -0,0 +1,177 @@
|
|||
{
|
||||
"name": "rink-currency-updater",
|
||||
"version": "0.6.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "rink-currency-updater",
|
||||
"version": "0.6.3",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"atomically": "1.7.0",
|
||||
"elementtree": "^0.1.7",
|
||||
"node-fetch": "^2.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/elementtree": "^0.1.0",
|
||||
"@types/node-fetch": "^2.5.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/elementtree": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/elementtree/-/elementtree-0.1.5.tgz",
|
||||
"integrity": "sha512-sDPG9iHzbN7Cvf0FLVzmaRiNMCYPQXh+xt0QI/I9P/sPdPX0veG/gvxA4eMuOq6tReg4cpXVH3356y2Qzdl+5g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.12.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
|
||||
"integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node-fetch": {
|
||||
"version": "2.6.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz",
|
||||
"integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"form-data": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/atomically": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz",
|
||||
"integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==",
|
||||
"engines": {
|
||||
"node": ">=10.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/elementtree": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/elementtree/-/elementtree-0.1.7.tgz",
|
||||
"integrity": "sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==",
|
||||
"dependencies": {
|
||||
"sax": "1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.1.4.tgz",
|
||||
"integrity": "sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg=="
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
web/currency/package.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"type": "module",
|
||||
"author": "Tiffany Bennett (https://tiffnix.com)",
|
||||
"name": "rink-currency-updater",
|
||||
"version": "0.6.3",
|
||||
"repository": "https://github.com/tiffany352/rink-rs",
|
||||
"homepage": "https://rinkcalc.app",
|
||||
"description": "Fetches currency data for rink",
|
||||
"license": "MPL-2.0",
|
||||
"devDependencies": {
|
||||
"@types/elementtree": "^0.1.0",
|
||||
"@types/node-fetch": "^2.5.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"elementtree": "^0.1.7",
|
||||
"node-fetch": "^2.6.1",
|
||||
"atomically": "1.7.0"
|
||||
}
|
||||
}
|
138
web/currency/update-currency.js
Normal file
|
@ -0,0 +1,138 @@
|
|||
import fetch from "node-fetch";
|
||||
import { parse } from "elementtree";
|
||||
import { writeFile } from "atomically";
|
||||
import { mkdir } from "fs/promises";
|
||||
import ecbDefaults from "./ecb-defaults.json" with { type: "json" };
|
||||
|
||||
async function btc() {
|
||||
const response = await fetch("https://api.blockchain.info/stats");
|
||||
console.log("Fetched", response.url, response.status, response.statusText);
|
||||
if (response.status != 200) {
|
||||
throw new Error(
|
||||
`Received ${response.status} ${response.statusText} from blockchain.info`,
|
||||
);
|
||||
}
|
||||
const json = await response.json();
|
||||
|
||||
const date = new Date(json.timestamp);
|
||||
|
||||
const defs = [
|
||||
{
|
||||
name: "BTC",
|
||||
doc: null,
|
||||
category: "currencies",
|
||||
type: "unit",
|
||||
expr: `price of bitcoin`,
|
||||
},
|
||||
{
|
||||
name: "bitcoin",
|
||||
doc: `Properties of the global Bitcoin network. Sourced from <https://blockchain.info>. Current as of ${date.toUTCString()}`,
|
||||
category: "currencies",
|
||||
type: "substance",
|
||||
symbol: null,
|
||||
properties: [
|
||||
{
|
||||
name: "price",
|
||||
doc: "Current market price of 1 BTC.",
|
||||
category: "currencies",
|
||||
inputName: "bitcoin",
|
||||
input: "1",
|
||||
outputName: "bitcoin",
|
||||
output: `${json.market_price_usd} USD`,
|
||||
},
|
||||
{
|
||||
name: "hashrate",
|
||||
doc: "Current hash rate of the global network",
|
||||
category: null,
|
||||
inputName: "hashrate",
|
||||
input: "1",
|
||||
outputName: "rate",
|
||||
output: `${json.hash_rate} 1e9 'hash'/sec`,
|
||||
},
|
||||
{
|
||||
name: "total",
|
||||
doc: "Total number of BTC in circulation.",
|
||||
category: null,
|
||||
inputName: "total",
|
||||
input: "1",
|
||||
outputName: "bitcoin",
|
||||
output: `${json.totalbc} / 1e8`,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return defs;
|
||||
}
|
||||
|
||||
async function ecb() {
|
||||
const response = await fetch(
|
||||
"https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml",
|
||||
);
|
||||
console.log("Fetched", response.url, response.status, response.statusText);
|
||||
if (response.status != 200) {
|
||||
throw new Error(
|
||||
`Received ${response.status} ${response.statusText} from ecb.europea.eu`,
|
||||
);
|
||||
}
|
||||
const body = await response.text();
|
||||
const doc = parse(body);
|
||||
const timestamp = doc.find(".//Cube[@time]")?.attrib.time || "unknown";
|
||||
|
||||
const desc = `Sourced from European Central Bank. Current as of ${timestamp}.`;
|
||||
|
||||
const defs = [];
|
||||
const seen = new Set();
|
||||
for (const element of doc.findall(".//Cube")) {
|
||||
const currency = element.attrib.currency;
|
||||
const rate = element.attrib.rate;
|
||||
if (currency && rate) {
|
||||
seen.add(currency);
|
||||
defs.push({
|
||||
name: currency,
|
||||
doc: desc,
|
||||
category: "currencies",
|
||||
type: "unit",
|
||||
expr: `(1 / ${rate}) EUR`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
defs.push({
|
||||
name: "HRK",
|
||||
doc: "Croatian Kuna. Pinned to Euro at a fixed rate since 2023-01-01.",
|
||||
category: "currencies",
|
||||
type: "unit",
|
||||
expr: "(1 / 7.5345) EUR",
|
||||
});
|
||||
|
||||
for (const [name, currency] of Object.entries(ecbDefaults)) {
|
||||
if (!seen.has(name)) {
|
||||
defs.push({
|
||||
name,
|
||||
doc: `Fetching live data failed. Fallback value provided from ${currency.source} on ${currency.time}.`,
|
||||
category: "currencies",
|
||||
type: "unit",
|
||||
expr: `(1 / ${currency.value}) EUR`,
|
||||
});
|
||||
}
|
||||
}
|
||||
return defs;
|
||||
}
|
||||
|
||||
const DATA_DIR = process.env.DATA_DIR || "data";
|
||||
export const currencyPath = `${DATA_DIR}/currency.json`;
|
||||
|
||||
export async function updateCurrency() {
|
||||
const [btcDefs, ecbDefs] = await Promise.all([btc(), ecb()]);
|
||||
const defs = [...btcDefs, ...ecbDefs];
|
||||
|
||||
await mkdir(DATA_DIR, { recursive: true });
|
||||
|
||||
await writeFile(currencyPath, JSON.stringify(defs, undefined, "\t"), {
|
||||
encoding: "utf8",
|
||||
});
|
||||
console.log("Successfully updated file.");
|
||||
}
|
||||
|
||||
updateCurrency();
|
19791
web/package-lock.json
generated
|
@ -1,53 +1,14 @@
|
|||
{
|
||||
"author": "Tiffany Bennett (https://tiffnix.com)",
|
||||
"name": "rink-web",
|
||||
"version": "0.6.3",
|
||||
"repository": "https://github.com/tiffany352/rink-rs",
|
||||
"homepage": "https://rinkcalc.app",
|
||||
"description": "Web interface to Rink",
|
||||
"license": "MPL-2.0",
|
||||
"scripts": {
|
||||
"dev": "sapper dev",
|
||||
"build": "sapper build",
|
||||
"export": "sapper export",
|
||||
"start": "node __sapper__/build",
|
||||
"cy:run": "cypress run",
|
||||
"cy:open": "cypress open",
|
||||
"test": "run-p --race dev cy:run"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^12.0.0",
|
||||
"@rollup/plugin-node-resolve": "^8.0.0",
|
||||
"@rollup/plugin-replace": "^2.2.0",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@types/elementtree": "^0.1.0",
|
||||
"@types/express": "^4.17.7",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@wasm-tool/rollup-plugin-rust": "^1.0.5",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"parcel-plugin-svelte": "^4.0.6",
|
||||
"parcel-plugin-wasm-pack": "^6.0.1",
|
||||
"rollup": "^2.3.4",
|
||||
"rollup-plugin-svelte": "^5.0.1",
|
||||
"rollup-plugin-terser": "^5.3.0",
|
||||
"rollup-plugin-typescript2": "^0.17.2",
|
||||
"sapper": "^0.27.16",
|
||||
"svelte": "^3.24.0",
|
||||
"svelte-preprocess": "^4.0.9",
|
||||
"tslib": "^2.0.0",
|
||||
"typescript": "^3.9.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"compression": "^1.7.1",
|
||||
"elementtree": "^0.1.7",
|
||||
"express": "^4.17.3",
|
||||
"node-fetch": "^2.6.1",
|
||||
"sirv": "^1.0.0",
|
||||
"atomically": "1.7.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 3 chrome versions",
|
||||
"last 3 firefox versions"
|
||||
]
|
||||
"type": "module",
|
||||
"author": "Tiffany Bennett (https://tiffnix.com)",
|
||||
"name": "rink-web-soupault",
|
||||
"version": "0.6.3",
|
||||
"repository": "https://github.com/tiffany352/rink-rs",
|
||||
"homepage": "https://rinkcalc.app",
|
||||
"description": "Static site generation for rinkcalc.app",
|
||||
"license": "MPL-2.0",
|
||||
"devDependencies": {
|
||||
"asciidoctor": "^3"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
|
|
92
web/plugins/deuglify.lua
Normal file
|
@ -0,0 +1,92 @@
|
|||
-- AsciiDoctor HTML fixing plugin for Soupault,
|
||||
-- written by Tiffany Bennett <https://tiffnix.com>
|
||||
--
|
||||
-- This work is licensed under CC BY-SA 4.0
|
||||
-- <https://creativecommons.org/licenses/by-sa/4.0/>
|
||||
--
|
||||
-- The idea with this plugin is to attempt to transform the HTML4-style
|
||||
-- output from AsciiDoctor into modern HTML5 using semantic tags like
|
||||
-- `<figure>`.
|
||||
|
||||
-- Inline all the section divs, they aren't really useful.
|
||||
local sects = HTML.select(page, "section.e-content>div")
|
||||
local index = 1
|
||||
while sects[index] do
|
||||
local sect = sects[index]
|
||||
local body = HTML.select_one(sect, ".sectionbody")
|
||||
HTML.unwrap(body)
|
||||
HTML.unwrap(sect)
|
||||
index = index + 1
|
||||
end
|
||||
|
||||
-- Adjust the <pre> tags to be more friendly to syntax highlighting.
|
||||
local pres = HTML.select(page, "pre.highlight")
|
||||
index = 1
|
||||
while pres[index] do
|
||||
local pre = pres[index]
|
||||
HTML.delete_attribute(pre, "class")
|
||||
local code = HTML.select_one(pre, "code")
|
||||
if code then
|
||||
HTML.set_attribute(code, "class", "hljs")
|
||||
end
|
||||
index = index + 1
|
||||
end
|
||||
|
||||
-- This unwraps some useless `<div><div class="content"></div></div>`.
|
||||
-- But sometimes there is also a `<div class="title">` in there, which
|
||||
-- should be transformed into a proper `<figure>` element.
|
||||
local divs = HTML.select(page, "section.e-content>div")
|
||||
index = 1
|
||||
while divs[index] do
|
||||
local div = divs[index]
|
||||
local title = HTML.select_one(div, ".title")
|
||||
local content = HTML.select_one(div, ".content")
|
||||
local class = HTML.get_attribute(div, "class")
|
||||
if title and content then
|
||||
HTML.set_tag_name(div, "figure")
|
||||
HTML.delete_attribute(div, "class")
|
||||
HTML.set_tag_name(title, "figcaption")
|
||||
HTML.set_attribute(content, "class", class)
|
||||
div = content
|
||||
end
|
||||
|
||||
if index == 1 and class == "paragraph" then
|
||||
local p = HTML.select_one(div, "p")
|
||||
HTML.set_attribute(p, "class", "p-summary")
|
||||
end
|
||||
|
||||
if class == "paragraph" or class == "imageblock" or class == "ulist" then
|
||||
HTML.unwrap(div)
|
||||
elseif class == "stemblock" then
|
||||
local content = HTML.select_one(div, ".content")
|
||||
if content then
|
||||
HTML.unwrap(content)
|
||||
end
|
||||
HTML.set_tag_name(div, "p")
|
||||
-- Add a class that allows stem blocks to be processed by katex.
|
||||
HTML.set_attribute(div, "class", "katex-block")
|
||||
elseif class == "listingblock" then
|
||||
local content = HTML.select_one(div, ".content")
|
||||
if content then
|
||||
HTML.unwrap(content)
|
||||
end
|
||||
HTML.unwrap(div)
|
||||
end
|
||||
index = index + 1
|
||||
end
|
||||
|
||||
-- There should never be a `<li><p></p></li>`, so unwrap them.
|
||||
local bad_ps = HTML.select(page, "li>p")
|
||||
index = 1
|
||||
while bad_ps[index] do
|
||||
HTML.unwrap(bad_ps[index])
|
||||
index = index + 1
|
||||
end
|
||||
|
||||
-- Same with `<td><p></p></td>`.
|
||||
bad_ps = HTML.select(page, "td>p")
|
||||
index = 1
|
||||
while bad_ps[index] do
|
||||
HTML.unwrap(bad_ps[index])
|
||||
index = index + 1
|
||||
end
|
141
web/plugins/fetch-releases.lua
Normal file
|
@ -0,0 +1,141 @@
|
|||
local feed_file = Sys.join_path(build_dir, "releases.json")
|
||||
local feed_url = "https://api.github.com/repos/tiffany352/rink-rs/releases"
|
||||
command = "cmark-gfm --smart -e autolink"
|
||||
|
||||
if not Sys.file_exists(feed_file) then
|
||||
Sys.run_program("curl " .. feed_url .. " -o " .. feed_file)
|
||||
end
|
||||
|
||||
local json = Sys.read_file(feed_file)
|
||||
local releases = JSON.from_string(json)
|
||||
|
||||
h_feed = HTML.select_one(page, ".h-feed")
|
||||
|
||||
function humanize_bytes(bytes)
|
||||
local value = bytes
|
||||
local suffix = "bytes"
|
||||
if bytes > 500000 then
|
||||
value = value / 1000000
|
||||
suffix = "MB"
|
||||
elseif bytes > 1500 then
|
||||
value = value / 1000
|
||||
suffix = "KB"
|
||||
end
|
||||
return format("%.2f %s", value, suffix)
|
||||
end
|
||||
|
||||
function build_downloads(assets)
|
||||
if not assets or not assets[1] then
|
||||
return nil
|
||||
end
|
||||
|
||||
local downloads = HTML.create_element("section")
|
||||
|
||||
HTML.append_child(downloads, HTML.create_element("h3", "Downloads"))
|
||||
local dl_ul = HTML.create_element("ul")
|
||||
j = 1
|
||||
while assets[j] do
|
||||
local asset = assets[j]
|
||||
local li = HTML.create_element("li")
|
||||
local a = HTML.create_element("a", asset.name)
|
||||
HTML.set_attribute(a, "href", asset.browser_download_url)
|
||||
HTML.set_attribute(a, "download", asset.name)
|
||||
HTML.append_child(li, a)
|
||||
local text = format(" (%s)", humanize_bytes(asset.size))
|
||||
HTML.append_child(li, HTML.create_text(text))
|
||||
HTML.append_child(dl_ul, li)
|
||||
j = j + 1
|
||||
end
|
||||
HTML.append_child(downloads, dl_ul)
|
||||
return downloads
|
||||
end
|
||||
|
||||
function build_description(markdown_text)
|
||||
-- run markdown processor
|
||||
local body_content = Sys.get_program_output(command, markdown_text)
|
||||
|
||||
-- match issue names like #123
|
||||
body_content = Sys.get_program_output(
|
||||
"sed -r 's,#([0-9]+),<a href=\"https://github.com/tiffany352/rink-rs/issues/\\1\">#\\1</a>,g'",
|
||||
body_content
|
||||
)
|
||||
|
||||
-- match username handles like @tiffany352
|
||||
body_content = Sys.get_program_output(
|
||||
"sed -r 's,@([a-zA-Z0-9_]+),<a href=\"https://github.com/\\1\">@\\1</a>,g'",
|
||||
body_content
|
||||
)
|
||||
|
||||
-- parse html & shrink headings
|
||||
local body = HTML.parse(body_content)
|
||||
local headers = HTML.select_all_of(body, { "h1", "h2" })
|
||||
local j = 1
|
||||
while headers[j] do
|
||||
HTML.set_tag_name(headers[j], "h3")
|
||||
j = j + 1
|
||||
end
|
||||
|
||||
-- shorten github links
|
||||
local links = HTML.select(body, "a")
|
||||
j = 1
|
||||
while links[j] do
|
||||
local repo = "https://github.com/tiffany352/rink-rs/"
|
||||
local text = HTML.inner_text(links[j])
|
||||
local issues = repo .. "issues"
|
||||
local pulls = repo .. "pull"
|
||||
if String.starts_with(text, issues) then
|
||||
text = "#" .. strsub(text, strlen(issues) + 2)
|
||||
local node = HTML.create_text(text)
|
||||
HTML.replace_content(links[j], node)
|
||||
elseif String.starts_with(text, repo .. "pull") then
|
||||
text = "#" .. strsub(text, strlen(pulls) + 2)
|
||||
local node = HTML.create_text(text)
|
||||
HTML.replace_content(links[j], node)
|
||||
end
|
||||
j = j + 1
|
||||
end
|
||||
|
||||
return body
|
||||
end
|
||||
|
||||
function add_release(release)
|
||||
local nice_published = Date.reformat(release.published_at, { "%Y-%m-%dT%H:%M:%S" }, "%B %d, %Y")
|
||||
local published = HTML.create_element("time", nice_published)
|
||||
HTML.set_attribute(published, "datetime", release.published_at)
|
||||
HTML.set_attribute(published, "class", "dt-published")
|
||||
|
||||
local meta = HTML.create_element("span")
|
||||
HTML.set_attribute(meta, "class", "meta")
|
||||
HTML.append_child(meta, published)
|
||||
|
||||
local content = HTML.create_element("section")
|
||||
local body = build_description(release.body)
|
||||
HTML.append_child(content, body)
|
||||
HTML.set_attribute(content, "class", "e-content")
|
||||
|
||||
local article = HTML.create_element("article")
|
||||
|
||||
local downloads = build_downloads(release.assets)
|
||||
|
||||
HTML.set_attribute(article, "class", "h-entry")
|
||||
HTML.append_child(article, HTML.create_element("h2", release.name))
|
||||
HTML.append_child(article, meta)
|
||||
HTML.append_child(article, content)
|
||||
if downloads ~= nil then
|
||||
HTML.append_child(article, downloads)
|
||||
end
|
||||
|
||||
HTML.append_child(h_feed, article)
|
||||
HTML.append_child(h_feed, HTML.create_element("hr"))
|
||||
end
|
||||
|
||||
local i = 1
|
||||
while releases[i] do
|
||||
local release = releases[i]
|
||||
|
||||
if not release.draft and not release.prerelease then
|
||||
add_release(release)
|
||||
end
|
||||
|
||||
i = i + 1
|
||||
end
|
85
web/plugins/opengraph.lua
Normal file
|
@ -0,0 +1,85 @@
|
|||
-- OpenGraph metadata plugin for Soupault,
|
||||
-- written by Tiffany Bennett <https://tiffnix.com>
|
||||
--
|
||||
-- This work is licensed under CC BY-SA 4.0
|
||||
-- <https://creativecommons.org/licenses/by-sa/4.0/>
|
||||
--
|
||||
-- If you have mf2 metadata most of this should just magically work.
|
||||
-- If you don't have mf2 metadata, I suggest you add it, because it'll
|
||||
-- be just as much work as trying to hack around its absence.
|
||||
--
|
||||
-- Rips the <h1> element for the og:title.
|
||||
-- Should be good enough in most cases.
|
||||
--
|
||||
-- Assumes that everything that isn't an index page is an article.
|
||||
-- This may or may not work for your usage.
|
||||
--
|
||||
-- Pulls `site_title` from the `[custom_options]` section for the
|
||||
-- og:site_name field.
|
||||
|
||||
site_title = soupault_config["custom_options"]["site_title"]
|
||||
|
||||
-- Creates a `<meta>` tag and puts it into `<head>`.
|
||||
function add_meta(property, content)
|
||||
if not content then return end
|
||||
|
||||
content = String.trim(content)
|
||||
content = Regex.replace_all(content, "\\s+", " ")
|
||||
|
||||
local head = HTML.select_one(page, "head")
|
||||
if not head then
|
||||
Log.error("No <head> element found")
|
||||
end
|
||||
|
||||
local meta = HTML.create_element("meta")
|
||||
HTML.set_attribute(meta, "content", content)
|
||||
HTML.set_attribute(meta, "property", property)
|
||||
HTML.append_child(head, meta)
|
||||
end
|
||||
|
||||
-- required metadata:
|
||||
local type = "website"
|
||||
if Sys.strip_extensions(Sys.basename(page_file)) ~= "index" then
|
||||
type = "article"
|
||||
end
|
||||
add_meta("og:type", type)
|
||||
|
||||
local title_elt = HTML.select_one(page, "h1")
|
||||
local title = title_elt and HTML.inner_text(title_elt)
|
||||
add_meta("og:title", title)
|
||||
|
||||
local image_elt = HTML.select_one(page, ".e-content img")
|
||||
local image = image_elt and HTML.get_attribute(image_elt, "src")
|
||||
add_meta("og:image", image)
|
||||
|
||||
local uid_elt = HTML.select_one(page, "span.u-uid")
|
||||
local uid = uid_elt and HTML.inner_text(uid_elt)
|
||||
add_meta("og:url", uid)
|
||||
|
||||
-- optional metadata:
|
||||
local desc_elt = HTML.select_one(page, "p.p-summary")
|
||||
local desc = desc_elt and HTML.inner_text(desc_elt)
|
||||
add_meta("og:description", desc)
|
||||
|
||||
add_meta("og:site_name", site_title)
|
||||
|
||||
-- article metadata:
|
||||
if type == "article" then
|
||||
local published_elt = HTML.select_one(page, "time.dt-published")
|
||||
local published = published_elt and HTML.get_attribute(published_elt, "datetime")
|
||||
published = published and Date.reformat(published, {"%Y-%m-%d"}, "%Y-%m-%dT%H:%M:%SZ")
|
||||
add_meta("article:published_time", published)
|
||||
|
||||
local updated_elt = HTML.select_one(page, "time.dt-updated")
|
||||
local updated = updated_elt and HTML.get_attribute(updated_elt, "datetime")
|
||||
updated = updated and Date.reformat(updated, {"%Y-%m-%d"}, "%Y-%m-%dT%H:%M:%SZ")
|
||||
add_meta("article:modified_time", updated)
|
||||
|
||||
local author_elt = HTML.select_one(page, ".h-card span.p-name")
|
||||
local author = author_elt and HTML.inner_text(author_elt)
|
||||
add_meta("article:author", author)
|
||||
|
||||
local section_elt = HTML.select_one(page, "#post-section")
|
||||
local section = section_elt and HTML.inner_text(section_elt)
|
||||
add_meta("article:section", section)
|
||||
end
|
47
web/plugins/relocate.lua
Normal file
|
@ -0,0 +1,47 @@
|
|||
-- Element translocation plugin for Soupault,
|
||||
-- written by Tiffany Bennett <https://tiffnix.com>
|
||||
--
|
||||
-- This work is licensed under CC BY-SA 4.0
|
||||
-- <https://creativecommons.org/licenses/by-sa/4.0/>
|
||||
--
|
||||
-- This allows you to pluck an element from somewhere in the page and
|
||||
-- move it to another part of the page.
|
||||
|
||||
-- The selector to use to find the element to be moved.
|
||||
selector = config["selector"]
|
||||
-- The selector of where the element should be moved to.
|
||||
new_parent = config["new_parent"]
|
||||
-- https://soupault.app/reference-manual/#glossary-action
|
||||
action = config["action"]
|
||||
|
||||
if not selector or not new_parent or not action then
|
||||
Log.error("selector, new_parent, and action configurations are required")
|
||||
end
|
||||
|
||||
element = HTML.select_one(page, selector)
|
||||
new_parent_elt = HTML.select_one(page, new_parent)
|
||||
|
||||
if not element then
|
||||
Log.info("Selector "..selector.." didn't match anything")
|
||||
return
|
||||
end
|
||||
if not new_parent_elt then
|
||||
Log.info("Selector "..new_parent.." didn't match anything")
|
||||
return
|
||||
end
|
||||
|
||||
if action == "prepend_child" then
|
||||
HTML.prepend_child(new_parent_elt, element)
|
||||
elseif action == "append_child" then
|
||||
HTML.append_child(new_parent_elt, element)
|
||||
elseif action == "insert_before" then
|
||||
HTML.insert_before(new_parent_elt, element)
|
||||
elseif action == "insert_after" then
|
||||
HTML.insert_after(new_parent_elt, element)
|
||||
elseif action == "replace_content" then
|
||||
HTML.replace_content(new_parent_elt, element)
|
||||
elseif action == "replace_element" then
|
||||
HTML.insert_after(new_parent_elt, element)
|
||||
else
|
||||
Log.error("Unknown action "..action)
|
||||
end
|
31
web/plugins/vite.lua
Normal file
|
@ -0,0 +1,31 @@
|
|||
Log.info("Running vite build")
|
||||
Sys.run_program("cd repl; npm run build; cd ..")
|
||||
|
||||
local immutable = Sys.join_path(build_dir, "immutable")
|
||||
Sys.mkdir(immutable)
|
||||
Sys.run_program("cp repl/dist/immutable/*.js " .. immutable)
|
||||
|
||||
-- use hashed filenames so that extremely aggressive caching can be set.
|
||||
-- no idea why wasm-pack doesn't do this by default.
|
||||
local wasm_path = "repl/dist/assets/rink_js_bg.wasm"
|
||||
local wasm = Sys.read_file(wasm_path)
|
||||
local long_hash = Digest.sha256(wasm)
|
||||
local hash = strsub(long_hash, 1, 8)
|
||||
local wasm_out = format("immutable/rink.%s.wasm", hash)
|
||||
Sys.write_file(Sys.join_path(build_dir, wasm_out), wasm)
|
||||
|
||||
local files = Sys.list_dir("repl/dist/immutable")
|
||||
local i = 1
|
||||
while files[i] do
|
||||
local path = files[i]
|
||||
local file = Sys.basename(path)
|
||||
if String.starts_with(file, "index") then
|
||||
local script = HTML.create_element("script")
|
||||
HTML.set_attribute(script, "src", "/immutable/" .. file)
|
||||
HTML.set_attribute(script, "data-wasm", "/" .. wasm_out)
|
||||
HTML.set_attribute(script, "async", "")
|
||||
local head = HTML.select_one(page, "head")
|
||||
HTML.append_child(head, script)
|
||||
end
|
||||
i = i + 1
|
||||
end
|
25
web/repl/index.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<script type="module" src="/src/index.ts"></script>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Rink</h1>
|
||||
<noscript>
|
||||
Running rink in the browser requires Javascript enabled. Sorry!
|
||||
</noscript>
|
||||
<div id="rink-outputs"></div>
|
||||
<form id="rink-query">
|
||||
<input type="text" id="query" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
712
web/repl/package-lock.json
generated
Normal file
|
@ -0,0 +1,712 @@
|
|||
{
|
||||
"name": "npm-crate-example",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "npm-crate-example",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"vite": "^2.6.14",
|
||||
"vite-plugin-wasm-pack": "^0.1.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz",
|
||||
"integrity": "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.54.tgz",
|
||||
"integrity": "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/linux-loong64": "0.14.54",
|
||||
"esbuild-android-64": "0.14.54",
|
||||
"esbuild-android-arm64": "0.14.54",
|
||||
"esbuild-darwin-64": "0.14.54",
|
||||
"esbuild-darwin-arm64": "0.14.54",
|
||||
"esbuild-freebsd-64": "0.14.54",
|
||||
"esbuild-freebsd-arm64": "0.14.54",
|
||||
"esbuild-linux-32": "0.14.54",
|
||||
"esbuild-linux-64": "0.14.54",
|
||||
"esbuild-linux-arm": "0.14.54",
|
||||
"esbuild-linux-arm64": "0.14.54",
|
||||
"esbuild-linux-mips64le": "0.14.54",
|
||||
"esbuild-linux-ppc64le": "0.14.54",
|
||||
"esbuild-linux-riscv64": "0.14.54",
|
||||
"esbuild-linux-s390x": "0.14.54",
|
||||
"esbuild-netbsd-64": "0.14.54",
|
||||
"esbuild-openbsd-64": "0.14.54",
|
||||
"esbuild-sunos-64": "0.14.54",
|
||||
"esbuild-windows-32": "0.14.54",
|
||||
"esbuild-windows-64": "0.14.54",
|
||||
"esbuild-windows-arm64": "0.14.54"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-android-64": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz",
|
||||
"integrity": "sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-android-arm64": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz",
|
||||
"integrity": "sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-darwin-64": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz",
|
||||
"integrity": "sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-darwin-arm64": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz",
|
||||
"integrity": "sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-freebsd-64": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz",
|
||||
"integrity": "sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-freebsd-arm64": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz",
|
||||
"integrity": "sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-32": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz",
|
||||
"integrity": "sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-64": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz",
|
||||
"integrity": "sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-arm": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz",
|
||||
"integrity": "sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-arm64": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz",
|
||||
"integrity": "sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-mips64le": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz",
|
||||
"integrity": "sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-ppc64le": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz",
|
||||
"integrity": "sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-riscv64": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz",
|
||||
"integrity": "sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-s390x": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz",
|
||||
"integrity": "sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-netbsd-64": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz",
|
||||
"integrity": "sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-openbsd-64": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz",
|
||||
"integrity": "sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-sunos-64": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz",
|
||||
"integrity": "sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-32": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz",
|
||||
"integrity": "sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-64": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz",
|
||||
"integrity": "sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-arm64": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz",
|
||||
"integrity": "sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.13.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
|
||||
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/narrowing": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/narrowing/-/narrowing-1.5.0.tgz",
|
||||
"integrity": "sha512-DUu4XdKgkfAPTAL28k79pdnshDE2W5T24QAnidSPo2F/W1TX6CjNzmEeXQfE5O1lxQvC0GYI6ZRDsLcyzugEYA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.38",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
|
||||
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.8",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.13.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "2.77.3",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.77.3.tgz",
|
||||
"integrity": "sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
||||
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "2.9.18",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-2.9.18.tgz",
|
||||
"integrity": "sha512-sAOqI5wNM9QvSEE70W3UGMdT8cyEn0+PmJMTFvTB8wB0YbYUWw3gUbY62AOyrXosGieF2htmeLATvNxpv/zNyQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.14.27",
|
||||
"postcss": "^8.4.13",
|
||||
"resolve": "^1.22.0",
|
||||
"rollup": ">=2.59.0 <2.78.0"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.2.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"less": "*",
|
||||
"sass": "*",
|
||||
"stylus": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"less": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
},
|
||||
"stylus": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-wasm-pack": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-wasm-pack/-/vite-plugin-wasm-pack-0.1.12.tgz",
|
||||
"integrity": "sha512-WliYvQp9HXluir4OKGbngkcKxtYtifU11cqLurRRJGsl770Sjr1iIkp5RuvU3IC1poT4A57Z2/YgAKI2Skm7ZA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.2",
|
||||
"fs-extra": "^10.0.0",
|
||||
"narrowing": "^1.4.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
web/repl/package.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "rink-web-repl",
|
||||
"version": "1.0.0",
|
||||
"main": "index.ts",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"vite": "^2.6.14",
|
||||
"vite-plugin-wasm-pack": "^0.1.9"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
308
web/repl/src/index.ts
Normal file
|
@ -0,0 +1,308 @@
|
|||
import type { ExecuteRes, HelloReq, HelloRes, RinkResponse, SpanOrList } from "./proto";
|
||||
|
||||
// Taken from https://stackoverflow.com/a/3809435
|
||||
const urlRegex =
|
||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g;
|
||||
const powRegex = /\^(\-?\d+)/g;
|
||||
|
||||
function buildInline(parent: HTMLElement, text: string) {
|
||||
// escape any html tags
|
||||
parent.innerText = text;
|
||||
text = parent.innerHTML;
|
||||
// apply the regexes
|
||||
text = text.replace(
|
||||
urlRegex,
|
||||
(match) => `<a href="${match}" rel="nofollow">${match}</a>`,
|
||||
);
|
||||
text = text.replace(powRegex, (_match, rest) => `<sup>${rest}</sup>`);
|
||||
parent.innerHTML = text;
|
||||
}
|
||||
|
||||
const dateFmt = new Intl.DateTimeFormat(undefined, {
|
||||
dateStyle: "long",
|
||||
timeStyle: "long",
|
||||
});
|
||||
|
||||
function buildHtml(tokens: [SpanOrList], parent: HTMLElement) {
|
||||
let ul: HTMLUListElement | null = null;
|
||||
let cur: HTMLElement = parent;
|
||||
for (const token of tokens) {
|
||||
if (token.type == "list") {
|
||||
buildHtml(token.children, cur);
|
||||
} else if (token.fmt == "pow") {
|
||||
let span = document.createElement("span");
|
||||
span.classList.add(`hl-hidden`);
|
||||
span.innerText = "^";
|
||||
|
||||
let sup = document.createElement("sup");
|
||||
let text = token.text.replace(/^\^/, "");
|
||||
sup.append(span, text);
|
||||
sup.prepend(span);
|
||||
|
||||
cur.appendChild(sup);
|
||||
} else if (token.fmt == "list_begin") {
|
||||
ul = document.createElement("ul");
|
||||
parent.appendChild(ul);
|
||||
let li = document.createElement("li");
|
||||
cur = li;
|
||||
ul.appendChild(li);
|
||||
} else if (token.fmt == "list_sep" && ul) {
|
||||
let li = document.createElement("li");
|
||||
cur = li;
|
||||
ul.appendChild(li);
|
||||
} else if (token.fmt == "date_time") {
|
||||
let time = document.createElement("time");
|
||||
let date = new Date(token.text);
|
||||
time.setAttribute("datetime", date.toISOString());
|
||||
time.innerText = dateFmt.format(date);
|
||||
cur.appendChild(time);
|
||||
} else {
|
||||
let span = document.createElement("span");
|
||||
span.classList.add(`hl-${token.fmt.replace("_", "-")}`);
|
||||
buildInline(span, token.text);
|
||||
cur.appendChild(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let rinkDiv: HTMLElement = document.querySelector("#rink-outputs")!;
|
||||
let form: HTMLFormElement = document.querySelector("#rink-query")!;
|
||||
let textEntry: HTMLInputElement = document.querySelector("#query")!;
|
||||
|
||||
function pushError(error: Error | string) {
|
||||
let message: string;
|
||||
if (error instanceof Error) {
|
||||
message = error.message;
|
||||
} else {
|
||||
message = error;
|
||||
}
|
||||
|
||||
const element = document.createElement("p");
|
||||
element.classList.add("hl-error");
|
||||
element.innerText = message;
|
||||
rinkDiv.appendChild(element);
|
||||
}
|
||||
|
||||
const currency = fetch("/data/currency.json").then((res) => res.text());
|
||||
|
||||
// The only way to fetch with progress is to use the old fashioned XMLHttpRequest API.
|
||||
const wasmBlob = new Promise<ArrayBuffer>((resolve, reject) => {
|
||||
const label = document.createElement("label");
|
||||
label.htmlFor = "rink-dl";
|
||||
label.innerText = "Downloading rink.wasm...";
|
||||
const progress = document.createElement("progress");
|
||||
progress.id = "rink-dl";
|
||||
progress.value = 0;
|
||||
const cancel = document.createElement("button");
|
||||
cancel.innerText = "Cancel";
|
||||
|
||||
rinkDiv.appendChild(label);
|
||||
rinkDiv.appendChild(progress);
|
||||
rinkDiv.appendChild(cancel);
|
||||
|
||||
const req = new XMLHttpRequest();
|
||||
req.open("GET", document.currentScript!.dataset.wasm!);
|
||||
req.responseType = "arraybuffer";
|
||||
|
||||
const onFinish = () => {
|
||||
label.remove();
|
||||
progress.remove();
|
||||
cancel.remove();
|
||||
};
|
||||
|
||||
req.onprogress = (event) => {
|
||||
let total = event.total;
|
||||
// ugly hack because for some reason browsers don't report correct totals
|
||||
const length = req.getResponseHeader("Content-Length");
|
||||
if (!event.lengthComputable && length) {
|
||||
total = parseInt(length);
|
||||
}
|
||||
progress.value = event.loaded;
|
||||
progress.max = total;
|
||||
const totalKb = Math.floor(total / 1000).toString();
|
||||
const loadedKb = Math.floor(event.loaded / 1000)
|
||||
.toString()
|
||||
.padStart(totalKb.length, "0");
|
||||
label.innerHTML = `Downloading rink.wasm... <code>${loadedKb} / ${totalKb}</code> kB`;
|
||||
};
|
||||
req.onload = (_event) => {
|
||||
onFinish();
|
||||
const buffer: ArrayBuffer = req.response;
|
||||
if (buffer) {
|
||||
resolve(buffer);
|
||||
} else {
|
||||
reject("Unable to get binary data from request");
|
||||
}
|
||||
};
|
||||
req.onerror = (_event) => {
|
||||
onFinish();
|
||||
reject("Download failed");
|
||||
};
|
||||
req.onabort = (_event) => {
|
||||
onFinish();
|
||||
reject("Download aborted");
|
||||
};
|
||||
|
||||
cancel.onclick = () => {
|
||||
req.abort();
|
||||
};
|
||||
|
||||
req.send();
|
||||
});
|
||||
|
||||
let wasmBuffer: ArrayBuffer | null = null;
|
||||
let currencyData: string | null = null;
|
||||
let worker: Worker | null = null;
|
||||
function startWorker() {
|
||||
worker = new Worker(new URL('./worker.ts', import.meta.url));
|
||||
worker.postMessage({
|
||||
type: "hello",
|
||||
buffer: wasmBuffer,
|
||||
currency: currencyData,
|
||||
} as HelloReq);
|
||||
}
|
||||
|
||||
Promise.all([wasmBlob, currency]).then(([buffer, currencyDataRes]) => {
|
||||
wasmBuffer = buffer;
|
||||
currencyData = currencyDataRes;
|
||||
startWorker();
|
||||
|
||||
function expectMessage(type: string, id: number | null): Promise<RinkResponse> {
|
||||
return new Promise((resolve, _reject) => {
|
||||
const handler = (event: MessageEvent<RinkResponse>) => {
|
||||
const msg = event.data;
|
||||
if (msg.type == type && (!id || (msg as any).id == id)) {
|
||||
worker!.removeEventListener("message", handler);
|
||||
resolve(msg);
|
||||
}
|
||||
};
|
||||
worker!.addEventListener("message", handler);
|
||||
})
|
||||
}
|
||||
|
||||
let queryCounter = 1;
|
||||
function requestExecute(query: string): Promise<[SpanOrList]> {
|
||||
const id = queryCounter++;
|
||||
worker!.postMessage({
|
||||
type: "execute",
|
||||
id,
|
||||
query,
|
||||
})
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
worker!.terminate();
|
||||
startWorker();
|
||||
reject("Terminated after 5 seconds");
|
||||
}, 5000);
|
||||
expectMessage("execute", id).then((msg) => {
|
||||
clearTimeout(timeoutId);
|
||||
msg = msg as ExecuteRes;
|
||||
if (msg.status == "success")
|
||||
resolve(msg.tokens);
|
||||
else
|
||||
reject(msg.message);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
let history = JSON.parse(
|
||||
window.localStorage.getItem("rink-history") || "[]",
|
||||
);
|
||||
let historyIndex = history.length;
|
||||
|
||||
async function execute(queryString: string) {
|
||||
let quote = document.createElement("blockquote");
|
||||
quote.innerText = queryString;
|
||||
let permalink = document.createElement("a");
|
||||
permalink.href = `${location.origin}/?q=${queryString}`;
|
||||
permalink.text = "#";
|
||||
quote.appendChild(permalink);
|
||||
rinkDiv.appendChild(quote);
|
||||
|
||||
let p = document.createElement("p");
|
||||
p.innerHTML = "<progress></progress>";
|
||||
rinkDiv.appendChild(p);
|
||||
|
||||
try {
|
||||
const tokens = await requestExecute(queryString);
|
||||
console.log("formatting tokens: ", tokens);
|
||||
|
||||
p.innerHTML = "";
|
||||
buildHtml(tokens, p);
|
||||
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
|
||||
// prevent duplicates in history
|
||||
history = history.filter(
|
||||
(query: string) => query != queryString,
|
||||
);
|
||||
history.push(queryString);
|
||||
// keep history from becoming too long
|
||||
if (history.length > 200) history.shift();
|
||||
historyIndex = history.length;
|
||||
window.localStorage.setItem(
|
||||
"rink-history",
|
||||
JSON.stringify(history),
|
||||
);
|
||||
} catch (error) {
|
||||
pushError(error);
|
||||
p.remove();
|
||||
}
|
||||
}
|
||||
|
||||
async function on_hello(msg: HelloRes) {
|
||||
let h1 = document.querySelector("h1");
|
||||
if (h1) {
|
||||
h1.innerText = `Rink ${msg.version}`;
|
||||
}
|
||||
// clear the loading message
|
||||
textEntry.placeholder = "Enter a query, like `3 feet to meters`";
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const queries = urlParams.getAll("q");
|
||||
for (const q of queries) {
|
||||
await execute(q);
|
||||
}
|
||||
}
|
||||
|
||||
expectMessage("hello", null).then((res) => on_hello(res as HelloRes)).catch((error) => pushError(error));
|
||||
|
||||
form.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
const text = textEntry.value;
|
||||
textEntry.value = "";
|
||||
execute(text);
|
||||
});
|
||||
|
||||
textEntry.addEventListener("keydown", (event) => {
|
||||
if (event instanceof KeyboardEvent && event.key == "ArrowUp") {
|
||||
event.preventDefault();
|
||||
historyIndex--;
|
||||
if (historyIndex < 0) historyIndex = 0;
|
||||
textEntry.value = history[historyIndex];
|
||||
} else if (
|
||||
event instanceof KeyboardEvent &&
|
||||
event.key == "ArrowDown"
|
||||
) {
|
||||
event.preventDefault();
|
||||
historyIndex++;
|
||||
if (historyIndex > history.length)
|
||||
historyIndex = history.length;
|
||||
if (history[historyIndex])
|
||||
textEntry.value = history[historyIndex];
|
||||
else textEntry.value = "";
|
||||
}
|
||||
});
|
||||
}).catch((error) => {
|
||||
pushError(error);
|
||||
textEntry.placeholder = "Loading failed.";
|
||||
textEntry.disabled = true;
|
||||
});
|
||||
|
||||
// unregister any service workers left over from previous versions of the site
|
||||
navigator.serviceWorker.getRegistrations().then((registrations) => {
|
||||
for (const registration of registrations) {
|
||||
console.log("unregistering service worker", registration);
|
||||
registration.unregister();
|
||||
}
|
||||
});
|
57
web/repl/src/proto.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
export type RinkRequest = HelloReq | ExecuteReq;
|
||||
|
||||
export type HelloReq = {
|
||||
type: "hello",
|
||||
buffer: ArrayBuffer,
|
||||
currency?: string,
|
||||
}
|
||||
|
||||
export type ExecuteReq = {
|
||||
type: "execute",
|
||||
id: number,
|
||||
query: string,
|
||||
}
|
||||
|
||||
export type RinkResponse = HelloRes | ExecuteRes;
|
||||
|
||||
export type HelloRes = {
|
||||
type: "hello",
|
||||
version: string,
|
||||
};
|
||||
|
||||
export type TokenType =
|
||||
| "plain"
|
||||
| "error"
|
||||
| "unit"
|
||||
| "quantity"
|
||||
| "number"
|
||||
| "user_input"
|
||||
| "list_begin"
|
||||
| "list_sep"
|
||||
| "doc_string"
|
||||
| "pow"
|
||||
| "prop_name"
|
||||
| "date_time";
|
||||
|
||||
export type Token = {
|
||||
type: "span";
|
||||
text: string;
|
||||
fmt: TokenType;
|
||||
};
|
||||
export type TokenList = {
|
||||
type: "list";
|
||||
children: [SpanOrList];
|
||||
};
|
||||
export type SpanOrList = Token | TokenList;
|
||||
|
||||
export type ExecuteRes = {
|
||||
type: "execute",
|
||||
id: number,
|
||||
status: "success",
|
||||
tokens: [SpanOrList],
|
||||
} | {
|
||||
type: "execute",
|
||||
id: number,
|
||||
status: "error",
|
||||
message: string,
|
||||
};
|
45
web/repl/src/worker.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
import init, * as Rink from "rink-js";
|
||||
import type { ExecuteRes, HelloRes, RinkRequest } from "./proto";
|
||||
|
||||
let ctx: Rink.Context | null = null;
|
||||
onmessage = (event: MessageEvent<RinkRequest>) => {
|
||||
const msg = event.data;
|
||||
console.log("worker recv: ", msg);
|
||||
if (msg.type == "hello") {
|
||||
init(msg.buffer).then((_rink) => {
|
||||
ctx = new Rink.Context();
|
||||
ctx.setSavePreviousResult(true);
|
||||
if (msg.currency)
|
||||
ctx.loadCurrency(msg.currency);
|
||||
const response: HelloRes = {
|
||||
type: "hello",
|
||||
version: Rink.version(),
|
||||
};
|
||||
console.log("worker send: ", response);
|
||||
postMessage(response);
|
||||
});
|
||||
} else if (msg.type == "execute") {
|
||||
let response: ExecuteRes;
|
||||
if (!ctx) {
|
||||
response = {
|
||||
type: "execute",
|
||||
status: "error",
|
||||
id: msg.id,
|
||||
message: "execute message sent before hello message"
|
||||
};
|
||||
} else {
|
||||
const query = new Rink.Query(msg.query);
|
||||
ctx.setTime(new Date());
|
||||
const tokens = ctx.eval_tokens(query);
|
||||
|
||||
response = {
|
||||
type: "execute",
|
||||
status: "success",
|
||||
id: msg.id,
|
||||
tokens,
|
||||
};
|
||||
}
|
||||
console.log("worker send: ", response);
|
||||
postMessage(response);
|
||||
}
|
||||
}
|
5
web/repl/tsconfig.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": "es2017"
|
||||
}
|
||||
}
|
10
web/repl/vite.config.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { defineConfig } from "vite";
|
||||
import wasmPack from "vite-plugin-wasm-pack";
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
minify: false,
|
||||
assetsDir: "immutable",
|
||||
},
|
||||
plugins: [wasmPack("../../rink-js")],
|
||||
});
|
|
@ -1,115 +0,0 @@
|
|||
import resolve from "@rollup/plugin-node-resolve";
|
||||
import replace from "@rollup/plugin-replace";
|
||||
import commonjs from "@rollup/plugin-commonjs";
|
||||
import svelte from "rollup-plugin-svelte";
|
||||
import typescript from "rollup-plugin-typescript2";
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
import rust from "@wasm-tool/rollup-plugin-rust";
|
||||
import sveltePreprocess from "svelte-preprocess";
|
||||
import config from "sapper/config/rollup.js";
|
||||
import pkg from "./package.json";
|
||||
import path from "path";
|
||||
import json from "@rollup/plugin-json";
|
||||
|
||||
const mode = process.env.NODE_ENV;
|
||||
const dev = mode === "development";
|
||||
|
||||
const onwarn = (warning, onwarn) =>
|
||||
(warning.code === "CIRCULAR_DEPENDENCY" &&
|
||||
/[/\\]@sapper[/\\]/.test(warning.message)) ||
|
||||
onwarn(warning);
|
||||
|
||||
export default {
|
||||
client: {
|
||||
input: config.client.input(),
|
||||
output: config.client.output(),
|
||||
plugins: [
|
||||
rust({
|
||||
serverPath: "/client/",
|
||||
debug: false,
|
||||
}),
|
||||
typescript({
|
||||
typescript: require("typescript"),
|
||||
}),
|
||||
json(),
|
||||
replace({
|
||||
"process.browser": true,
|
||||
"process.env.NODE_ENV": JSON.stringify(mode),
|
||||
}),
|
||||
svelte({
|
||||
dev,
|
||||
hydratable: true,
|
||||
emitCss: true,
|
||||
preprocess: sveltePreprocess(),
|
||||
}),
|
||||
resolve({
|
||||
browser: true,
|
||||
dedupe: ["svelte"],
|
||||
}),
|
||||
commonjs(),
|
||||
|
||||
!dev &&
|
||||
terser({
|
||||
module: true,
|
||||
}),
|
||||
],
|
||||
|
||||
preserveEntrySignatures: false,
|
||||
onwarn,
|
||||
},
|
||||
|
||||
server: {
|
||||
input: config.server.input(),
|
||||
output: config.server.output(),
|
||||
plugins: [
|
||||
rust({
|
||||
nodejs: true,
|
||||
serverPath:
|
||||
path
|
||||
.relative(process.cwd(), config.server.output().dir)
|
||||
.replace("\\", "/") + "/",
|
||||
debug: false,
|
||||
}),
|
||||
typescript({
|
||||
typescript: require("typescript"),
|
||||
}),
|
||||
json(),
|
||||
replace({
|
||||
"process.browser": false,
|
||||
"process.env.NODE_ENV": JSON.stringify(mode),
|
||||
}),
|
||||
svelte({
|
||||
generate: "ssr",
|
||||
dev,
|
||||
preprocess: sveltePreprocess(),
|
||||
}),
|
||||
resolve({
|
||||
dedupe: ["svelte"],
|
||||
}),
|
||||
commonjs(),
|
||||
],
|
||||
external: Object.keys(pkg.dependencies).concat(
|
||||
require("module").builtinModules
|
||||
),
|
||||
|
||||
preserveEntrySignatures: "strict",
|
||||
onwarn,
|
||||
},
|
||||
|
||||
serviceworker: {
|
||||
input: config.serviceworker.input(),
|
||||
output: config.serviceworker.output(),
|
||||
plugins: [
|
||||
resolve(),
|
||||
replace({
|
||||
"process.browser": true,
|
||||
"process.env.NODE_ENV": JSON.stringify(mode),
|
||||
}),
|
||||
commonjs(),
|
||||
!dev && terser(),
|
||||
],
|
||||
|
||||
preserveEntrySignatures: false,
|
||||
onwarn,
|
||||
},
|
||||
};
|
102
web/scripts/asciidoc.js
Normal file
|
@ -0,0 +1,102 @@
|
|||
// AsciiDoctor preprocessor for Soupault,
|
||||
// written by Tiffany Bennett <https://tiffnix.com>
|
||||
//
|
||||
// This work is licensed under CC BY-SA 4.0
|
||||
// <https://creativecommons.org/licenses/by-sa/4.0/>
|
||||
//
|
||||
// This version has katex handling removed.
|
||||
//
|
||||
// Inserts extra metadata into the page using mf2 metadata. This is much
|
||||
// more information than is included when using `asciidoctor
|
||||
// --embedded`.
|
||||
|
||||
import Asciidoctor from "asciidoctor";
|
||||
|
||||
const authors = {
|
||||
Tiffany: {
|
||||
url: "https://tiffnix.com",
|
||||
pfp: "/images/profile-pic.png",
|
||||
},
|
||||
};
|
||||
|
||||
let asciidoctor = Asciidoctor();
|
||||
let registry = asciidoctor.Extensions.create();
|
||||
let options = {
|
||||
extension_registry: registry,
|
||||
// Turn off section IDs, because I use soupault to generate them
|
||||
// instead.
|
||||
attributes: "sectids!",
|
||||
safe: "unsafe",
|
||||
};
|
||||
|
||||
let path = process.argv[2];
|
||||
let doc = asciidoctor.loadFile(path, options);
|
||||
|
||||
let output = [];
|
||||
|
||||
// .convert() doesn't include h1, so it's added here, with the mf2
|
||||
// p-name tag.
|
||||
if (doc.getTitle()) {
|
||||
output.push(`<h1 class="p-name">${doc.getTitle()}</h1>`);
|
||||
}
|
||||
|
||||
let meta = [];
|
||||
|
||||
// Generate an mf2 h-card for the author
|
||||
if (doc.getAuthor() != "") {
|
||||
let author = authors[doc.getAuthor()];
|
||||
if (author) {
|
||||
meta.push(
|
||||
`<span class="h-card">
|
||||
<img class="u-photo" src="${author.pfp}" alt="" />
|
||||
<a class="p-name u-url p-author" rel="me" href="${author.url}">${doc.getAuthor()}</a>
|
||||
</span>`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// I do a nerd thing of rendering dates like `april 03, 2024` (in
|
||||
// lowercase specifically). You might want to adjust it to your own
|
||||
// tastes.
|
||||
let format = new Intl.DateTimeFormat("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "2-digit",
|
||||
});
|
||||
|
||||
function timeTag(date, klass) {
|
||||
let fmt = format.format(new Date(date)).toLowerCase();
|
||||
return `<time class="${klass}" datetime="${date}">${fmt}</time>`;
|
||||
}
|
||||
|
||||
let published = doc.getAttribute("published");
|
||||
if (published) {
|
||||
meta.push(timeTag(published, "dt-published"));
|
||||
}
|
||||
|
||||
let revision = doc.getRevisionDate();
|
||||
if (revision && published != revision) {
|
||||
let revHtml = timeTag(revision, "dt-updated");
|
||||
meta.push(`updated ${revHtml}`);
|
||||
}
|
||||
|
||||
if (doc.hasAttribute("section")) {
|
||||
let sect = doc.getAttribute("section");
|
||||
meta.push(`<span class="p-category" id="post-section">${sect}</span>`);
|
||||
}
|
||||
|
||||
// Meta elements are strung together with dots.
|
||||
// Aesthetic choice, you might want to change it.
|
||||
if (meta.length > 0 || doc.hasAttribute("uid")) {
|
||||
let uidHtml = "";
|
||||
if (doc.hasAttribute("uid")) {
|
||||
let uid = doc.getAttribute("uid");
|
||||
uidHtml = `<span class="u-uid">${uid}</span>`;
|
||||
}
|
||||
|
||||
output.push(`<span class="meta">${meta.join(" • ")}${uidHtml}</span>`);
|
||||
}
|
||||
|
||||
let html = doc.convert(options);
|
||||
output.push(html);
|
||||
console.log(output.join(""));
|
82
web/site/about.adoc
Normal file
|
@ -0,0 +1,82 @@
|
|||
= Rink
|
||||
|
||||
Rink is an open source unit-aware calculator. It can be used for physics
|
||||
and engineering calculations, as well as dimensionality analysis.
|
||||
|
||||
Rink supports most systems of measurements including SI, CGS, natural,
|
||||
international customary, US customary, UK customary, as well as
|
||||
historical measurements. In addition, Rink supports currency
|
||||
conversions.
|
||||
|
||||
== Features
|
||||
|
||||
* Arbitrary precision math
|
||||
* Shows SI physical quantities
|
||||
* Finds applicable SI derived units automatically
|
||||
* Detailed error messages
|
||||
* Helps with dimensionality analysis, such as by offering unit
|
||||
factorizations and finding units for quantities.
|
||||
* Temperature conversions
|
||||
|
||||
== Install
|
||||
|
||||
The most common version of Rink is the command line interface `rink`.
|
||||
|
||||
You can download it from link:/releases[Releases], or install it through
|
||||
your package manager:
|
||||
|
||||
[cols="1,3"]
|
||||
|===
|
||||
| Source | Command
|
||||
|
||||
| Cargo
|
||||
| `cargo install rink`
|
||||
|
||||
| Pacman
|
||||
| `pacman -S rink`
|
||||
|
||||
| Nix
|
||||
| `nix-env -i rink`
|
||||
|
||||
| Scoop
|
||||
| `scoop install rink`
|
||||
|===
|
||||
|
||||
== Examples
|
||||
|
||||
:elec: https://www.eia.gov/electricity/monthly/epm_table_grapher.php?t=epmt_5_6_a
|
||||
|
||||
How much does it cost to run my computer each year? Say it uses 100
|
||||
watts for 4 hours per day, and use the {elec}[US average electricity
|
||||
cost].
|
||||
|
||||
> 0.1545$/kWh * 100W * (4 hours / day) to $/year
|
||||
approx. 22.57196 USD / tropicalyear
|
||||
|
||||
If you made a solid sphere of gold the size of the moon, what would the
|
||||
surface gravity be?
|
||||
|
||||
> volume of moon * (19.283 g/cm^3) * G / (radius of moon)^2
|
||||
approx. 9.365338 meter / second^2 (acceleration)
|
||||
> ans to gravity
|
||||
approx. 0.9549987 gravity (acceleration)
|
||||
|
||||
:eff: https://www.bts.gov/content/average-fuel-efficiency-us-passenger-cars-and-light-trucks
|
||||
|
||||
Ever heard someone joke about Americans measuring fuel efficiency as
|
||||
rods per hogshead? Let's try with the {eff}[average US car fuel
|
||||
efficiency].
|
||||
|
||||
> 9.4 km/l to mpg
|
||||
approx. 22.11017 mpg (fuel_efficiency)
|
||||
> 9.4 km/l to rods per hogshead
|
||||
approx. 445741.0 rod / ushogshead (fuel_efficiency)
|
||||
|
||||
And then you wonder, wait, what even are these units anyway?
|
||||
|
||||
> hogshead
|
||||
Definition: ushogshead = 2 liquidbarrel = approx. 238480942.3 millimeter^3 (volume; m^3)
|
||||
> liquidbarrel
|
||||
Definition: liquidbarrel = 31.5 usgallon = approx. 119240471.1 millimeter^3 (volume; m^3)
|
||||
> rod
|
||||
Definition: rod = 5.5 yard = 5.0292 meter (length; m)
|
10
web/site/cli-manpage.adoc
Normal file
|
@ -0,0 +1,10 @@
|
|||
:website:
|
||||
|
||||
include::../../docs/rink.1.adoc[]
|
||||
|
||||
== See Also
|
||||
|
||||
* link:/configuration[Rink `config.toml` format]
|
||||
* link:/manual[Rink manual]
|
||||
* link:/definitions-format[Rink `definitions.units` format]
|
||||
* link:/datepatterns-format[Rink `datepatterns.txt` format]
|
11
web/site/configuration.adoc
Normal file
|
@ -0,0 +1,11 @@
|
|||
= Rink configuration
|
||||
:website:
|
||||
|
||||
include::../../docs/rink.5.adoc[]
|
||||
|
||||
== See Also
|
||||
|
||||
* link:/cli-manpage[rink(1) manpage]
|
||||
* link:/manual[Rink manual]
|
||||
* link:/definitions-format[Rink `definitions.units` format]
|
||||
* link:/datepatterns-format[Rink `datepatterns.txt` format]
|
11
web/site/datepatterns-format.adoc
Normal file
|
@ -0,0 +1,11 @@
|
|||
= Rink `datepatterns.txt` format
|
||||
:website:
|
||||
|
||||
include::../../docs/rink-dates.5.adoc[]
|
||||
|
||||
== See Also
|
||||
|
||||
* link:/cli-manpage[rink(1) manpage]
|
||||
* link:/configuration[Rink `config.toml` format]
|
||||
* link:/manual[Rink manual]
|
||||
* link:/definitions-format[Rink `definitions.units` format]
|
11
web/site/definitions-format.adoc
Normal file
|
@ -0,0 +1,11 @@
|
|||
= Rink `definitions.units` format
|
||||
:website:
|
||||
|
||||
include::../../docs/rink-defs.5.adoc[]
|
||||
|
||||
== See Also
|
||||
|
||||
* link:/cli-manpage[rink(1) manpage]
|
||||
* link:/configuration[Rink `config.toml` format]
|
||||
* link:/manual[Rink manual]
|
||||
* link:/datepatterns-format[Rink `datepatterns.txt` format]
|
BIN
web/site/favicon.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
web/site/fonts/FiraCode/uU9NCBsR6Z2vfE9aq3bh09SDulI.woff2
Normal file
BIN
web/site/fonts/FiraCode/uU9NCBsR6Z2vfE9aq3bh0NSDulI.woff2
Normal file
BIN
web/site/fonts/FiraCode/uU9NCBsR6Z2vfE9aq3bh0dSDulI.woff2
Normal file
BIN
web/site/fonts/FiraCode/uU9NCBsR6Z2vfE9aq3bh2dSDulI.woff2
Normal file
BIN
web/site/fonts/FiraCode/uU9NCBsR6Z2vfE9aq3bh3dSD.woff2
Normal file
BIN
web/site/fonts/FiraCode/uU9NCBsR6Z2vfE9aq3bh3tSDulI.woff2
Normal file
306
web/site/global.css
Normal file
|
@ -0,0 +1,306 @@
|
|||
:root {
|
||||
--bg-color: #141518;
|
||||
--bg-highlight: #191c22;
|
||||
--text-main: #fff;
|
||||
--text-dimmed: #da8d6a;
|
||||
--text-heading: #a2b9da;
|
||||
--font-main: "Atkinson Hyperlegible", "Segoe UI", "San Francisco", "Roboto",
|
||||
sans-serif;
|
||||
--font-code: "Fira Code", "Cascadia Mono", "Consolas", "Monaco", monospace;
|
||||
--link-normal: #569dda;
|
||||
--link-focus: #569dda;
|
||||
--link-visited: #437dc3;
|
||||
--border: #111f2b;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-main);
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
tab-size: 4;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
main {
|
||||
margin: 0 auto;
|
||||
width: 40em;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
font-family: var(--font-main);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
body {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
main {
|
||||
margin: 0 auto;
|
||||
width: 1024px;
|
||||
}
|
||||
|
||||
#page-container {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
aside#toc {
|
||||
width: 200px;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
aside#toc ol {
|
||||
position: sticky;
|
||||
top: 1em;
|
||||
}
|
||||
|
||||
#page-container section.e-content {
|
||||
width: 800px;
|
||||
}
|
||||
|
||||
nav {
|
||||
padding-left: 0.75em;
|
||||
padding-right: 0.75em;
|
||||
}
|
||||
|
||||
nav > a {
|
||||
padding: 0.3em;
|
||||
}
|
||||
|
||||
nav > .nav-spacer {
|
||||
display: unset;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
main > article {
|
||||
padding: 0.5em;
|
||||
padding-top: 0;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: flex-end;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: var(--border);
|
||||
gap: 0.5em;
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
.nav-spacer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
nav > a {
|
||||
text-decoration: none;
|
||||
padding: 0.5em;
|
||||
border-bottom: 2px solid transparent;
|
||||
|
||||
color: var(--link-normal);
|
||||
stroke: var(--link-normal);
|
||||
}
|
||||
|
||||
nav > a:hover,
|
||||
nav > a:focus {
|
||||
border-bottom: 2px solid var(--link-focus);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
nav > a > svg {
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
font-size: 80%;
|
||||
color: var(--text-dimmed);
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0.75em auto;
|
||||
}
|
||||
|
||||
figure > pre {
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
||||
figure img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
picture {
|
||||
display: content;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
p {
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5em;
|
||||
margin: 0;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
color: var(--text-heading);
|
||||
font-family: var(--font-main);
|
||||
background-color: var(--bg-highlight);
|
||||
clear: both;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
h1 a.toclink,
|
||||
h2 a.toclink,
|
||||
h3 a.toclink,
|
||||
h4 a.toclink,
|
||||
h5 a.toclink,
|
||||
h6 a.toclink {
|
||||
display: none;
|
||||
}
|
||||
|
||||
h1:hover a.toclink,
|
||||
h1:focus a.toclink,
|
||||
h2:hover a.toclink,
|
||||
h2:focus a.toclink,
|
||||
h3:hover a.toclink,
|
||||
h3:focus a.toclink,
|
||||
h4:hover a.toclink,
|
||||
h4:focus a.toclink,
|
||||
h5:hover a.toclink,
|
||||
h5:focus a.toclink,
|
||||
h6:hover a.toclink,
|
||||
h6:focus a.toclink {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: var(--font-code);
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: var(--bg-highlight);
|
||||
font-family: var(--font-code);
|
||||
max-width: 100%;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
pre > code {
|
||||
display: content;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--link-normal);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: var(--link-visited);
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:focus {
|
||||
color: var(--link-focus);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a > h3 {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.katex-block {
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
.h-feed .h-entry > time {
|
||||
margin: 0.5em 0;
|
||||
font-size: 0.9em;
|
||||
color: var(--text-dimmed);
|
||||
}
|
||||
|
||||
.h-feed .h-entry {
|
||||
/*border-top: 1px dashed var(--border);*/
|
||||
clear: right;
|
||||
}
|
||||
|
||||
.h-feed .h-entry > h3 {
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
||||
.h-feed .h-entry > blockquote {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.h-feed .h-entry > picture > img {
|
||||
float: right;
|
||||
max-width: 30%;
|
||||
height: 8em;
|
||||
margin: 0.5em;
|
||||
}
|
||||
|
||||
.h-card img {
|
||||
border-radius: 50%;
|
||||
vertical-align: -20%;
|
||||
}
|
||||
|
||||
span.u-uid {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span.meta {
|
||||
font-size: 85%;
|
||||
font-weight: 300;
|
||||
color: var(--text-dimmed);
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
table {
|
||||
background-color: var(--bg-highlight);
|
||||
display: block;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
margin: 1em auto;
|
||||
overflow-x: auto;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0 0.5em;
|
||||
}
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
19
web/site/index.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
<h1>Rink</h1>
|
||||
<p class="p-summary">Run rink queries locally in your browser.</p>
|
||||
<link rel="stylesheet" href="/rink-repl.css" />
|
||||
<form id="rink-query">
|
||||
<output id="rink-outputs" for="query">
|
||||
<noscript>
|
||||
<p>
|
||||
Running rink in the browser requires Javascript enabled. Sorry!
|
||||
</p>
|
||||
</noscript>
|
||||
</output>
|
||||
<input
|
||||
type="text"
|
||||
id="query"
|
||||
placeholder="Loading..."
|
||||
autofocus
|
||||
autocomplete="off"
|
||||
/>
|
||||
</form>
|
18
web/site/manual.adoc
Normal file
|
@ -0,0 +1,18 @@
|
|||
= Rink Manual
|
||||
:website:
|
||||
|
||||
Documentation for rink's query language.
|
||||
|
||||
* link:/cli-manpage[rink(1) manpage]
|
||||
* link:/configuration[Rink `config.toml` format]
|
||||
* link:/definitions-format[Rink `definitions.units` format]
|
||||
* link:/datepatterns-format[Rink `datepatterns.txt` format]
|
||||
|
||||
include::../../docs/rink.7.adoc[]
|
||||
|
||||
== See Also
|
||||
|
||||
* link:/cli-manpage[rink(1) manpage]
|
||||
* link:/configuration[Rink `config.toml` format]
|
||||
* link:/definitions-format[Rink `definitions.units` format]
|
||||
* link:/datepatterns-format[Rink `datepatterns.txt` format]
|
8
web/site/opensearch.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">
|
||||
<ShortName>Rink</ShortName>
|
||||
<Description>Run unit calculations using rink</Description>
|
||||
<InputEncoding>utf-8</InputEncoding>
|
||||
<Image width="16" height="16" type="image/x-icon">https://rinkcalc.app/favicon.png</Image>
|
||||
<Url type="text/html" template="https://rinkcalc.app?q={searchTerms}"/>
|
||||
<moz:SearchForm>https://rinkcalc.app</moz:SearchForm>
|
||||
</OpenSearchDescription>
|
2
web/site/releases.html
Normal file
|
@ -0,0 +1,2 @@
|
|||
<h1>Releases</h1>
|
||||
<section class="h-feed"></section>
|
75
web/site/rink-repl.css
Normal file
|
@ -0,0 +1,75 @@
|
|||
:root {
|
||||
--bg-input: #0a0b0c;
|
||||
--fg-input: #ffffff;
|
||||
--border-input: #2c2f32;
|
||||
--border-input-focus: #2992d4;
|
||||
}
|
||||
|
||||
#rink-outputs {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#rink-outputs > blockquote {
|
||||
border-left: 2px solid #fff;
|
||||
margin-left: -2px;
|
||||
padding-left: 1em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#rink-outputs > blockquote > a {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 5em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#rink-outputs > label[for="rink-dl"] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#rink-query > input {
|
||||
appearance: none;
|
||||
background-color: var(--bg-input);
|
||||
color: var(--fg-input);
|
||||
border-radius: 3px;
|
||||
border: 2px solid var(--border-input);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
font-size: inherit;
|
||||
padding: 0.2em 0.5em;
|
||||
margin: 0.1em 0;
|
||||
}
|
||||
|
||||
#rink-query > input:focus {
|
||||
outline: none;
|
||||
border: 2px solid var(--border-input-focus);
|
||||
}
|
||||
|
||||
.hl-unit,
|
||||
.hl-prop-name {
|
||||
color: #8accff;
|
||||
}
|
||||
|
||||
.hl-quantity {
|
||||
color: #b3ff98;
|
||||
}
|
||||
|
||||
.hl-doc-string {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hl-error {
|
||||
color: #ff7474;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hl-user-input {
|
||||
font-family: var(--font-code);
|
||||
}
|
||||
|
||||
/* invisible formatting text for when you copy-paste */
|
||||
.hl-hidden {
|
||||
font-size: 0;
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
{
|
||||
"id": "rink",
|
||||
"name": "Rink",
|
||||
"short_name": "Rink",
|
||||
"lang": "en",
|
||||
"theme_color": "#2196f3",
|
||||
"background_color": "#2196f3",
|
||||
"background_color": "#141518",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"start_url": "/",
|
156
web/site/webfonts.css
Normal file
|
@ -0,0 +1,156 @@
|
|||
/****** Atkinson Hyperlegible ******/
|
||||
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: "Atkinson Hyperlegible";
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/fonts/AtkinsonHyperlegible/9Bt43C1KxNDXMspQ1lPyU89-1h6ONRlW45G056IkUwCybQ.woff2)
|
||||
format("woff2");
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF,
|
||||
U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: "Atkinson Hyperlegible";
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/fonts/AtkinsonHyperlegible/9Bt43C1KxNDXMspQ1lPyU89-1h6ONRlW45G056IqUwA.woff2)
|
||||
format("woff2");
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
|
||||
U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122,
|
||||
U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: "Atkinson Hyperlegible";
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/fonts/AtkinsonHyperlegible/9Bt93C1KxNDXMspQ1lPyU89-1h6ONRlW45G056qRdhWDTFieFA.woff2)
|
||||
format("woff2");
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF,
|
||||
U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: "Atkinson Hyperlegible";
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/fonts/AtkinsonHyperlegible/9Bt93C1KxNDXMspQ1lPyU89-1h6ONRlW45G056qRdhWNTFg.woff2)
|
||||
format("woff2");
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
|
||||
U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122,
|
||||
U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: "Atkinson Hyperlegible";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/fonts/AtkinsonHyperlegible/9Bt23C1KxNDXMspQ1lPyU89-1h6ONRlW45G07JIoSwQ.woff2)
|
||||
format("woff2");
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF,
|
||||
U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: "Atkinson Hyperlegible";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/fonts/AtkinsonHyperlegible/9Bt23C1KxNDXMspQ1lPyU89-1h6ONRlW45G04pIo.woff2)
|
||||
format("woff2");
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
|
||||
U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122,
|
||||
U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: "Atkinson Hyperlegible";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/fonts/AtkinsonHyperlegible/9Bt73C1KxNDXMspQ1lPyU89-1h6ONRlW45G8Wbc9eiWPVFw.woff2)
|
||||
format("woff2");
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF,
|
||||
U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: "Atkinson Hyperlegible";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/fonts/AtkinsonHyperlegible/9Bt73C1KxNDXMspQ1lPyU89-1h6ONRlW45G8Wbc9dCWP.woff2)
|
||||
format("woff2");
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
|
||||
U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122,
|
||||
U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
/****** Fira Code ******/
|
||||
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: "Fira Code";
|
||||
font-style: normal;
|
||||
font-weight: 300 700;
|
||||
font-display: swap;
|
||||
src: url(/fonts/FiraCode/uU9NCBsR6Z2vfE9aq3bh0NSDulI.woff2) format("woff2");
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
|
||||
U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: "Fira Code";
|
||||
font-style: normal;
|
||||
font-weight: 300 700;
|
||||
font-display: swap;
|
||||
src: url(/fonts/FiraCode/uU9NCBsR6Z2vfE9aq3bh2dSDulI.woff2) format("woff2");
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: "Fira Code";
|
||||
font-style: normal;
|
||||
font-weight: 300 700;
|
||||
font-display: swap;
|
||||
src: url(/fonts/FiraCode/uU9NCBsR6Z2vfE9aq3bh0dSDulI.woff2) format("woff2");
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: "Fira Code";
|
||||
font-style: normal;
|
||||
font-weight: 300 700;
|
||||
font-display: swap;
|
||||
src: url(/fonts/FiraCode/uU9NCBsR6Z2vfE9aq3bh3tSDulI.woff2) format("woff2");
|
||||
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1,
|
||||
U+03A3-03FF;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: "Fira Code";
|
||||
font-style: normal;
|
||||
font-weight: 300 700;
|
||||
font-display: swap;
|
||||
src: url(/fonts/FiraCode/uU9NCBsR6Z2vfE9aq3bh09SDulI.woff2) format("woff2");
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF,
|
||||
U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: "Fira Code";
|
||||
font-style: normal;
|
||||
font-weight: 300 700;
|
||||
font-display: swap;
|
||||
src: url(/fonts/FiraCode/uU9NCBsR6Z2vfE9aq3bh3dSD.woff2) format("woff2");
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
|
||||
U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122,
|
||||
U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
78
web/soupault.toml
Normal file
|
@ -0,0 +1,78 @@
|
|||
[settings]
|
||||
# Defaults
|
||||
soupault_version = "4.9.0"
|
||||
strict = true
|
||||
verbose = true
|
||||
debug = false
|
||||
site_dir = "site"
|
||||
build_dir = "build"
|
||||
page_file_extensions = ["htm", "html", "md", "rst", "adoc"]
|
||||
clean_urls = true
|
||||
keep_extensions = ["html", "htm"]
|
||||
default_extension = "html"
|
||||
ignore_extensions = ["draft"]
|
||||
generator_mode = true
|
||||
complete_page_selector = "html"
|
||||
default_template_file = "templates/main.html"
|
||||
default_content_selector = "article>section"
|
||||
default_content_action = "append_child"
|
||||
keep_doctype = true
|
||||
doctype = "<!DOCTYPE html>"
|
||||
pretty_print_html = true
|
||||
plugin_discovery = true
|
||||
plugin_dirs = ["plugins"]
|
||||
caching = false
|
||||
cache_dir = ".soupault-cache"
|
||||
page_character_encoding = "utf-8"
|
||||
|
||||
[preprocessors]
|
||||
adoc = "node scripts/asciidoc.js"
|
||||
|
||||
[widgets.page-title]
|
||||
widget = "title"
|
||||
selector = "h1"
|
||||
force = false
|
||||
|
||||
[widgets.toc]
|
||||
widget = "toc"
|
||||
selector = "#toc"
|
||||
min_level = 2
|
||||
max_level = 4
|
||||
numbered_list = true
|
||||
heading_links = true
|
||||
heading_link_text = "#"
|
||||
heading_link_class = "toclink"
|
||||
use_heading_slug = true
|
||||
slug_replacement_string = "-"
|
||||
ignore_heading_selectors = [".linkheader"]
|
||||
|
||||
[widgets.deuglify]
|
||||
widget = "deuglify"
|
||||
|
||||
[widgets.fetch-releases]
|
||||
widget = "fetch-releases"
|
||||
page = "releases.html"
|
||||
|
||||
[widgets.vite]
|
||||
widget = "vite"
|
||||
page = "index.html"
|
||||
|
||||
[widgets.relocate-h1]
|
||||
widget = "relocate"
|
||||
selector = "h1"
|
||||
new_parent = "main>nav"
|
||||
action = "insert_after"
|
||||
after = "toc"
|
||||
|
||||
[widgets.relocate-meta]
|
||||
widget = "relocate"
|
||||
selector = ".e-content>span.meta"
|
||||
new_parent = "h1"
|
||||
action = "insert_after"
|
||||
after = "relocate-h1"
|
||||
|
||||
[widgets.opengraph]
|
||||
widget = "opengraph"
|
||||
|
||||
[custom_options]
|
||||
site_title = "Rink"
|
|
@ -1,14 +0,0 @@
|
|||
import wasm from "../../rink-js/Cargo.toml";
|
||||
import Rink from "./util/rink";
|
||||
import * as sapper from "@sapper/app";
|
||||
|
||||
sapper.start({
|
||||
target: document.querySelector("#sapper"),
|
||||
});
|
||||
|
||||
async function loadRink() {
|
||||
let rink = await wasm();
|
||||
Rink.setRink(rink);
|
||||
}
|
||||
|
||||
loadRink();
|
|
@ -1,21 +0,0 @@
|
|||
<script>
|
||||
import { children } from "svelte/internal";
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.card-inner {
|
||||
border-left: 4px solid #ddd;
|
||||
padding: 0.1em 0.5em;
|
||||
background-color: rgb(250, 250, 250);
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 0.5em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-inner">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
|
@ -1,48 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import QueryInput from "./QueryInput.svelte";
|
||||
|
||||
export let header: "dark" | "normal" = "normal";
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
max-width: 60em;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
border: 1px solid rgb(200, 200, 200);
|
||||
background-color: white;
|
||||
border-bottom: none;
|
||||
margin-top: 1em;
|
||||
min-height: 5px;
|
||||
}
|
||||
|
||||
.dark {
|
||||
background-color: rgb(30, 30, 30);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.content {
|
||||
border: 1px solid rgb(200, 200, 200);
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
padding: 1em;
|
||||
padding-top: 0;
|
||||
background-color: white;
|
||||
border-top: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container">
|
||||
<QueryInput />
|
||||
|
||||
<div class={`header ${header}`}>
|
||||
<slot name="header" />
|
||||
</div>
|
||||
<div class="content" role="main">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
|
@ -1,73 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import type { Dimensionality } from "../util/reply";
|
||||
|
||||
export let quantity: Dimensionality;
|
||||
|
||||
type DimensionalityArray = [string, number][];
|
||||
|
||||
function readUnits(
|
||||
quantity: Dimensionality
|
||||
): { numer: DimensionalityArray; denom: DimensionalityArray } {
|
||||
const numer: DimensionalityArray = [];
|
||||
const denom: DimensionalityArray = [];
|
||||
if (quantity) {
|
||||
for (const [dim, pow] of Object.entries(quantity)) {
|
||||
if (pow > 0) {
|
||||
numer.push([dim, pow]);
|
||||
} else {
|
||||
denom.push([dim, pow]);
|
||||
}
|
||||
}
|
||||
}
|
||||
numer.sort();
|
||||
denom.sort();
|
||||
return { numer, denom };
|
||||
}
|
||||
|
||||
$: unit = readUnits(quantity);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.unit > a {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
font-size: 0;
|
||||
color: transparent;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
<span class="unit"
|
||||
>{#each unit.numer as [dim, pow], i
|
||||
}{#if i != 0
|
||||
} {/if
|
||||
}<a href={`/unit/${dim}`}
|
||||
><span
|
||||
>{dim}</span
|
||||
>{#if pow != 1
|
||||
}<span class="hidden"
|
||||
>^</span
|
||||
><sup
|
||||
>{pow}</sup
|
||||
>{/if
|
||||
}</a
|
||||
>{/each
|
||||
}{#if unit.denom.length > 0
|
||||
}<span
|
||||
> / </span
|
||||
>{#each unit.denom as [dim, pow]
|
||||
}<a href={`/unit/${dim}`}
|
||||
><span
|
||||
>{dim}</span
|
||||
>{#if pow != -1
|
||||
}<span class="hidden"
|
||||
>^</span
|
||||
><sup
|
||||
>{-pow}</sup
|
||||
>{/if
|
||||
}</a
|
||||
>{/each
|
||||
}{/if
|
||||
}</span>
|
|
@ -1,30 +0,0 @@
|
|||
<script lang="typescript">
|
||||
export let text: string;
|
||||
export let icon: string;
|
||||
export let href: string;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
a {
|
||||
padding: 0.25em;
|
||||
padding: 0.5em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
color: rgb(60, 60, 60);
|
||||
text-decoration: unset;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: rgb(10, 10, 10);
|
||||
border-bottom: 3px solid rgb(120, 120, 120);
|
||||
padding-bottom: calc(0.5em - 3px);
|
||||
}
|
||||
</style>
|
||||
|
||||
<a {href}>
|
||||
<img src={icon} alt="" width="24" height="24" />
|
||||
{text}
|
||||
</a>
|
|
@ -1,19 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import type { NumberParts } from "../util/reply";
|
||||
import Dimensionality from "./Dimensionality.svelte";
|
||||
|
||||
export let number: NumberParts;
|
||||
</script>
|
||||
|
||||
{#if number.exactValue && number.exactValue.indexOf('/') != -1}
|
||||
{number.exactValue}, approx. {number.approxValue}
|
||||
{:else if number.exactValue}
|
||||
{number.exactValue}
|
||||
{:else if number.approxValue}approx. {number.approxValue}{/if}
|
||||
{#if number.rawUnit}
|
||||
<Dimensionality quantity={number.rawUnit} />
|
||||
{/if}
|
||||
<!-- prettier-ignore -->
|
||||
{#if number.quantity}
|
||||
(<a href={`/quantity/${number.quantity}`}>{number.quantity}</a>)
|
||||
{/if}
|
|
@ -1,11 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import type { NumericParts } from "../util/expr";
|
||||
|
||||
export let number: NumericParts;
|
||||
</script>
|
||||
|
||||
{#if number.exactValue}
|
||||
{number.exactValue}
|
||||
{:else if number.approxValue}
|
||||
approx. {number.approxValue}
|
||||
{:else}{number.numer}/{number.denom}{/if}
|
|
@ -1,23 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import { stores } from "@sapper/app";
|
||||
export let title: string;
|
||||
export let type: string = "website";
|
||||
export let image: string = "/images/icons/icon-512x512.png";
|
||||
export let description: string = "";
|
||||
|
||||
const { page } = stores();
|
||||
|
||||
function getUrl(page: any) {
|
||||
const url = page.host + page.path;
|
||||
|
||||
return `https://${url}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<title>{title}</title>
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:type" content={type} />
|
||||
<meta property="og:url" content={getUrl($page)} />
|
||||
<meta property="og:image" content="https://{$page.host}{image}" />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:site_name" content="Rink" />
|
|
@ -1,19 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import { Precedence } from "../util/precedence";
|
||||
|
||||
export let precedence: Precedence;
|
||||
export let expected: Precedence;
|
||||
</script>
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
{#if true
|
||||
}{#if precedence < expected
|
||||
}<span
|
||||
>(</span
|
||||
>{/if
|
||||
}<slot
|
||||
/>{#if precedence < expected
|
||||
}<span
|
||||
>)</span
|
||||
>{/if}
|
||||
{/if}
|
|
@ -1,124 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import { goto } from "@sapper/app";
|
||||
import { query } from "../stores";
|
||||
|
||||
export let text: string = "";
|
||||
|
||||
query.subscribe((value) => {
|
||||
text = value;
|
||||
});
|
||||
|
||||
let inputElement: HTMLInputElement;
|
||||
|
||||
async function handleSubmit(event: Event & { target: HTMLFormElement }) {
|
||||
event.preventDefault();
|
||||
const slug = encodeURIComponent(text);
|
||||
await goto(`/query/${slug}`);
|
||||
}
|
||||
|
||||
function handleFormClick() {
|
||||
inputElement.focus();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.input {
|
||||
font-size: 1.25em;
|
||||
flex-grow: 1;
|
||||
background: none;
|
||||
border: none;
|
||||
margin-left: 0.3em;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
background-color: rgb(255, 255, 255);
|
||||
border: 1px solid rgb(60, 60, 60);
|
||||
border-radius: 5px;
|
||||
padding: 0.3em;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
svg {
|
||||
stroke: rgb(60, 60, 60);
|
||||
}
|
||||
|
||||
header {
|
||||
margin-top: 1em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.home {
|
||||
padding: calc(0.3em - 1px);
|
||||
margin: 0.2em;
|
||||
background-color: rgb(255, 255, 255);
|
||||
border: 1px solid rgb(60, 60, 60);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.home:hover,
|
||||
.home:focus {
|
||||
background-color: rgb(240, 240, 240);
|
||||
border: 1px solid rgb(20, 20, 20);
|
||||
}
|
||||
</style>
|
||||
|
||||
<header role="navigation">
|
||||
<a href="/" class="home" title="Home" alt="Home">
|
||||
<svg
|
||||
role="figure"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="feather feather-home">
|
||||
<title>Home</title>
|
||||
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
|
||||
<polyline points="9 22 9 12 15 12 15 22" />
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<form
|
||||
action="/query"
|
||||
method="get"
|
||||
on:submit={handleSubmit}
|
||||
on:click={handleFormClick}
|
||||
role="search"
|
||||
aria-label="Search">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="feather feather-search">
|
||||
<title>Search</title>
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||||
</svg>
|
||||
|
||||
<input
|
||||
bind:this={inputElement}
|
||||
class="input"
|
||||
type="search"
|
||||
name="input"
|
||||
bind:value={text}
|
||||
placeholder="Enter a query..." />
|
||||
</form>
|
||||
</header>
|
|
@ -1,20 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import type { BinOpExpr } from "../../util/expr";
|
||||
import type { Precedence } from "../../util/precedence";
|
||||
import { expectedPrecedence, symbol } from "../../util/precedence";
|
||||
import PrecedenceWrapper from "../PrecedenceWrapper.svelte";
|
||||
import Node from "./Node.svelte";
|
||||
|
||||
export let value: BinOpExpr;
|
||||
export let precedence: Precedence;
|
||||
</script>
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
<PrecedenceWrapper {precedence} expected={expectedPrecedence(value.op)}
|
||||
><Node value={value.left} precedence={expectedPrecedence(value.op) - 1}
|
||||
/><span
|
||||
>{symbol(value.op)}</span
|
||||
><Node
|
||||
value={value.right}
|
||||
precedence={expectedPrecedence(value.op) - 1}
|
||||
/></PrecedenceWrapper>
|
|
@ -1,12 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import type { CallExpr } from "../../util/expr";
|
||||
|
||||
export let value: CallExpr;
|
||||
</script>
|
||||
|
||||
<span>{value.func}</span>
|
||||
<span>(</span>
|
||||
{#each value.args as arg}
|
||||
<svelte:self value={arg} />
|
||||
{/each}
|
||||
<span>)</span>
|
|
@ -1,10 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import type { ConstExpr } from "../../util/expr";
|
||||
import Numeric from "../Numeric.svelte";
|
||||
|
||||
export let value: ConstExpr;
|
||||
</script>
|
||||
|
||||
<span>
|
||||
<Numeric number={value.value} />
|
||||
</span>
|
|
@ -1,7 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import type { DateExpr } from "../../util/expr";
|
||||
|
||||
export let value: DateExpr;
|
||||
</script>
|
||||
|
||||
<pre>{(JSON.stringify(value), null, 2)}</pre>
|
|
@ -1,10 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import Node from "./Node.svelte";
|
||||
import type { Expr } from "../../util/expr";
|
||||
|
||||
export let value: Expr;
|
||||
</script>
|
||||
|
||||
<span>
|
||||
<Node {value} />
|
||||
</span>
|
|
@ -1,24 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import type { Fraction } from "../../util/precedence";
|
||||
import { Precedence } from "../../util/precedence";
|
||||
import Node from "./Node.svelte";
|
||||
import PrecedenceWrapper from "../PrecedenceWrapper.svelte";
|
||||
|
||||
export let value: Fraction;
|
||||
export let precedence: Precedence;
|
||||
</script>
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
<PrecedenceWrapper {precedence} expected={Precedence.Mul}
|
||||
><sup
|
||||
><Node
|
||||
value={value.left}
|
||||
precedence={Precedence.Mul}
|
||||
/></sup
|
||||
><span>∕</span
|
||||
><sub
|
||||
><Node
|
||||
value={value.right}
|
||||
precedence={Precedence.Mul}
|
||||
/></sub
|
||||
></PrecedenceWrapper>
|
|
@ -1,20 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import type { MulExpr } from "../../util/expr";
|
||||
import { Precedence } from "../../util/precedence";
|
||||
import PrecedenceWrapper from "../PrecedenceWrapper.svelte";
|
||||
import Node from "./Node.svelte";
|
||||
|
||||
export let value: MulExpr;
|
||||
export let precedence: Precedence;
|
||||
</script>
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
<PrecedenceWrapper {precedence} expected={Precedence.Mul}
|
||||
>{#each value.exprs as item, i
|
||||
}{#if i != 0
|
||||
}<span
|
||||
> </span
|
||||
>{/if
|
||||
}<Node value={item} precedence={Precedence.Mul}
|
||||
/>{/each
|
||||
}</PrecedenceWrapper>
|
|
@ -1,46 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import type { Expr } from "../../util/expr";
|
||||
import { Precedence, isPow, isFraction } from "../../util/precedence";
|
||||
import Unit from "./Unit.svelte";
|
||||
import Quote from "./Quote.svelte";
|
||||
import Const from "./Const.svelte";
|
||||
import Date from "./Date.svelte";
|
||||
import Pow from "./Pow.svelte";
|
||||
import Fraction from "./Fraction.svelte";
|
||||
import Binop from "./Binop.svelte";
|
||||
import Unaryop from "./Unaryop.svelte";
|
||||
import Mul from "./Mul.svelte";
|
||||
import Of from "./Of.svelte";
|
||||
import Call from "./Call.svelte";
|
||||
|
||||
export let value: Expr;
|
||||
export let precedence: Precedence = Precedence.Equals;
|
||||
</script>
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
{#if value.type == 'unit'
|
||||
}<Unit {value}
|
||||
/>{:else if value.type == 'quote'
|
||||
}<Quote {value}
|
||||
/>{:else if value.type == 'const'
|
||||
}<Const {value}
|
||||
/>{:else if value.type == 'date'
|
||||
}<Date {value}
|
||||
/>{:else if isPow(value)
|
||||
}<Pow {value} {precedence}
|
||||
/>{:else if isFraction(value)
|
||||
}<Fraction {value} {precedence}
|
||||
/>{:else if value.type == 'binop'
|
||||
}<Binop {value} {precedence}
|
||||
/>{:else if value.type == 'unaryop'
|
||||
}<Unaryop {value}
|
||||
/>{:else if value.type == 'mul'
|
||||
}<Mul {value} {precedence}
|
||||
/>{:else if value.type == 'of'
|
||||
}<Of {value} {precedence}
|
||||
/>{:else if value.type == 'call'
|
||||
}<Call {value}
|
||||
/>{:else if value.type == 'error'
|
||||
}<span style="color: red"
|
||||
>{value.message}</span
|
||||
>{/if}
|
|
@ -1,15 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import type { OfExpr } from "../../util/expr";
|
||||
import { Precedence } from "../../util/precedence";
|
||||
import PrecedenceWrapper from "../PrecedenceWrapper.svelte";
|
||||
import Node from "./Node.svelte";
|
||||
|
||||
export let value: OfExpr;
|
||||
export let precedence: Precedence;
|
||||
</script>
|
||||
|
||||
<PrecedenceWrapper {precedence} expected={Precedence.Term}>
|
||||
<span>{value.property}</span>
|
||||
<span>of</span>
|
||||
<Node value={value.expr} />
|
||||
</PrecedenceWrapper>
|
|
@ -1,26 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import { Precedence } from "../../util/precedence";
|
||||
import type { PowExpr } from "../../util/precedence";
|
||||
import PrecedenceWrapper from "../PrecedenceWrapper.svelte";
|
||||
import Node from "./Node.svelte";
|
||||
|
||||
export let value: PowExpr;
|
||||
export let precedence: Precedence;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.hidden {
|
||||
font-size: 0;
|
||||
color: transparent;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
<PrecedenceWrapper {precedence} expected={Precedence.Pow}
|
||||
><Node value={value.left} precedence={Precedence.Term}
|
||||
/><span class="hidden">^</span
|
||||
><sup
|
||||
><Node value={value.right} precedence={Precedence.Term}
|
||||
/></sup
|
||||
></PrecedenceWrapper
|
||||
>
|
|
@ -1,7 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import type { QuoteExpr } from "../../util/expr";
|
||||
|
||||
export let value: QuoteExpr;
|
||||
</script>
|
||||
|
||||
<span>"{value.string}"</span>
|
|
@ -1,9 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import type { UnaryOpExpr } from "../../util/expr";
|
||||
import Node from "./Node.svelte";
|
||||
|
||||
export let value: UnaryOpExpr;
|
||||
</script>
|
||||
|
||||
<span>{value.op}</span>
|
||||
<Node value={value.expr} />
|
|
@ -1,7 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import type { UnitExpr } from "../../util/expr";
|
||||
|
||||
export let value: UnitExpr;
|
||||
</script>
|
||||
|
||||
<a href={`/unit/${value.name}`}>{value.name}</a>
|
|
@ -1,29 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import type { ConformanceError } from "../../util/reply";
|
||||
import Dimensionality from "../Dimensionality.svelte";
|
||||
|
||||
export let value: ConformanceError;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.capitalize::first-letter {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h3>Conformance Error</h3>
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
<p>
|
||||
<a href="/quantity/{value.left.quantity}">{value.left.quantity}</a>
|
||||
(<Dimensionality quantity={value.left.rawDimensions} />)
|
||||
!=
|
||||
<a href="/quantity/{value.right.quantity}">{value.right.quantity}</a>
|
||||
(<Dimensionality quantity={value.right.rawDimensions} />)
|
||||
</p>
|
||||
<h4>Suggestions</h4>
|
||||
<ul>
|
||||
{#each value.suggestions as suggestion}
|
||||
<li class="capitalize">{suggestion}</li>
|
||||
{/each}
|
||||
</ul>
|
|
@ -1,10 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import type { ConversionReply } from "../../util/reply";
|
||||
import Number from "../Number.svelte";
|
||||
|
||||
export let value: ConversionReply;
|
||||
</script>
|
||||
|
||||
<p>
|
||||
<Number number={value.value} />
|
||||
</p>
|
|
@ -1,26 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import type { DateReply } from "../../util/reply";
|
||||
|
||||
export let value: DateReply;
|
||||
|
||||
const format = new Intl.DateTimeFormat(undefined, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
weekday: "long",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
second: "numeric",
|
||||
timeZoneName: "short",
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.small {
|
||||
color: rgb(120, 120, 120);
|
||||
font-size: 0.8em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<p>{format.format(new Date(value.rfc3339))}</p>
|
||||
<p class="small">{new Date(value.rfc3339).toUTCString()}</p>
|
|
@ -1,31 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import type { DefReply } from "../../util/reply";
|
||||
import Number from "../Number.svelte";
|
||||
import Expr from "../expr/Expr.svelte";
|
||||
|
||||
export let value: DefReply;
|
||||
</script>
|
||||
|
||||
<h3>{value.canonName}</h3>
|
||||
|
||||
{#if value.defExpr}
|
||||
<p>
|
||||
Definition:
|
||||
<Expr value={value.defExpr.ast} />
|
||||
</p>
|
||||
{:else if value.type == 'def'}
|
||||
<p>Definition: {value.def}</p>
|
||||
{/if}
|
||||
|
||||
{#if value.defExpr && value.value}
|
||||
<p>
|
||||
Value:
|
||||
<Number number={value.value} />
|
||||
</p>
|
||||
{:else if value.defExpr}
|
||||
<p>Value: {value.defExpr}</p>
|
||||
{/if}
|
||||
|
||||
{#if value.doc}
|
||||
<p>{value.doc}</p>
|
||||
{/if}
|
|
@ -1,29 +0,0 @@
|
|||
<script lang="typescript">
|
||||
import type { Duration, DurationReply, NumberParts } from "../../util/reply";
|
||||
import Dimensionality from "../Dimensionality.svelte";
|
||||
|
||||
export let value: DurationReply;
|
||||
|
||||
let keys: (keyof Duration)[] = [
|
||||
"years",
|
||||
"months",
|
||||
"weeks",
|
||||
"days",
|
||||
"hours",
|
||||
"minutes",
|
||||
"seconds",
|
||||
];
|
||||
|
||||
$: values = keys
|
||||
.map((key) => value[key])
|
||||
.filter((value) => value.exactValue != "0");
|
||||
</script>
|
||||
|
||||
{#each values as value, i}
|
||||
{#if i != 0}<span>, </span>{/if}
|
||||
<span>{value.exactValue}</span>
|
||||
<Dimensionality quantity={value.rawUnit} />
|
||||
{:else}
|
||||
<span>0</span>
|
||||
<Dimensionality quantity={{ seconds: 1 }} />
|
||||
{/each}
|