Replace marked/turndown/purify with ribbit editor
This commit moves all html/markdown editor logic to the ribbit library. Submodule tracks https://git.evilchi.li/evilchili/ribbit.git. The dist files in static/ are committed copies; the submodule provides source access for development. We use pre_format and pre_build slam hooks to ensure the submodules are initialized and updated correctly, and to perform the dist file copy.
This commit is contained in:
parent
ce8042759e
commit
256c8d7090
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -16,6 +16,7 @@ downloads/
|
||||||
eggs/
|
eggs/
|
||||||
.eggs/
|
.eggs/
|
||||||
lib/
|
lib/
|
||||||
|
!lib/ribbit
|
||||||
lib64/
|
lib64/
|
||||||
parts/
|
parts/
|
||||||
sdist/
|
sdist/
|
||||||
|
|
|
||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "lib/ribbit"]
|
||||||
|
path = lib/ribbit
|
||||||
|
url = https://git.evilchi.li/evilchili/ribbit.git
|
||||||
18
.slam/hooks/pre_build
Executable file
18
.slam/hooks/pre_build
Executable file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
RIBBIT_DIR="lib/ribbit"
|
||||||
|
STATIC_DIR="src/ttfrog/themes/default/static"
|
||||||
|
|
||||||
|
echo "Building ribbit..."
|
||||||
|
cd "$RIBBIT_DIR"
|
||||||
|
npm install --silent
|
||||||
|
npm run build --silent
|
||||||
|
cd - > /dev/null
|
||||||
|
|
||||||
|
echo "Copying ribbit dist files..."
|
||||||
|
cp "$RIBBIT_DIR/dist/ribbit.js" "$STATIC_DIR/"
|
||||||
|
cp "$RIBBIT_DIR/dist/ribbit.min.js" "$STATIC_DIR/"
|
||||||
|
cp "$RIBBIT_DIR/src/ribbit.css" "$STATIC_DIR/"
|
||||||
|
|
||||||
|
echo "ribbit build complete."
|
||||||
5
.slam/hooks/pre_format
Executable file
5
.slam/hooks/pre_format
Executable file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Initialize and update git submodules to their latest remote commits.
|
||||||
|
if [ -f .gitmodules ]; then
|
||||||
|
git -c protocol.file.allow=always submodule update --init --force --remote --recursive
|
||||||
|
fi
|
||||||
1
lib/ribbit
Submodule
1
lib/ribbit
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 5983ce50fda7f1f15db71c1fb9f62db0e4655e0e
|
||||||
772
poetry.lock
generated
Normal file
772
poetry.lock
generated
Normal file
|
|
@ -0,0 +1,772 @@
|
||||||
|
# This file is automatically @generated by Poetry 2.3.4 and should not be changed by hand.
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotated-doc"
|
||||||
|
version = "0.0.4"
|
||||||
|
description = "Document parameters, class attributes, return types, and variables inline, with Annotated."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320"},
|
||||||
|
{file = "annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blinker"
|
||||||
|
version = "1.9.0"
|
||||||
|
description = "Fast, simple object-to-object and broadcast signaling"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"},
|
||||||
|
{file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cachelib"
|
||||||
|
version = "0.13.0"
|
||||||
|
description = "A collection of cache libraries in the same API interface."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "cachelib-0.13.0-py3-none-any.whl", hash = "sha256:8c8019e53b6302967d4e8329a504acf75e7bc46130291d30188a6e4e58162516"},
|
||||||
|
{file = "cachelib-0.13.0.tar.gz", hash = "sha256:209d8996e3c57595bee274ff97116d1d73c4980b2fd9a34c7846cd07fd2e1a48"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "click"
|
||||||
|
version = "8.3.3"
|
||||||
|
description = "Composable command line interface toolkit"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613"},
|
||||||
|
{file = "click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
description = "Cross-platform colored terminal text."
|
||||||
|
optional = false
|
||||||
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||||
|
groups = ["main", "dev"]
|
||||||
|
files = [
|
||||||
|
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||||
|
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||||
|
]
|
||||||
|
markers = {main = "platform_system == \"Windows\"", dev = "sys_platform == \"win32\""}
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coverage"
|
||||||
|
version = "7.13.5"
|
||||||
|
description = "Code coverage measurement for Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "coverage-7.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0723d2c96324561b9aa76fb982406e11d93cdb388a7a7da2b16e04719cf7ca5"},
|
||||||
|
{file = "coverage-7.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52f444e86475992506b32d4e5ca55c24fc88d73bcbda0e9745095b28ef4dc0cf"},
|
||||||
|
{file = "coverage-7.13.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:704de6328e3d612a8f6c07000a878ff38181ec3263d5a11da1db294fa6a9bdf8"},
|
||||||
|
{file = "coverage-7.13.5-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a1a6d79a14e1ec1832cabc833898636ad5f3754a678ef8bb4908515208bf84f4"},
|
||||||
|
{file = "coverage-7.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79060214983769c7ba3f0cee10b54c97609dca4d478fa1aa32b914480fd5738d"},
|
||||||
|
{file = "coverage-7.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:356e76b46783a98c2a2fe81ec79df4883a1e62895ea952968fb253c114e7f930"},
|
||||||
|
{file = "coverage-7.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0cef0cdec915d11254a7f549c1170afecce708d30610c6abdded1f74e581666d"},
|
||||||
|
{file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dc022073d063b25a402454e5712ef9e007113e3a676b96c5f29b2bda29352f40"},
|
||||||
|
{file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9b74db26dfea4f4e50d48a4602207cd1e78be33182bc9cbf22da94f332f99878"},
|
||||||
|
{file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ad146744ca4fd09b50c482650e3c1b1f4dfa1d4792e0a04a369c7f23336f0400"},
|
||||||
|
{file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c555b48be1853fe3997c11c4bd521cdd9a9612352de01fa4508f16ec341e6fe0"},
|
||||||
|
{file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7034b5c56a58ae5e85f23949d52c14aca2cfc6848a31764995b7de88f13a1ea0"},
|
||||||
|
{file = "coverage-7.13.5-cp310-cp310-win32.whl", hash = "sha256:eb7fdf1ef130660e7415e0253a01a7d5a88c9c4d158bcf75cbbd922fd65a5b58"},
|
||||||
|
{file = "coverage-7.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:3e1bb5f6c78feeb1be3475789b14a0f0a5b47d505bfc7267126ccbd50289999e"},
|
||||||
|
{file = "coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d"},
|
||||||
|
{file = "coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587"},
|
||||||
|
{file = "coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642"},
|
||||||
|
{file = "coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b"},
|
||||||
|
{file = "coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686"},
|
||||||
|
{file = "coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743"},
|
||||||
|
{file = "coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75"},
|
||||||
|
{file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209"},
|
||||||
|
{file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a"},
|
||||||
|
{file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e"},
|
||||||
|
{file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd"},
|
||||||
|
{file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8"},
|
||||||
|
{file = "coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf"},
|
||||||
|
{file = "coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9"},
|
||||||
|
{file = "coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45"},
|
||||||
|
{file = "coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61"},
|
||||||
|
{file = "coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
toml = ["tomli ; python_full_version <= \"3.11.0a6\""]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flask"
|
||||||
|
version = "3.1.3"
|
||||||
|
description = "A simple framework for building complex web applications."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c"},
|
||||||
|
{file = "flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
blinker = ">=1.9.0"
|
||||||
|
click = ">=8.1.3"
|
||||||
|
itsdangerous = ">=2.2.0"
|
||||||
|
jinja2 = ">=3.1.2"
|
||||||
|
markupsafe = ">=2.1.1"
|
||||||
|
werkzeug = ">=3.1.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
async = ["asgiref (>=3.2)"]
|
||||||
|
dotenv = ["python-dotenv"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flask-session"
|
||||||
|
version = "0.8.0"
|
||||||
|
description = "Server-side session support for Flask"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "flask_session-0.8.0-py3-none-any.whl", hash = "sha256:5dae6e9ddab334f8dc4dea4305af37851f4e7dc0f484caf3351184001195e3b7"},
|
||||||
|
{file = "flask_session-0.8.0.tar.gz", hash = "sha256:20e045eb01103694e70be4a49f3a80dbb1b57296a22dc6f44bbf3f83ef0742ff"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
cachelib = "*"
|
||||||
|
flask = ">=2.2"
|
||||||
|
msgspec = ">=0.18.6"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
all = ["Flask-Session[cachelib,memcached,mongodb,redis,sqlalchemy]"]
|
||||||
|
cachelib = ["cachelib (>=0.10.2)"]
|
||||||
|
memcached = ["pymemcache"]
|
||||||
|
mongodb = ["pymongo (>=4.6.2)"]
|
||||||
|
redis = ["redis (>=5.0.3)"]
|
||||||
|
sqlalchemy = ["flask-sqlalchemy (>=3.0.5)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "grung-db"
|
||||||
|
version = "1.0.0.2"
|
||||||
|
description = "grung-db: A very small database toolkit."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["main"]
|
||||||
|
files = []
|
||||||
|
develop = false
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
nanoid = "^2.0.0"
|
||||||
|
tinydb = "^4.8.2"
|
||||||
|
|
||||||
|
[package.source]
|
||||||
|
type = "git"
|
||||||
|
url = "https://git.evilchi.li/evilchili/grung-db.git"
|
||||||
|
reference = "HEAD"
|
||||||
|
resolved_reference = "8355d5a3c28efcdb583623caf54b70e79fab2af5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iniconfig"
|
||||||
|
version = "2.3.0"
|
||||||
|
description = "brain-dead simple config-ini parsing"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"},
|
||||||
|
{file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itsdangerous"
|
||||||
|
version = "2.2.0"
|
||||||
|
description = "Safely pass data to untrusted environments and back."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"},
|
||||||
|
{file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jinja2"
|
||||||
|
version = "3.1.6"
|
||||||
|
description = "A very fast and expressive template engine."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
|
||||||
|
{file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
MarkupSafe = ">=2.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
i18n = ["Babel (>=2.7)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markdown-it-py"
|
||||||
|
version = "4.0.0"
|
||||||
|
description = "Python port of markdown-it. Markdown parsing, done right!"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"},
|
||||||
|
{file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
mdurl = ">=0.1,<1.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
benchmarking = ["psutil", "pytest", "pytest-benchmark"]
|
||||||
|
compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "markdown-it-pyrs", "mistletoe (>=1.0,<2.0)", "mistune (>=3.0,<4.0)", "panflute (>=2.3,<3.0)"]
|
||||||
|
linkify = ["linkify-it-py (>=1,<3)"]
|
||||||
|
plugins = ["mdit-py-plugins (>=0.5.0)"]
|
||||||
|
profiling = ["gprof2dot"]
|
||||||
|
rtd = ["ipykernel", "jupyter_sphinx", "mdit-py-plugins (>=0.5.0)", "myst-parser", "pyyaml", "sphinx", "sphinx-book-theme (>=1.0,<2.0)", "sphinx-copybutton", "sphinx-design"]
|
||||||
|
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions", "requests"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markupsafe"
|
||||||
|
version = "3.0.3"
|
||||||
|
description = "Safely add untrusted strings to HTML/XML markup."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"},
|
||||||
|
{file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"},
|
||||||
|
{file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"},
|
||||||
|
{file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"},
|
||||||
|
{file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"},
|
||||||
|
{file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"},
|
||||||
|
{file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"},
|
||||||
|
{file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"},
|
||||||
|
{file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"},
|
||||||
|
{file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"},
|
||||||
|
{file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"},
|
||||||
|
{file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"},
|
||||||
|
{file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"},
|
||||||
|
{file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"},
|
||||||
|
{file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"},
|
||||||
|
{file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"},
|
||||||
|
{file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"},
|
||||||
|
{file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"},
|
||||||
|
{file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"},
|
||||||
|
{file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"},
|
||||||
|
{file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"},
|
||||||
|
{file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"},
|
||||||
|
{file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"},
|
||||||
|
{file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"},
|
||||||
|
{file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"},
|
||||||
|
{file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"},
|
||||||
|
{file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"},
|
||||||
|
{file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"},
|
||||||
|
{file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"},
|
||||||
|
{file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"},
|
||||||
|
{file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"},
|
||||||
|
{file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"},
|
||||||
|
{file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"},
|
||||||
|
{file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"},
|
||||||
|
{file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"},
|
||||||
|
{file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"},
|
||||||
|
{file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"},
|
||||||
|
{file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"},
|
||||||
|
{file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"},
|
||||||
|
{file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"},
|
||||||
|
{file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"},
|
||||||
|
{file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"},
|
||||||
|
{file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"},
|
||||||
|
{file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"},
|
||||||
|
{file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"},
|
||||||
|
{file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"},
|
||||||
|
{file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mdurl"
|
||||||
|
version = "0.1.2"
|
||||||
|
description = "Markdown URL utilities"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
|
||||||
|
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "msgspec"
|
||||||
|
version = "0.21.1"
|
||||||
|
description = "A fast serialization and validation library, with builtin support for JSON, MessagePack, YAML, and TOML."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "msgspec-0.21.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72d9cd03241b8b2edb2e12dcc66c500fa480d8cbd71a8bac105809d468882064"},
|
||||||
|
{file = "msgspec-0.21.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed2ab278200e743a1d2610a4e0c8fc74f6cecb8548544cdec43f927bd9265238"},
|
||||||
|
{file = "msgspec-0.21.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd677e3001fdfed9186de72eab434da2976303cd5eb9550921d3d0c3e3e168ce"},
|
||||||
|
{file = "msgspec-0.21.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f667b90b37fad734a91671abd68e0d7f4d066862771b87e91c53996dcb7a9027"},
|
||||||
|
{file = "msgspec-0.21.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:49880fd20fdbcfe1b793f07dd83f12572bab679c9800352c8b2240289aa46a06"},
|
||||||
|
{file = "msgspec-0.21.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ae0162e22849a5e91eaad907766525107523b0daea3df267a9fcb5ba4e0936ae"},
|
||||||
|
{file = "msgspec-0.21.1-cp310-cp310-win_amd64.whl", hash = "sha256:f041a2279f31e3a53319005e4d60ba77c085cfcbe394cdc7ce803c2d01fe9449"},
|
||||||
|
{file = "msgspec-0.21.1-cp310-cp310-win_arm64.whl", hash = "sha256:1bf17cbd7b28a5dffc7e764c654eed8ccde5e0f1de7970628608304640d4ce4e"},
|
||||||
|
{file = "msgspec-0.21.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b504b6e7f7a22a24b27232b73034421692147865162daaec9f3bf62439007c87"},
|
||||||
|
{file = "msgspec-0.21.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4692b7c1609155708c4418f88e92f63c13fdf08aa095c84bae82bad75b53389b"},
|
||||||
|
{file = "msgspec-0.21.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d3124010b3815451494c85ff345e693cb9fe5889cfcbbef39ed8622e0e72319c"},
|
||||||
|
{file = "msgspec-0.21.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6badc03b9725352219cca017bfe71c61f2fbd0fb5982b410ac17c97c213deb30"},
|
||||||
|
{file = "msgspec-0.21.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5d2d4116ebe3035a78d9ec76e99a9d64e5fa6d44fe61a9c5de7fd1acf54bcc69"},
|
||||||
|
{file = "msgspec-0.21.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0d1009f6715f5bff3b54d4ff5c7428ad96197e0534e1645b8e9b955890c84664"},
|
||||||
|
{file = "msgspec-0.21.1-cp311-cp311-win_amd64.whl", hash = "sha256:c6faffe5bb644ec884052679af4dfd776d4b5ca90e4a7ec7e7e319e4e6b93a6e"},
|
||||||
|
{file = "msgspec-0.21.1-cp311-cp311-win_arm64.whl", hash = "sha256:ee9e3f11fa94603f7d673bf795cfa31b549c4a2c723bc39b45beb1e7f5a3fb99"},
|
||||||
|
{file = "msgspec-0.21.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4248cf0b6129b7d230eacd493c17cc2d4f3989f3bb7f633a928a85b7dcfa251"},
|
||||||
|
{file = "msgspec-0.21.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5102c7e9b3acff82178449b85006d96310e690291bb1ea0142f1b24bcb8aabcb"},
|
||||||
|
{file = "msgspec-0.21.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:846758412e9518252b2ac9bffd6f0e54d9ff614f5f9488df7749f81ff5c80920"},
|
||||||
|
{file = "msgspec-0.21.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21995e74b5c598c2e004110ad66ec7f1b8c20bf2bcf3b2de8fd9a3094422d3ff"},
|
||||||
|
{file = "msgspec-0.21.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6129f0cca52992e898fd5344187f7c8127b63d810b2fd73e36fca73b4c6475ee"},
|
||||||
|
{file = "msgspec-0.21.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ef3ec2296248d1f8b9231acb051b6d471dfde8f21819e86c9adaaa9f42918521"},
|
||||||
|
{file = "msgspec-0.21.1-cp312-cp312-win_amd64.whl", hash = "sha256:d4ab834a054c6f0cbeef6df9e7e1b33d5f1bc7b86dea1d2fd7cad003873e783d"},
|
||||||
|
{file = "msgspec-0.21.1-cp312-cp312-win_arm64.whl", hash = "sha256:628aaa35c74950a8c59da330d7e98917e1c7188f983745782027748ee4ca573e"},
|
||||||
|
{file = "msgspec-0.21.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:764173717a01743f007e9f74520ed281f24672c604514f7d76c1c3a10e8edb66"},
|
||||||
|
{file = "msgspec-0.21.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:344c7cd0eaed1fb81d7959f99100ef71ec9b536881a376f11b9a6c4803365697"},
|
||||||
|
{file = "msgspec-0.21.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48943e278b3854c2f89f955ddc6f9f430d3f0784b16e47d10604ee0463cd21f5"},
|
||||||
|
{file = "msgspec-0.21.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9aa659ebb0101b1cbc31461212b87e341d961f0ab0772aaf068a99e001ec4aa"},
|
||||||
|
{file = "msgspec-0.21.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7b27d1a8ead2b6f5b0c4f2d07b8be1ccfcc041c8a0e704781edebe3ae13c484"},
|
||||||
|
{file = "msgspec-0.21.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:38fe93e86b61328fe544cb7fd871fad5a27c8734bfda90f65e5dbe288ae50f61"},
|
||||||
|
{file = "msgspec-0.21.1-cp313-cp313-win_amd64.whl", hash = "sha256:8bc666331c35fcce05a7cd2d6221adbe0f6058f8e750711413d22793c080ac6a"},
|
||||||
|
{file = "msgspec-0.21.1-cp313-cp313-win_arm64.whl", hash = "sha256:42bb1241e0750c1a4346f2aa84db26c5ffd99a4eb3a954927d9f149ff2f42898"},
|
||||||
|
{file = "msgspec-0.21.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fab48eb45fdbfbdb2c0edfec00ffc53b6b6085beefc6b50b61e01659f9f8757f"},
|
||||||
|
{file = "msgspec-0.21.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3cb779ea0c35bc807ff941d415875c1f69ca0be91a2e907ab99a171811d86a9a"},
|
||||||
|
{file = "msgspec-0.21.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68604db36b3b4dd9bf160e436e12798a4738848144cea1aca1cb984011eb160f"},
|
||||||
|
{file = "msgspec-0.21.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d6b9dc50948eaf65df54d2fd0ff66e6d8c32f116037209ee861810eb9b676cb"},
|
||||||
|
{file = "msgspec-0.21.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:52c5e21930942302394429c5a582ce7e6b62c7f983b3760834c2ce107e0dd6df"},
|
||||||
|
{file = "msgspec-0.21.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:abbb39d65681fa24ed394e01af3d59d869068324f900c61d06062b7fb9980f2f"},
|
||||||
|
{file = "msgspec-0.21.1-cp314-cp314-win_amd64.whl", hash = "sha256:5666b1b560b97b6ec2eb3fca8a502298ebac56e13bbca1f88523538ce83d01ea"},
|
||||||
|
{file = "msgspec-0.21.1-cp314-cp314-win_arm64.whl", hash = "sha256:d8b8578e4c83b14ceea4cef0d0b747e31d9330fe4b03b2b2ad4063866a178f93"},
|
||||||
|
{file = "msgspec-0.21.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:15f523d51c00ebad412213bfe9f06f0a50ec2b93e0c19e824a2d267cabb48ea2"},
|
||||||
|
{file = "msgspec-0.21.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4e47390360583ba3d5c6cb44cf0a9f61b0a06a899d3c2c00627cedebb2e2884b"},
|
||||||
|
{file = "msgspec-0.21.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f60800e6299b798142dc40b0644da77ceac5ea0568be58228417eae14135c847"},
|
||||||
|
{file = "msgspec-0.21.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5f8e9dfcd98419cf7568808470c4317a3fb30bef0e3715b568730a2b272a20d7"},
|
||||||
|
{file = "msgspec-0.21.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92d89dfad13bd1ea640dc3e37e724ed380da1030b272bdf5ecafb983c3ad7c75"},
|
||||||
|
{file = "msgspec-0.21.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0d03867786e5d7ba25d666df4b11320c27170f4aeafcb8e3a8b0a50a4fb742ca"},
|
||||||
|
{file = "msgspec-0.21.1-cp314-cp314t-win_amd64.whl", hash = "sha256:740fbf1c9d59992ca3537d6fbe9ebbf9eaf726a65fbf31448e0ecbc710697a63"},
|
||||||
|
{file = "msgspec-0.21.1-cp314-cp314t-win_arm64.whl", hash = "sha256:0d2cc73df6058d811a126ac3a8ad63a4dfa210c82f9cf5a004802eaf4712de90"},
|
||||||
|
{file = "msgspec-0.21.1.tar.gz", hash = "sha256:2313508e394b0d208f8f56892ca9b2799e2561329de9763b19619595a6c0f72c"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
toml = ["tomli ; python_version < \"3.11\"", "tomli_w"]
|
||||||
|
yaml = ["pyyaml"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nanoid"
|
||||||
|
version = "2.0.0"
|
||||||
|
description = "A tiny, secure, URL-friendly, unique string ID generator for Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "nanoid-2.0.0-py3-none-any.whl", hash = "sha256:90aefa650e328cffb0893bbd4c236cfd44c48bc1f2d0b525ecc53c3187b653bb"},
|
||||||
|
{file = "nanoid-2.0.0.tar.gz", hash = "sha256:5a80cad5e9c6e9ae3a41fa2fb34ae189f7cb420b2a5d8f82bd9d23466e4efa68"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "26.2"
|
||||||
|
description = "Core utilities for Python packages"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e"},
|
||||||
|
{file = "packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pluggy"
|
||||||
|
version = "1.6.0"
|
||||||
|
description = "plugin and hook calling mechanisms for python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"},
|
||||||
|
{file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["pre-commit", "tox"]
|
||||||
|
testing = ["coverage", "pytest", "pytest-benchmark"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pygments"
|
||||||
|
version = "2.20.0"
|
||||||
|
description = "Pygments is a syntax highlighting package written in Python."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main", "dev"]
|
||||||
|
files = [
|
||||||
|
{file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"},
|
||||||
|
{file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
windows-terminal = ["colorama (>=0.4.6)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest"
|
||||||
|
version = "9.0.3"
|
||||||
|
description = "pytest: simple powerful testing with Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9"},
|
||||||
|
{file = "pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
|
||||||
|
iniconfig = ">=1.0.1"
|
||||||
|
packaging = ">=22"
|
||||||
|
pluggy = ">=1.5,<2"
|
||||||
|
pygments = ">=2.7.2"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-cov"
|
||||||
|
version = "7.1.0"
|
||||||
|
description = "Pytest plugin for measuring coverage."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678"},
|
||||||
|
{file = "pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
coverage = {version = ">=7.10.6", extras = ["toml"]}
|
||||||
|
pluggy = ">=1.2"
|
||||||
|
pytest = ">=7"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
testing = ["process-tests", "pytest-xdist", "virtualenv"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-dotenv"
|
||||||
|
version = "1.2.2"
|
||||||
|
description = "Read key-value pairs from a .env file and set them as environment variables"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a"},
|
||||||
|
{file = "python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
cli = ["click (>=5.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyyaml"
|
||||||
|
version = "6.0.3"
|
||||||
|
description = "YAML parser and emitter for Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"},
|
||||||
|
{file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"},
|
||||||
|
{file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"},
|
||||||
|
{file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"},
|
||||||
|
{file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"},
|
||||||
|
{file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"},
|
||||||
|
{file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"},
|
||||||
|
{file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"},
|
||||||
|
{file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"},
|
||||||
|
{file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"},
|
||||||
|
{file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"},
|
||||||
|
{file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"},
|
||||||
|
{file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"},
|
||||||
|
{file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"},
|
||||||
|
{file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"},
|
||||||
|
{file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"},
|
||||||
|
{file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"},
|
||||||
|
{file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"},
|
||||||
|
{file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"},
|
||||||
|
{file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"},
|
||||||
|
{file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"},
|
||||||
|
{file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"},
|
||||||
|
{file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"},
|
||||||
|
{file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"},
|
||||||
|
{file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"},
|
||||||
|
{file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"},
|
||||||
|
{file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"},
|
||||||
|
{file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"},
|
||||||
|
{file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"},
|
||||||
|
{file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"},
|
||||||
|
{file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"},
|
||||||
|
{file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"},
|
||||||
|
{file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"},
|
||||||
|
{file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"},
|
||||||
|
{file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"},
|
||||||
|
{file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"},
|
||||||
|
{file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"},
|
||||||
|
{file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"},
|
||||||
|
{file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"},
|
||||||
|
{file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"},
|
||||||
|
{file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"},
|
||||||
|
{file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"},
|
||||||
|
{file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"},
|
||||||
|
{file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"},
|
||||||
|
{file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"},
|
||||||
|
{file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"},
|
||||||
|
{file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"},
|
||||||
|
{file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"},
|
||||||
|
{file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"},
|
||||||
|
{file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"},
|
||||||
|
{file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"},
|
||||||
|
{file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"},
|
||||||
|
{file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"},
|
||||||
|
{file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"},
|
||||||
|
{file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"},
|
||||||
|
{file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"},
|
||||||
|
{file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"},
|
||||||
|
{file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"},
|
||||||
|
{file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"},
|
||||||
|
{file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"},
|
||||||
|
{file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"},
|
||||||
|
{file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"},
|
||||||
|
{file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"},
|
||||||
|
{file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"},
|
||||||
|
{file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"},
|
||||||
|
{file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"},
|
||||||
|
{file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"},
|
||||||
|
{file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"},
|
||||||
|
{file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"},
|
||||||
|
{file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"},
|
||||||
|
{file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"},
|
||||||
|
{file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"},
|
||||||
|
{file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rich"
|
||||||
|
version = "15.0.0"
|
||||||
|
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9.0"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb"},
|
||||||
|
{file = "rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
markdown-it-py = ">=2.2.0"
|
||||||
|
pygments = ">=2.13.0,<3.0.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shellingham"
|
||||||
|
version = "1.5.4"
|
||||||
|
description = "Tool to Detect Surrounding Shell"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"},
|
||||||
|
{file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinydb"
|
||||||
|
version = "4.8.2"
|
||||||
|
description = "TinyDB is a tiny, document oriented database optimized for your happiness :)"
|
||||||
|
optional = false
|
||||||
|
python-versions = "<4.0,>=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "tinydb-4.8.2-py3-none-any.whl", hash = "sha256:f97030ee5cbc91eeadd1d7af07ab0e48ceb04aa63d4a983adbaca4cba16e86c3"},
|
||||||
|
{file = "tinydb-4.8.2.tar.gz", hash = "sha256:f7dfc39b8d7fda7a1ca62a8dbb449ffd340a117c1206b68c50b1a481fb95181d"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typer"
|
||||||
|
version = "0.25.0"
|
||||||
|
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "typer-0.25.0-py3-none-any.whl", hash = "sha256:ac01b48823d3db9a83c9e164338057eadbb1c9957a2a6b4eeb486669c560b5dc"},
|
||||||
|
{file = "typer-0.25.0.tar.gz", hash = "sha256:123eaf9f19bb40fd268310e12a542c0c6b4fab9c98d9d23342a01ff95e3ce930"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
annotated-doc = ">=0.0.2"
|
||||||
|
click = ">=8.2.1"
|
||||||
|
rich = ">=13.8.0"
|
||||||
|
shellingham = ">=1.3.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "werkzeug"
|
||||||
|
version = "3.1.8"
|
||||||
|
description = "The comprehensive WSGI web application library."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50"},
|
||||||
|
{file = "werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
markupsafe = ">=2.1.1"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
watchdog = ["watchdog (>=2.3)"]
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
lock-version = "2.1"
|
||||||
|
python-versions = "^3.11"
|
||||||
|
content-hash = "da7cc095f36ea5b79ca6deb213520dbc0c3bfd4598a105f4659bfe4ba4d50239"
|
||||||
|
|
@ -17,8 +17,8 @@ flask = "*"
|
||||||
tinydb = "^4.8.2"
|
tinydb = "^4.8.2"
|
||||||
pyyaml = "^6.0.2"
|
pyyaml = "^6.0.2"
|
||||||
nanoid = "^2.0.0"
|
nanoid = "^2.0.0"
|
||||||
# grung-db = {git = "https://git.evilchi.li/evilchili/grung-db.git"}
|
# grung-db = {git = "file:///home/greg/dev/grung-db/"}
|
||||||
grung-db = {git = "file:///home/greg/dev/grung-db/"}
|
grung-db = {git = "https://git.evilchi.li/evilchili/grung-db.git"}
|
||||||
flask-session = "^0.8.0"
|
flask-session = "^0.8.0"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,11 @@
|
||||||
|
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<!-- for converting markdown to html -->
|
<!-- ribbit editor library -->
|
||||||
<script src="{{ url_for('static', filename='purify.min.js' ) }}"></script>
|
<script src="{{ url_for('static', filename='ribbit.js' ) }}"></script>
|
||||||
<script src="{{ url_for('static', filename='marked.umd.min.js' ) }}"></script>
|
<!-- ttfrog app code -->
|
||||||
<script src="{{ url_for('static', filename='froghat.js' ) }}"></script>
|
<script src="{{ url_for('static', filename='froghat.js' ) }}"></script>
|
||||||
{% if user.can_write(page) %}
|
{% if user.can_write(page) %}
|
||||||
<script src="{{ url_for('static', filename='turndown.js' ) }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='joplin-turndown-plugin-gfm.js' ) }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='froghat-editor.js' ) }}"></script>
|
<script src="{{ url_for('static', filename='froghat-editor.js' ) }}"></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<script>
|
<script>
|
||||||
|
|
|
||||||
|
|
@ -227,14 +227,6 @@ class FroghatEditor extends Froghat {
|
||||||
|
|
||||||
this.toolbar = new FroghatToolbar({editor: this});
|
this.toolbar = new FroghatToolbar({editor: this});
|
||||||
|
|
||||||
this.turndown = new TurndownService({
|
|
||||||
headingStyle: 'atx',
|
|
||||||
codeBlockStyle: 'fenced',
|
|
||||||
emDelimiter: '*',
|
|
||||||
strongDelimiter: '**',
|
|
||||||
});
|
|
||||||
this.turndown.use([turndownPluginGfm.gfm, turndownPluginGfm.tables]);
|
|
||||||
this.turndown.keep(['pre']);
|
|
||||||
this.#bindEvents();
|
this.#bindEvents();
|
||||||
|
|
||||||
this.plugins().forEach(plugin => { plugin.setEditable() });
|
this.plugins().forEach(plugin => { plugin.setEditable() });
|
||||||
|
|
@ -326,7 +318,7 @@ class FroghatEditor extends Froghat {
|
||||||
|
|
||||||
|
|
||||||
htmlToMarkdown(html) {
|
htmlToMarkdown(html) {
|
||||||
return this.turndown.turndown(html || this.element.innerHTML);
|
return hopdown.toMarkdown(html || this.element.innerHTML);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMarkdown() {
|
getMarkdown() {
|
||||||
|
|
|
||||||
|
|
@ -57,116 +57,23 @@ FroghatAPIv1 = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
class Froghat {
|
/*
|
||||||
|
* Froghat and FroghatPlugin are provided by the ribbit library
|
||||||
|
* as Ribbit and RibbitPlugin. Alias them for backward compatibility.
|
||||||
|
*/
|
||||||
|
class Froghat extends Ribbit {
|
||||||
constructor(settings) {
|
constructor(settings) {
|
||||||
/*
|
super({ ...settings, api: settings.api || FroghatAPIv1, editorId: settings.editorId || 'froghat' });
|
||||||
* Create a new Froghat instance.
|
|
||||||
*/
|
|
||||||
this.api = settings.api || FroghatAPIv1;
|
|
||||||
|
|
||||||
this.element = document.getElementById(settings.editorId || 'froghat');
|
|
||||||
|
|
||||||
this.marked = marked;
|
|
||||||
this.marked.use({
|
|
||||||
breaks: false,
|
|
||||||
gfm: true,
|
|
||||||
});
|
|
||||||
this.states = {
|
|
||||||
VIEW: 'view',
|
|
||||||
}
|
|
||||||
this.cachedHTML = null;
|
|
||||||
this.cachedMarkdown = null;
|
|
||||||
this.state = null;
|
|
||||||
this.changed = false;
|
|
||||||
this.enabledPlugins = {};
|
|
||||||
|
|
||||||
settings.plugins.forEach(plugin => {
|
|
||||||
this.enabledPlugins[plugin.name] = new plugin({name: plugin.name, wiki: this});
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run() {
|
|
||||||
this.element.classList.add("loaded");
|
|
||||||
this.view();
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins() {
|
|
||||||
return Object.values(this.enabledPlugins).sort((a, b) => { a.precedence < b.precedence });
|
|
||||||
}
|
|
||||||
|
|
||||||
getState() {
|
|
||||||
return this.state;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(newState) {
|
|
||||||
this.state = newState;
|
|
||||||
Object.values(this.states).forEach(state => {
|
|
||||||
if (state == newState) {
|
|
||||||
this.element.classList.add(state);
|
|
||||||
} else {
|
|
||||||
this.element.classList.remove(state);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
markdownToHTML(md) {
|
|
||||||
var html = this.marked.parse(md);
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
getHTML(string) {
|
|
||||||
/*
|
|
||||||
* Convert the markdown source to HTML.
|
|
||||||
*/
|
|
||||||
if (this.changed || !this.cachedHTML) {
|
|
||||||
this.cachedHTML = this.markdownToHTML(this.getMarkdown());
|
|
||||||
}
|
|
||||||
return this.cachedHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
getMarkdown() {
|
|
||||||
if (!this.cachedMarkdown) {
|
|
||||||
this.cachedMarkdown = this.element.textContent;
|
|
||||||
}
|
|
||||||
return this.cachedMarkdown;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
view() {
|
view() {
|
||||||
/*
|
super.view();
|
||||||
* Convert the wiki read-only mode and display the current HTML.
|
const main = document.getElementById("main");
|
||||||
*/
|
if (main) main.classList.remove("editing");
|
||||||
if (this.getState() === this.states.VIEW) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.element.innerHTML = this.getHTML();
|
|
||||||
this.setState(this.states.VIEW);
|
|
||||||
this.element.contentEditable = false;
|
|
||||||
document.getElementById("main").classList.remove("editing");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class FroghatPlugin {
|
class FroghatPlugin extends RibbitPlugin {};
|
||||||
|
|
||||||
constructor(settings) {
|
|
||||||
this.name = settings.name;
|
|
||||||
this.wiki = settings.wiki;
|
|
||||||
this.precedence = 50;
|
|
||||||
};
|
|
||||||
|
|
||||||
setEditable() {
|
|
||||||
};
|
|
||||||
|
|
||||||
toMarkdown(html) {
|
|
||||||
return html;
|
|
||||||
};
|
|
||||||
|
|
||||||
toHTML(md) {
|
|
||||||
return md;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
WIDGETS = {};
|
WIDGETS = {};
|
||||||
|
|
@ -465,94 +372,14 @@ class MacroPlugin extends FroghatPlugin {
|
||||||
|
|
||||||
const plugin = this;
|
const plugin = this;
|
||||||
|
|
||||||
this.wiki.marked.use({
|
// TODO: Re-implement macro pre/post-processing as ribbit Tag hooks.
|
||||||
extensions: [
|
// The marked.use() hooks for multiline macros and the postprocess
|
||||||
{
|
// pipeline need to be converted to custom Tags.
|
||||||
name: 'heading',
|
|
||||||
renderer(token) {
|
|
||||||
var ref = camelCase(token.text).join("");
|
|
||||||
return `<h${token.depth} id='${ref}'>${token.text}</h${token.depth}>`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
hooks: {
|
|
||||||
preprocess: (source) => {
|
|
||||||
const matched = source.matchAll(plugin.multilinePattern);
|
|
||||||
var md = source;
|
|
||||||
matched.forEach(match => {
|
|
||||||
var wrapper = '<span class="macro" data-plugin-name="macro" data-macro-name="multiline" data-inline="true" style="display: inline-block;">';
|
|
||||||
var content = decodeHtmlEntities(match.groups.content)
|
|
||||||
.replaceAll("\n", "::BR::")
|
|
||||||
.replaceAll('`', '::QU::')
|
|
||||||
.replaceAll('{', '::OC::')
|
|
||||||
.replaceAll('}', '::CC::');
|
|
||||||
md = md.replaceAll(match[0], wrapper + '\0' + content + '\0</span>');
|
|
||||||
});
|
|
||||||
return md;
|
|
||||||
},
|
|
||||||
postprocess: (html) => {
|
|
||||||
plugin.getTokens(plugin.pattern, html).forEach(token => {
|
|
||||||
var pat = new RegExp('(?<!<pre>.+?)' + token.source, 'mg');
|
|
||||||
html = html.replaceAll(pat, token.rendered);
|
|
||||||
});
|
|
||||||
html = html.replaceAll(plugin.endPattern, '</div>');
|
|
||||||
Object.values(plugin.macros).forEach(macro => {
|
|
||||||
if (macro.postprocess) {
|
|
||||||
html = macro.postprocess(html);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
html = html.replaceAll("::BR::", "<br>")
|
|
||||||
.replaceAll('::QU::', '`')
|
|
||||||
.replaceAll('::OC::', '{')
|
|
||||||
.replaceAll('::CC::', '}');
|
|
||||||
|
|
||||||
// remove unsafe html tags
|
|
||||||
return DOMPurify.sanitize(html, {});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setEditable() {
|
setEditable() {
|
||||||
const plugin = this;
|
// TODO: Re-implement macro HTML→markdown conversion as a ribbit Tag.
|
||||||
this.wiki.turndown.addRule('macros', {
|
// The turndown macro rule needs to be converted to Tag.toMarkdown().
|
||||||
filter: function (node, options) {
|
|
||||||
return ((node.nodeName === 'ASIDE' || node.nodeName === 'DIV' || node.nodeName === 'SPAN') && node.dataset.pluginName == 'macro')
|
|
||||||
},
|
|
||||||
replacement: function (content, node, options) {
|
|
||||||
var macro = plugin.macros[node.getAttribute('data-macro-name')];
|
|
||||||
|
|
||||||
if (macro.fromHTML) {
|
|
||||||
return macro.fromHTML(node, content);
|
|
||||||
}
|
|
||||||
|
|
||||||
var md = '{{' + node.dataset.macroName;
|
|
||||||
if (node.dataset.keywords) {
|
|
||||||
md += " " + node.dataset.keywords;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var paramName in node.dataset) {
|
|
||||||
if (paramName.indexOf("param") != 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
md += ` ${paramName.replace('param', '').toLowerCase()}="${node.dataset[paramName]}"`
|
|
||||||
};
|
|
||||||
|
|
||||||
if (node.dataset.inline == "false") {
|
|
||||||
md = `\n\n${md}\n\n`;
|
|
||||||
md += plugin.wiki.htmlToMarkdown(node.innerHTML);
|
|
||||||
md += "\n\n}}\n\n";
|
|
||||||
} else {
|
|
||||||
md += "}}";
|
|
||||||
}
|
|
||||||
|
|
||||||
// replace nulls with line breaks, for the multiline macro
|
|
||||||
md = md.replaceAll('\0', "\n");
|
|
||||||
|
|
||||||
return md;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,242 +0,0 @@
|
||||||
var turndownPluginGfm = (function (exports) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var highlightRegExp = /highlight-(?:text|source)-([a-z0-9]+)/;
|
|
||||||
|
|
||||||
function highlightedCodeBlock (turndownService) {
|
|
||||||
turndownService.addRule('highlightedCodeBlock', {
|
|
||||||
filter: function (node) {
|
|
||||||
var firstChild = node.firstChild;
|
|
||||||
return (
|
|
||||||
node.nodeName === 'DIV' &&
|
|
||||||
highlightRegExp.test(node.className) &&
|
|
||||||
firstChild &&
|
|
||||||
firstChild.nodeName === 'PRE'
|
|
||||||
)
|
|
||||||
},
|
|
||||||
replacement: function (content, node, options) {
|
|
||||||
var className = node.className || '';
|
|
||||||
var language = (className.match(highlightRegExp) || [null, ''])[1];
|
|
||||||
|
|
||||||
return (
|
|
||||||
'\n\n' + options.fence + language + '\n' +
|
|
||||||
node.firstChild.textContent +
|
|
||||||
'\n' + options.fence + '\n\n'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function strikethrough (turndownService) {
|
|
||||||
turndownService.addRule('strikethrough', {
|
|
||||||
filter: ['del', 's', 'strike'],
|
|
||||||
replacement: function (content) {
|
|
||||||
return '~' + content + '~'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var indexOf = Array.prototype.indexOf;
|
|
||||||
var every = Array.prototype.every;
|
|
||||||
var rules = {};
|
|
||||||
|
|
||||||
rules.tableCell = {
|
|
||||||
filter: ['th', 'td'],
|
|
||||||
replacement: function (content, node) {
|
|
||||||
if (tableShouldBeSkipped(nodeParentTable(node))) return content;
|
|
||||||
return cell(content, node)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rules.tableRow = {
|
|
||||||
filter: 'tr',
|
|
||||||
replacement: function (content, node) {
|
|
||||||
const parentTable = nodeParentTable(node);
|
|
||||||
if (tableShouldBeSkipped(parentTable)) return content;
|
|
||||||
|
|
||||||
var borderCells = '';
|
|
||||||
var alignMap = { left: ':--', right: '--:', center: ':-:' };
|
|
||||||
|
|
||||||
if (isHeadingRow(node)) {
|
|
||||||
const colCount = tableColCount(parentTable);
|
|
||||||
for (var i = 0; i < colCount; i++) {
|
|
||||||
const childNode = colCount >= node.childNodes.length ? null : node.childNodes[i];
|
|
||||||
var border = '---';
|
|
||||||
var align = childNode ? (childNode.getAttribute('align') || '').toLowerCase() : '';
|
|
||||||
|
|
||||||
if (align) border = alignMap[align] || border;
|
|
||||||
|
|
||||||
if (childNode) {
|
|
||||||
borderCells += cell(border, node.childNodes[i]);
|
|
||||||
} else {
|
|
||||||
borderCells += cell(border, null, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return '\n' + content + (borderCells ? '\n' + borderCells : '')
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rules.table = {
|
|
||||||
// Only convert tables with a heading row.
|
|
||||||
// Tables with no heading row are kept using `keep` (see below).
|
|
||||||
filter: function (node) {
|
|
||||||
return node.nodeName === 'TABLE'
|
|
||||||
},
|
|
||||||
|
|
||||||
replacement: function (content, node) {
|
|
||||||
if (tableShouldBeSkipped(node)) return content;
|
|
||||||
|
|
||||||
// Ensure there are no blank lines
|
|
||||||
content = content.replace(/\n+/g, '\n');
|
|
||||||
|
|
||||||
// If table has no heading, add an empty one so as to get a valid Markdown table
|
|
||||||
var secondLine = content.trim().split('\n');
|
|
||||||
if (secondLine.length >= 2) secondLine = secondLine[1];
|
|
||||||
var secondLineIsDivider = secondLine.indexOf('| ---') === 0;
|
|
||||||
|
|
||||||
var columnCount = tableColCount(node);
|
|
||||||
var emptyHeader = '';
|
|
||||||
if (columnCount && !secondLineIsDivider) {
|
|
||||||
emptyHeader = '|' + ' |'.repeat(columnCount) + '\n' + '|' + ' --- |'.repeat(columnCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
return '\n\n' + emptyHeader + content + '\n\n'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rules.tableSection = {
|
|
||||||
filter: ['thead', 'tbody', 'tfoot'],
|
|
||||||
replacement: function (content) {
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// A tr is a heading row if:
|
|
||||||
// - the parent is a THEAD
|
|
||||||
// - or if its the first child of the TABLE or the first TBODY (possibly
|
|
||||||
// following a blank THEAD)
|
|
||||||
// - and every cell is a TH
|
|
||||||
function isHeadingRow (tr) {
|
|
||||||
var parentNode = tr.parentNode;
|
|
||||||
return (
|
|
||||||
parentNode.nodeName === 'THEAD' ||
|
|
||||||
(
|
|
||||||
parentNode.firstChild === tr &&
|
|
||||||
(parentNode.nodeName === 'TABLE' || isFirstTbody(parentNode)) &&
|
|
||||||
every.call(tr.childNodes, function (n) { return n.nodeName === 'TH' })
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isFirstTbody (element) {
|
|
||||||
var previousSibling = element.previousSibling;
|
|
||||||
return (
|
|
||||||
element.nodeName === 'TBODY' && (
|
|
||||||
!previousSibling ||
|
|
||||||
(
|
|
||||||
previousSibling.nodeName === 'THEAD' &&
|
|
||||||
/^\s*$/i.test(previousSibling.textContent)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function cell (content, node = null, index = null) {
|
|
||||||
if (index === null) index = indexOf.call(node.parentNode.childNodes, node);
|
|
||||||
var prefix = ' ';
|
|
||||||
if (index === 0) prefix = '| ';
|
|
||||||
let filteredContent = content.trim().replace(/\n\r/g, '<br>').replace(/\n/g, "<br>");
|
|
||||||
filteredContent = filteredContent.replace(/\|+/g, '\\|');
|
|
||||||
while (filteredContent.length < 3) filteredContent += ' ';
|
|
||||||
if (node) filteredContent = handleColSpan(filteredContent, node, ' ');
|
|
||||||
return prefix + filteredContent + ' |'
|
|
||||||
}
|
|
||||||
|
|
||||||
function nodeContainsTable(node) {
|
|
||||||
if (!node.childNodes) return false;
|
|
||||||
|
|
||||||
for (let i = 0; i < node.childNodes.length; i++) {
|
|
||||||
const child = node.childNodes[i];
|
|
||||||
if (child.nodeName === 'TABLE') return true;
|
|
||||||
if (nodeContainsTable(child)) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Various conditions under which a table should be skipped - i.e. each cell
|
|
||||||
// will be rendered one after the other as if they were paragraphs.
|
|
||||||
function tableShouldBeSkipped(tableNode) {
|
|
||||||
if (!tableNode) return true;
|
|
||||||
if (!tableNode.rows) return true;
|
|
||||||
if (tableNode.rows.length === 1 && tableNode.rows[0].childNodes.length <= 1) return true; // Table with only one cell
|
|
||||||
|
|
||||||
// Not sure why we're excluding this. possibly because it'll freak out the parser? --evilchili
|
|
||||||
//if (nodeContainsTable(tableNode)) return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function nodeParentTable(node) {
|
|
||||||
let parent = node.parentNode;
|
|
||||||
while (parent.nodeName !== 'TABLE') {
|
|
||||||
parent = parent.parentNode;
|
|
||||||
if (!parent) return null;
|
|
||||||
}
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleColSpan(content, node, emptyChar) {
|
|
||||||
const colspan = node.getAttribute('colspan') || 1;
|
|
||||||
for (let i = 1; i < colspan; i++) {
|
|
||||||
content += ' | ' + emptyChar.repeat(3);
|
|
||||||
}
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
|
|
||||||
function tableColCount(node) {
|
|
||||||
let maxColCount = 0;
|
|
||||||
for (let i = 0; i < node.rows.length; i++) {
|
|
||||||
const row = node.rows[i];
|
|
||||||
const colCount = row.childNodes.length;
|
|
||||||
if (colCount > maxColCount) maxColCount = colCount;
|
|
||||||
}
|
|
||||||
return maxColCount
|
|
||||||
}
|
|
||||||
|
|
||||||
function tables (turndownService) {
|
|
||||||
turndownService.keep(function (node) {
|
|
||||||
return node.nodeName === 'TABLE'
|
|
||||||
});
|
|
||||||
for (var key in rules) turndownService.addRule(key, rules[key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function taskListItems (turndownService) {
|
|
||||||
turndownService.addRule('taskListItems', {
|
|
||||||
filter: function (node) {
|
|
||||||
return node.type === 'checkbox' && node.parentNode.nodeName === 'LI'
|
|
||||||
},
|
|
||||||
replacement: function (content, node) {
|
|
||||||
return (node.checked ? '[x]' : '[ ]') + ' '
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function gfm (turndownService) {
|
|
||||||
turndownService.use([
|
|
||||||
highlightedCodeBlock,
|
|
||||||
strikethrough,
|
|
||||||
tables,
|
|
||||||
taskListItems
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.gfm = gfm;
|
|
||||||
exports.highlightedCodeBlock = highlightedCodeBlock;
|
|
||||||
exports.strikethrough = strikethrough;
|
|
||||||
exports.tables = tables;
|
|
||||||
exports.taskListItems = taskListItems;
|
|
||||||
|
|
||||||
return exports;
|
|
||||||
|
|
||||||
}({}));
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
58
src/ttfrog/themes/default/static/ribbit.css
Normal file
58
src/ttfrog/themes/default/static/ribbit.css
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* ribbit.css — editor styles for the ribbit WYSIWYG markdown editor.
|
||||||
|
*
|
||||||
|
* Provides base content formatting and editor state styles.
|
||||||
|
* Override with your own theme CSS for custom look and feel.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ── Content formatting ──────────────────────────────── */
|
||||||
|
|
||||||
|
a { text-decoration: none; }
|
||||||
|
|
||||||
|
q, blockquote {
|
||||||
|
margin-left: 30px;
|
||||||
|
font-size: 1.3em;
|
||||||
|
font-style: italic;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
table { width: 100%; }
|
||||||
|
th { border-bottom: 1px solid #000; padding: 3px; }
|
||||||
|
th, td { padding: 2px; }
|
||||||
|
table td table { max-width: 95%; }
|
||||||
|
|
||||||
|
pre {
|
||||||
|
border: 1px dashed black;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 5px;
|
||||||
|
background: #EEE;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px dashed black;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
background: #EEE;
|
||||||
|
margin: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Editor states ───────────────────────────────────── */
|
||||||
|
|
||||||
|
#ribbit {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ribbit.loaded {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ribbit.edit {
|
||||||
|
font-family: monospace;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ribbit.wysiwyg .md {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
775
src/ttfrog/themes/default/static/ribbit.js
Normal file
775
src/ttfrog/themes/default/static/ribbit.js
Normal file
|
|
@ -0,0 +1,775 @@
|
||||||
|
"use strict";
|
||||||
|
(() => {
|
||||||
|
// src/tags.ts
|
||||||
|
function inlineTag(def) {
|
||||||
|
const escaped = def.delimiter.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
const matchPattern = new RegExp("^" + escaped + "(.+?)" + escaped);
|
||||||
|
const globalPattern = new RegExp(escaped + "(.+?)" + escaped, "g");
|
||||||
|
const upperTag = def.htmlTag.toUpperCase();
|
||||||
|
const selector = [upperTag, ...(def.aliases || "").split(",").filter(Boolean)].join(",");
|
||||||
|
const recursive = def.recursive !== false;
|
||||||
|
return {
|
||||||
|
name: def.name,
|
||||||
|
precedence: def.precedence ?? 50,
|
||||||
|
recursive,
|
||||||
|
pattern: globalPattern,
|
||||||
|
delimiter: def.delimiter,
|
||||||
|
match: (context) => {
|
||||||
|
const matched = context.text.slice(context.offset).match(matchPattern);
|
||||||
|
if (!matched) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
content: matched[1],
|
||||||
|
raw: matched[0],
|
||||||
|
consumed: matched[0].length
|
||||||
|
};
|
||||||
|
},
|
||||||
|
toHTML: (token, convert) => {
|
||||||
|
const inner = recursive ? convert.inline(token.content) : escapeHtml(token.content);
|
||||||
|
return `<${def.htmlTag}>${inner}</${def.htmlTag}>`;
|
||||||
|
},
|
||||||
|
selector,
|
||||||
|
toMarkdown: (element, convert) => {
|
||||||
|
if (!recursive && element.parentNode?.nodeName === "PRE") {
|
||||||
|
return convert.children(element);
|
||||||
|
}
|
||||||
|
return def.delimiter + convert.children(element) + def.delimiter;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function escapeHtml(source) {
|
||||||
|
return source.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
||||||
|
}
|
||||||
|
function camelId(text) {
|
||||||
|
return text.trim().split(/\s+/).map(
|
||||||
|
(word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
|
||||||
|
).join("");
|
||||||
|
}
|
||||||
|
function parseListBlock(lines, start, indent, inlineConvert) {
|
||||||
|
const prefix = new RegExp("^" + " ".repeat(indent) + "([\\*\\-]|\\d+\\.)\\s");
|
||||||
|
const isOl = /^\d+\./.test(lines[start].trim());
|
||||||
|
const tag = isOl ? "ol" : "ul";
|
||||||
|
const items = [];
|
||||||
|
let i = start;
|
||||||
|
while (i < lines.length) {
|
||||||
|
const line = lines[i];
|
||||||
|
if (/^\s*$/.test(line)) break;
|
||||||
|
const lineIndent = line.match(/^(\s*)/)[1].length;
|
||||||
|
if (lineIndent < indent) break;
|
||||||
|
if (lineIndent > indent) {
|
||||||
|
const sub = parseListBlock(lines, i, lineIndent, inlineConvert);
|
||||||
|
items[items.length - 1].sub = sub.html;
|
||||||
|
i = sub.end;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!prefix.test(line)) break;
|
||||||
|
items.push({
|
||||||
|
text: line.replace(prefix, ""),
|
||||||
|
sub: ""
|
||||||
|
});
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
const html = "<" + tag + ">" + items.map(
|
||||||
|
(item) => "<li>" + inlineConvert(item.text) + item.sub + "</li>"
|
||||||
|
).join("") + "</" + tag + ">";
|
||||||
|
return { html, end: i };
|
||||||
|
}
|
||||||
|
function listToMd(node, depth, convert) {
|
||||||
|
const isOl = node.nodeName === "OL";
|
||||||
|
const indent = " ".repeat(depth);
|
||||||
|
const lines = [];
|
||||||
|
Array.from(node.children).forEach((listItem, index) => {
|
||||||
|
const marker = isOl ? index + 1 + ". " : "- ";
|
||||||
|
let text = "";
|
||||||
|
let sublist = "";
|
||||||
|
Array.from(listItem.childNodes).forEach((child) => {
|
||||||
|
if (child.nodeType === 1 && (child.nodeName === "UL" || child.nodeName === "OL")) {
|
||||||
|
sublist += listToMd(child, depth + 1, convert);
|
||||||
|
} else {
|
||||||
|
text += convert.node(child);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
lines.push(indent + marker + text.trim());
|
||||||
|
if (sublist) lines.push(sublist);
|
||||||
|
});
|
||||||
|
const result = lines.join("\n");
|
||||||
|
return depth === 0 ? "\n\n" + result + "\n\n" : result;
|
||||||
|
}
|
||||||
|
function isBlockStart(lines, index) {
|
||||||
|
const line = lines[index];
|
||||||
|
if (/^(`{3,})/.test(line)) return true;
|
||||||
|
if (/^(\*{3,}|-{3,}|_{3,})\s*$/.test(line)) return true;
|
||||||
|
if (/^#{1,6}\s/.test(line)) return true;
|
||||||
|
if (/^>\s?/.test(line)) return true;
|
||||||
|
if (/^[*\-]\s/.test(line) || /^\d+\.\s/.test(line)) return true;
|
||||||
|
if (line.indexOf("|") !== -1 && index + 1 < lines.length && /^\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?\s*$/.test(lines[index + 1])) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function parseTableRow(line) {
|
||||||
|
return line.replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
|
||||||
|
}
|
||||||
|
function parseAligns(line) {
|
||||||
|
return parseTableRow(line).map((cell) => {
|
||||||
|
if (/^:-+:$/.test(cell)) return "center";
|
||||||
|
if (/^-+:$/.test(cell)) return "right";
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
var defaultBlockTags = {
|
||||||
|
"PRE": {
|
||||||
|
/*
|
||||||
|
* ```lang
|
||||||
|
* code here
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
name: "fencedCode",
|
||||||
|
match: (context) => {
|
||||||
|
const matched = context.lines[context.index].match(/^(`{3,})(.*)/);
|
||||||
|
if (!matched) return null;
|
||||||
|
const fence = matched[1], lang = matched[2].trim();
|
||||||
|
const code = [];
|
||||||
|
let i = context.index + 1;
|
||||||
|
while (i < context.lines.length && !context.lines[i].startsWith(fence))
|
||||||
|
code.push(context.lines[i++]);
|
||||||
|
return {
|
||||||
|
content: code.join("\n"),
|
||||||
|
raw: "",
|
||||||
|
consumed: i + 1 - context.index,
|
||||||
|
meta: {
|
||||||
|
lang
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
toHTML: (token) => "<pre><code" + (token.meta?.lang ? ` class="language-${escapeHtml(token.meta.lang)}"` : "") + ">" + escapeHtml(token.content) + "</code></pre>",
|
||||||
|
selector: "PRE",
|
||||||
|
toMarkdown: (element) => {
|
||||||
|
const code = element.querySelector("code");
|
||||||
|
const lang = (code?.getAttribute("class") || "").match(/language-(\S+)/)?.[1] || "";
|
||||||
|
return "\n\n```" + lang + "\n" + (code?.textContent || element.textContent || "") + "\n```\n\n";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"HR": {
|
||||||
|
/*
|
||||||
|
* ***
|
||||||
|
* ---
|
||||||
|
* ___
|
||||||
|
*/
|
||||||
|
name: "hr",
|
||||||
|
match: (context) => {
|
||||||
|
if (!/^(\*{3,}|-{3,}|_{3,})\s*$/.test(context.lines[context.index])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
content: "",
|
||||||
|
raw: "",
|
||||||
|
consumed: 1
|
||||||
|
};
|
||||||
|
},
|
||||||
|
toHTML: () => "<hr>",
|
||||||
|
selector: "HR",
|
||||||
|
toMarkdown: () => "\n\n---\n\n"
|
||||||
|
},
|
||||||
|
"H1,H2,H3,H4,H5,H6": {
|
||||||
|
/*
|
||||||
|
* # Heading 1
|
||||||
|
* ## Heading 2
|
||||||
|
* ### Heading 3
|
||||||
|
*/
|
||||||
|
name: "heading",
|
||||||
|
match: (context) => {
|
||||||
|
const matched = context.lines[context.index].match(/^(#{1,6})\s+(.*)/);
|
||||||
|
if (!matched) return null;
|
||||||
|
return {
|
||||||
|
content: matched[2].trim(),
|
||||||
|
raw: "",
|
||||||
|
consumed: 1,
|
||||||
|
meta: {
|
||||||
|
level: String(matched[1].length)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
toHTML: (token, convert) => `<h${token.meta.level} id='${camelId(token.content)}'>${convert.inline(token.content)}</h${token.meta.level}>`,
|
||||||
|
selector: "H1,H2,H3,H4,H5,H6",
|
||||||
|
toMarkdown: (element, convert) => "\n\n" + "#".repeat(parseInt(element.nodeName[1])) + " " + convert.children(element) + "\n\n"
|
||||||
|
},
|
||||||
|
"BLOCKQUOTE": {
|
||||||
|
/*
|
||||||
|
* > quoted text
|
||||||
|
* > more quoted text
|
||||||
|
*/
|
||||||
|
name: "blockquote",
|
||||||
|
match: (context) => {
|
||||||
|
if (!/^>\s?/.test(context.lines[context.index])) return null;
|
||||||
|
const lines = [];
|
||||||
|
let i = context.index;
|
||||||
|
while (i < context.lines.length && /^>\s?/.test(context.lines[i]))
|
||||||
|
lines.push(context.lines[i++].replace(/^>\s?/, ""));
|
||||||
|
return {
|
||||||
|
content: lines.join("\n"),
|
||||||
|
raw: "",
|
||||||
|
consumed: i - context.index
|
||||||
|
};
|
||||||
|
},
|
||||||
|
toHTML: (token, convert) => "<blockquote>" + convert.block(token.content) + "</blockquote>",
|
||||||
|
selector: "BLOCKQUOTE",
|
||||||
|
toMarkdown: (element, convert) => "\n\n" + convert.children(element).trim().split("\n").map((line) => "> " + line).join("\n") + "\n\n"
|
||||||
|
},
|
||||||
|
"UL,OL": {
|
||||||
|
/*
|
||||||
|
* - unordered item
|
||||||
|
* - unordered item
|
||||||
|
* - nested item
|
||||||
|
*
|
||||||
|
* 1. ordered item
|
||||||
|
* 2. ordered item
|
||||||
|
*/
|
||||||
|
name: "list",
|
||||||
|
match: (context) => {
|
||||||
|
const line = context.lines[context.index];
|
||||||
|
if (!/^[*\-]\s/.test(line) && !/^\d+\.\s/.test(line)) return null;
|
||||||
|
return {
|
||||||
|
content: "",
|
||||||
|
raw: "",
|
||||||
|
consumed: 0
|
||||||
|
};
|
||||||
|
},
|
||||||
|
toHTML: (token) => token.raw,
|
||||||
|
selector: "UL,OL",
|
||||||
|
toMarkdown: (element, convert) => listToMd(element, 0, convert)
|
||||||
|
},
|
||||||
|
"TABLE": {
|
||||||
|
/*
|
||||||
|
* | head 1 | head 2 |
|
||||||
|
* |--------|--------|
|
||||||
|
* | cell 1 | cell 2 |
|
||||||
|
*/
|
||||||
|
name: "table",
|
||||||
|
match: (context) => {
|
||||||
|
const { lines, index } = context;
|
||||||
|
if (lines[index].indexOf("|") === -1 || index + 1 >= lines.length) return null;
|
||||||
|
if (!/^\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?\s*$/.test(lines[index + 1])) return null;
|
||||||
|
const headers = parseTableRow(lines[index]);
|
||||||
|
const aligns = parseAligns(lines[index + 1]);
|
||||||
|
const rows = [];
|
||||||
|
let i = index + 2;
|
||||||
|
while (i < lines.length && lines[i].indexOf("|") !== -1 && !/^\s*$/.test(lines[i]))
|
||||||
|
rows.push(parseTableRow(lines[i++]));
|
||||||
|
return {
|
||||||
|
content: "",
|
||||||
|
raw: "",
|
||||||
|
consumed: i - index,
|
||||||
|
meta: {
|
||||||
|
headers: JSON.stringify(headers),
|
||||||
|
aligns: JSON.stringify(aligns),
|
||||||
|
rows: JSON.stringify(rows)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
toHTML: (token, convert) => {
|
||||||
|
const headers = JSON.parse(token.meta.headers);
|
||||||
|
const aligns = JSON.parse(token.meta.aligns);
|
||||||
|
const rows = JSON.parse(token.meta.rows);
|
||||||
|
function cell(tag, text, index) {
|
||||||
|
const align = aligns[index] ? ` style="text-align:${aligns[index]}"` : "";
|
||||||
|
return `<${tag}${align}>${convert.inline(text)}</${tag}>`;
|
||||||
|
}
|
||||||
|
const head = "<thead><tr>" + headers.map((text, i) => cell("th", text, i)).join("") + "</tr></thead>";
|
||||||
|
const body = rows.map(
|
||||||
|
(row) => "<tr>" + row.map((text, i) => cell("td", text, i)).join("") + "</tr>"
|
||||||
|
).join("");
|
||||||
|
return "<table>" + head + "<tbody>" + body + "</tbody></table>";
|
||||||
|
},
|
||||||
|
selector: "TABLE",
|
||||||
|
toMarkdown: (element, convert) => {
|
||||||
|
const rows = Array.from(element.querySelectorAll("tr"));
|
||||||
|
if (!rows.length) return "";
|
||||||
|
const headers = Array.from(rows[0].querySelectorAll("th,td")).map((cell) => convert.children(cell).trim());
|
||||||
|
const separator = headers.map(() => "---");
|
||||||
|
const output = [
|
||||||
|
"| " + headers.join(" | ") + " |",
|
||||||
|
"| " + separator.join(" | ") + " |"
|
||||||
|
];
|
||||||
|
rows.slice(1).forEach((row) => {
|
||||||
|
const cells = Array.from(row.querySelectorAll("td,th")).map((cell) => convert.children(cell).trim());
|
||||||
|
output.push("| " + cells.join(" | ") + " |");
|
||||||
|
});
|
||||||
|
return "\n\n" + output.join("\n") + "\n\n";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"P": {
|
||||||
|
/*
|
||||||
|
* Any text that doesn't match another block tag
|
||||||
|
* becomes a paragraph.
|
||||||
|
*/
|
||||||
|
name: "paragraph",
|
||||||
|
match: (context) => {
|
||||||
|
const collected = [];
|
||||||
|
let i = context.index;
|
||||||
|
while (i < context.lines.length && !/^\s*$/.test(context.lines[i]) && !isBlockStart(context.lines, i))
|
||||||
|
collected.push(context.lines[i++]);
|
||||||
|
return collected.length ? {
|
||||||
|
content: collected.join("\n"),
|
||||||
|
raw: "",
|
||||||
|
consumed: i - context.index
|
||||||
|
} : null;
|
||||||
|
},
|
||||||
|
toHTML: (token, convert) => "<p>" + convert.inline(token.content) + "</p>",
|
||||||
|
selector: "P",
|
||||||
|
toMarkdown: (element, convert) => "\n\n" + convert.children(element) + "\n\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var defaultInlineTags = {
|
||||||
|
"CODE": inlineTag({
|
||||||
|
/*
|
||||||
|
* `inline code`
|
||||||
|
*/
|
||||||
|
name: "code",
|
||||||
|
delimiter: "`",
|
||||||
|
htmlTag: "code",
|
||||||
|
precedence: 10,
|
||||||
|
recursive: false
|
||||||
|
}),
|
||||||
|
"A": {
|
||||||
|
/*
|
||||||
|
* [link text](http://example.com)
|
||||||
|
*/
|
||||||
|
name: "link",
|
||||||
|
match: (context) => {
|
||||||
|
const matched = context.text.slice(context.offset).match(/^\[([^\]]+)\]\(([^)]+)\)/);
|
||||||
|
if (!matched) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
content: matched[1],
|
||||||
|
raw: matched[0],
|
||||||
|
consumed: matched[0].length,
|
||||||
|
meta: {
|
||||||
|
href: matched[2]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
toHTML: (token, convert) => '<a href="' + escapeHtml(token.meta.href) + '">' + convert.inline(token.content) + "</a>",
|
||||||
|
selector: "A",
|
||||||
|
toMarkdown: (element, convert) => "[" + convert.children(element) + "](" + (element.getAttribute("href") || "") + ")"
|
||||||
|
},
|
||||||
|
"_boldItalic": {
|
||||||
|
/*
|
||||||
|
* ***bold and italic***
|
||||||
|
*/
|
||||||
|
...inlineTag({
|
||||||
|
name: "boldItalic",
|
||||||
|
delimiter: "***",
|
||||||
|
htmlTag: "em",
|
||||||
|
precedence: 30
|
||||||
|
}),
|
||||||
|
toHTML: (token, convert) => "<em><strong>" + convert.inline(token.content) + "</strong></em>",
|
||||||
|
selector: ((element) => false),
|
||||||
|
toMarkdown: () => ""
|
||||||
|
},
|
||||||
|
"STRONG,B": inlineTag({
|
||||||
|
/*
|
||||||
|
* **bold text**
|
||||||
|
*/
|
||||||
|
name: "bold",
|
||||||
|
delimiter: "**",
|
||||||
|
htmlTag: "strong",
|
||||||
|
aliases: "B",
|
||||||
|
precedence: 40
|
||||||
|
}),
|
||||||
|
"EM,I": inlineTag({
|
||||||
|
/*
|
||||||
|
* *italic text*
|
||||||
|
*/
|
||||||
|
name: "italic",
|
||||||
|
delimiter: "*",
|
||||||
|
htmlTag: "em",
|
||||||
|
aliases: "I",
|
||||||
|
precedence: 50
|
||||||
|
})
|
||||||
|
};
|
||||||
|
var defaultTags = {
|
||||||
|
...defaultBlockTags,
|
||||||
|
...defaultInlineTags
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/hopdown.ts
|
||||||
|
var HopDown = class {
|
||||||
|
constructor(options = {}) {
|
||||||
|
let tagMap;
|
||||||
|
if (options.tags) {
|
||||||
|
tagMap = options.tags;
|
||||||
|
} else if (options.exclude) {
|
||||||
|
const excluded = new Set(options.exclude);
|
||||||
|
tagMap = Object.fromEntries(
|
||||||
|
Object.entries(defaultTags).filter(([, tag]) => !excluded.has(tag.name))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
tagMap = defaultTags;
|
||||||
|
}
|
||||||
|
const allTags = Object.values(tagMap);
|
||||||
|
const defaultBlockNames = new Set(Object.values(defaultBlockTags).map((t) => t.name));
|
||||||
|
const defaultInlineNames = new Set(Object.values(defaultInlineTags).map((t) => t.name));
|
||||||
|
this.blockTags = allTags.filter(
|
||||||
|
(tag) => defaultBlockNames.has(tag.name) || !defaultInlineNames.has(tag.name) && !tag.pattern
|
||||||
|
);
|
||||||
|
this.inlineTags = allTags.filter(
|
||||||
|
(tag) => defaultInlineNames.has(tag.name) || tag.pattern
|
||||||
|
);
|
||||||
|
this.tags = /* @__PURE__ */ new Map();
|
||||||
|
for (const [selector, tag] of Object.entries(tagMap)) {
|
||||||
|
for (const sel of selector.split(",").map((s) => s.trim()).filter(Boolean)) {
|
||||||
|
if (sel.startsWith("_")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const existing = this.tags.get(sel);
|
||||||
|
if (existing && existing !== tag) {
|
||||||
|
throw new Error(
|
||||||
|
`HTML tag "${sel}" is claimed by both "${existing.name}" and "${tag.name}". Use the exclude option to remove one before adding the other.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.tags.set(sel, tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.validateInlineTags();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Verify that no two inline tags have colliding delimiters without
|
||||||
|
* correct precedence ordering. If delimiter A is a prefix of delimiter B,
|
||||||
|
* B must have lower (earlier) precedence so the longer match wins.
|
||||||
|
*/
|
||||||
|
validateInlineTags() {
|
||||||
|
const withDelimiters = this.inlineTags.filter((tag) => tag.delimiter).map((tag) => ({
|
||||||
|
name: tag.name,
|
||||||
|
delimiter: tag.delimiter,
|
||||||
|
precedence: tag.precedence ?? 50
|
||||||
|
}));
|
||||||
|
for (let i = 0; i < withDelimiters.length; i++) {
|
||||||
|
for (let j = i + 1; j < withDelimiters.length; j++) {
|
||||||
|
const a = withDelimiters[i];
|
||||||
|
const b = withDelimiters[j];
|
||||||
|
const aPrefix = b.delimiter.startsWith(a.delimiter);
|
||||||
|
const bPrefix = a.delimiter.startsWith(b.delimiter);
|
||||||
|
if (!aPrefix && !bPrefix) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const longer = a.delimiter.length > b.delimiter.length ? a : b;
|
||||||
|
const shorter = a.delimiter.length > b.delimiter.length ? b : a;
|
||||||
|
if (longer.precedence >= shorter.precedence) {
|
||||||
|
throw new Error(
|
||||||
|
`Inline tag "${longer.name}" (delimiter "${longer.delimiter}") must have lower precedence than "${shorter.name}" (delimiter "${shorter.delimiter}") because its delimiter is a prefix match. Got ${longer.name}=${longer.precedence}, ${shorter.name}=${shorter.precedence}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Convert a markdown string to HTML.
|
||||||
|
*/
|
||||||
|
toHTML(md) {
|
||||||
|
return this.processBlocks(md);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Convert an HTML string back to markdown.
|
||||||
|
*/
|
||||||
|
toMarkdown(html) {
|
||||||
|
const container = document.createElement("div");
|
||||||
|
container.innerHTML = html;
|
||||||
|
return this.nodeToMd(container).replace(/\n{3,}/g, "\n\n").trim();
|
||||||
|
}
|
||||||
|
processBlocks(md) {
|
||||||
|
const lines = md.replace(/\r\n/g, "\n").split("\n");
|
||||||
|
const output = [];
|
||||||
|
let index = 0;
|
||||||
|
while (index < lines.length) {
|
||||||
|
if (/^\s*$/.test(lines[index])) {
|
||||||
|
index++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let matched = false;
|
||||||
|
for (const tag of this.blockTags) {
|
||||||
|
const context = {
|
||||||
|
lines,
|
||||||
|
index,
|
||||||
|
text: "",
|
||||||
|
offset: 0
|
||||||
|
};
|
||||||
|
const token = tag.match(context);
|
||||||
|
if (!token) continue;
|
||||||
|
if (tag.name === "list") {
|
||||||
|
const result = parseListBlock(lines, index, 0, (source) => this.processInline(source));
|
||||||
|
output.push(result.html);
|
||||||
|
index = result.end;
|
||||||
|
} else {
|
||||||
|
output.push(tag.toHTML(token, this.makeConverter()));
|
||||||
|
index += token.consumed;
|
||||||
|
}
|
||||||
|
matched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!matched) {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output.join("\n");
|
||||||
|
}
|
||||||
|
processInline(source) {
|
||||||
|
const sorted = [...this.inlineTags].sort(
|
||||||
|
(a, b) => (a.precedence ?? 50) - (b.precedence ?? 50)
|
||||||
|
);
|
||||||
|
const placeholders = [];
|
||||||
|
let text = source;
|
||||||
|
for (const tag of sorted) {
|
||||||
|
const recursive = tag.recursive ?? true;
|
||||||
|
if (tag.name === "link") {
|
||||||
|
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, linkText, href) => {
|
||||||
|
let inner = linkText;
|
||||||
|
const hasPlaceholders = /\x00P\d+\x00/.test(inner);
|
||||||
|
if (hasPlaceholders) {
|
||||||
|
inner = inner.replace(/\x00P(\d+)\x00/g, (__, idx) => placeholders[parseInt(idx)]);
|
||||||
|
} else {
|
||||||
|
inner = this.processInline(inner);
|
||||||
|
}
|
||||||
|
placeholders.push('<a href="' + escapeHtml(href) + '">' + inner + "</a>");
|
||||||
|
return "\0P" + (placeholders.length - 1) + "\0";
|
||||||
|
});
|
||||||
|
} else if (!recursive && tag.pattern) {
|
||||||
|
const globalPattern = tag.pattern;
|
||||||
|
globalPattern.lastIndex = 0;
|
||||||
|
text = text.replace(globalPattern, (_, content) => {
|
||||||
|
placeholders.push(tag.toHTML(
|
||||||
|
{ content, raw: "", consumed: 0 },
|
||||||
|
this.makeConverter()
|
||||||
|
));
|
||||||
|
return "\0P" + (placeholders.length - 1) + "\0";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text = escapeHtml(text);
|
||||||
|
for (const tag of sorted) {
|
||||||
|
const recursive = tag.recursive ?? true;
|
||||||
|
if (tag.name === "link" || !recursive) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const globalPattern = tag.pattern;
|
||||||
|
if (globalPattern) {
|
||||||
|
globalPattern.lastIndex = 0;
|
||||||
|
text = text.replace(globalPattern, (_, content) => {
|
||||||
|
const restored = content.replace(/\x00P(\d+)\x00/g, (__, idx) => placeholders[parseInt(idx)]);
|
||||||
|
const htmlTag = tag.name === "boldItalic" ? null : (tag.selector || "").split(",")[0].toLowerCase();
|
||||||
|
if (tag.name === "boldItalic") {
|
||||||
|
return "<em><strong>" + restored + "</strong></em>";
|
||||||
|
}
|
||||||
|
return `<${htmlTag}>${restored}</${htmlTag}>`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text = text.replace(/\x00P(\d+)\x00/g, (_, index) => placeholders[parseInt(index)]);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
nodeToMd(node) {
|
||||||
|
if (node.nodeType === 3) {
|
||||||
|
return node.textContent || "";
|
||||||
|
}
|
||||||
|
if (node.nodeType !== 1) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const element = node;
|
||||||
|
const tag = this.tags.get(element.nodeName);
|
||||||
|
if (tag) {
|
||||||
|
return tag.toMarkdown(element, this.makeConverter());
|
||||||
|
}
|
||||||
|
return this.childrenToMd(node);
|
||||||
|
}
|
||||||
|
childrenToMd(node) {
|
||||||
|
return Array.from(node.childNodes).map((child) => this.nodeToMd(child)).join("");
|
||||||
|
}
|
||||||
|
makeConverter() {
|
||||||
|
return {
|
||||||
|
inline: (source) => this.processInline(source),
|
||||||
|
block: (md) => this.processBlocks(md),
|
||||||
|
children: (node) => this.childrenToMd(node),
|
||||||
|
node: (node) => this.nodeToMd(node)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var hopdown = new HopDown();
|
||||||
|
var hopdown_default = hopdown;
|
||||||
|
|
||||||
|
// src/ribbit.ts
|
||||||
|
var RibbitPlugin = class {
|
||||||
|
constructor(settings) {
|
||||||
|
this.name = settings.name;
|
||||||
|
this.wiki = settings.wiki;
|
||||||
|
this.precedence = 50;
|
||||||
|
}
|
||||||
|
setEditable() {
|
||||||
|
}
|
||||||
|
toMarkdown(html) {
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
toHTML(md) {
|
||||||
|
return md;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var Ribbit = class {
|
||||||
|
constructor(settings) {
|
||||||
|
this.api = settings.api || null;
|
||||||
|
this.element = document.getElementById(settings.editorId || "ribbit");
|
||||||
|
this.states = {
|
||||||
|
VIEW: "view"
|
||||||
|
};
|
||||||
|
this.cachedHTML = null;
|
||||||
|
this.cachedMarkdown = null;
|
||||||
|
this.state = null;
|
||||||
|
this.changed = false;
|
||||||
|
this.enabledPlugins = {};
|
||||||
|
(settings.plugins || []).forEach((plugin) => {
|
||||||
|
this.enabledPlugins[plugin.name] = new plugin({
|
||||||
|
name: plugin.name,
|
||||||
|
wiki: this
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
run() {
|
||||||
|
this.element.classList.add("loaded");
|
||||||
|
this.view();
|
||||||
|
}
|
||||||
|
plugins() {
|
||||||
|
return Object.values(this.enabledPlugins).sort((a, b) => a.precedence - b.precedence);
|
||||||
|
}
|
||||||
|
getState() {
|
||||||
|
return this.state;
|
||||||
|
}
|
||||||
|
setState(newState) {
|
||||||
|
this.state = newState;
|
||||||
|
Object.values(this.states).forEach((state) => {
|
||||||
|
if (state === newState) {
|
||||||
|
this.element.classList.add(state);
|
||||||
|
} else {
|
||||||
|
this.element.classList.remove(state);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
markdownToHTML(md) {
|
||||||
|
return hopdown_default.toHTML(md);
|
||||||
|
}
|
||||||
|
getHTML() {
|
||||||
|
if (this.changed || !this.cachedHTML) {
|
||||||
|
this.cachedHTML = this.markdownToHTML(this.getMarkdown());
|
||||||
|
}
|
||||||
|
return this.cachedHTML;
|
||||||
|
}
|
||||||
|
getMarkdown() {
|
||||||
|
if (!this.cachedMarkdown) {
|
||||||
|
this.cachedMarkdown = this.element.textContent || "";
|
||||||
|
}
|
||||||
|
return this.cachedMarkdown;
|
||||||
|
}
|
||||||
|
view() {
|
||||||
|
if (this.getState() === this.states.VIEW) return;
|
||||||
|
this.element.innerHTML = this.getHTML();
|
||||||
|
this.setState(this.states.VIEW);
|
||||||
|
this.element.contentEditable = "false";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function camelCase(words) {
|
||||||
|
return words.trim().split(/\s+/g).map((word) => {
|
||||||
|
const lc = word.toLowerCase();
|
||||||
|
return lc.charAt(0).toUpperCase() + lc.slice(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function decodeHtmlEntities(html) {
|
||||||
|
const txt = document.createElement("textarea");
|
||||||
|
txt.innerHTML = html;
|
||||||
|
return txt.value;
|
||||||
|
}
|
||||||
|
function encodeHtmlEntities(str) {
|
||||||
|
return str.replace(/[\u00A0-\u9999<>&]/g, (i) => "&#" + i.charCodeAt(0) + ";");
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/ribbit-editor.ts
|
||||||
|
var RibbitEditor = class extends Ribbit {
|
||||||
|
run() {
|
||||||
|
this.states = {
|
||||||
|
VIEW: "view",
|
||||||
|
EDIT: "edit",
|
||||||
|
WYSIWYG: "wysiwyg"
|
||||||
|
};
|
||||||
|
this.#bindEvents();
|
||||||
|
this.plugins().forEach((plugin) => {
|
||||||
|
plugin.setEditable();
|
||||||
|
});
|
||||||
|
this.element.classList.add("loaded");
|
||||||
|
this.view();
|
||||||
|
}
|
||||||
|
#bindEvents() {
|
||||||
|
this.element.addEventListener("input", () => {
|
||||||
|
if (this.state !== this.states.VIEW) {
|
||||||
|
this.changed = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
htmlToMarkdown(html) {
|
||||||
|
return hopdown_default.toMarkdown(html || this.element.innerHTML);
|
||||||
|
}
|
||||||
|
getMarkdown() {
|
||||||
|
if (this.getState() === this.states.EDIT) {
|
||||||
|
let html = this.element.innerHTML;
|
||||||
|
html = html.replace(/<(?:div|br)>/ig, "");
|
||||||
|
html = html.replace(/<\/div>/ig, "\n");
|
||||||
|
this.cachedMarkdown = decodeHtmlEntities(html);
|
||||||
|
} else if (this.getState() === this.states.WYSIWYG) {
|
||||||
|
this.cachedMarkdown = this.htmlToMarkdown(this.element.innerHTML);
|
||||||
|
}
|
||||||
|
if (!this.cachedMarkdown) {
|
||||||
|
this.cachedMarkdown = this.element.textContent || "";
|
||||||
|
}
|
||||||
|
return this.cachedMarkdown;
|
||||||
|
}
|
||||||
|
wysiwyg() {
|
||||||
|
if (this.getState() === this.states.WYSIWYG) return;
|
||||||
|
this.changed = false;
|
||||||
|
this.element.contentEditable = "true";
|
||||||
|
this.element.innerHTML = this.getHTML();
|
||||||
|
Array.from(this.element.querySelectorAll(".macro")).forEach((el) => {
|
||||||
|
const macroEl = el;
|
||||||
|
if (macroEl.dataset.editable === "false") {
|
||||||
|
macroEl.contentEditable = "false";
|
||||||
|
macroEl.style.opacity = "0.5";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.setState(this.states.WYSIWYG);
|
||||||
|
}
|
||||||
|
edit() {
|
||||||
|
if (this.state === this.states.EDIT) return;
|
||||||
|
this.changed = false;
|
||||||
|
this.element.contentEditable = "true";
|
||||||
|
this.element.innerHTML = encodeHtmlEntities(this.getMarkdown());
|
||||||
|
this.setState(this.states.EDIT);
|
||||||
|
}
|
||||||
|
insertAtCursor(node) {
|
||||||
|
const sel = window.getSelection();
|
||||||
|
const range = sel.getRangeAt(0);
|
||||||
|
range.deleteContents();
|
||||||
|
range.insertNode(node);
|
||||||
|
range.setStartAfter(node);
|
||||||
|
this.element.focus();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(range);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.HopDown = HopDown;
|
||||||
|
window.hopdown = hopdown_default;
|
||||||
|
window.inlineTag = inlineTag;
|
||||||
|
window.defaultTags = defaultTags;
|
||||||
|
window.defaultBlockTags = defaultBlockTags;
|
||||||
|
window.defaultInlineTags = defaultInlineTags;
|
||||||
|
window.Ribbit = Ribbit;
|
||||||
|
window.RibbitEditor = RibbitEditor;
|
||||||
|
window.RibbitPlugin = RibbitPlugin;
|
||||||
|
window.camelCase = camelCase;
|
||||||
|
window.decodeHtmlEntities = decodeHtmlEntities;
|
||||||
|
window.encodeHtmlEntities = encodeHtmlEntities;
|
||||||
|
})();
|
||||||
|
//# sourceMappingURL=ribbit.js.map
|
||||||
39
src/ttfrog/themes/default/static/ribbit.min.js
vendored
Normal file
39
src/ttfrog/themes/default/static/ribbit.min.js
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
"use strict";(()=>{function u(t){let e=t.delimiter.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),n=new RegExp("^"+e+"(.+?)"+e),o=new RegExp(e+"(.+?)"+e,"g"),r=[t.htmlTag.toUpperCase(),...(t.aliases||"").split(",").filter(Boolean)].join(","),s=t.recursive!==!1;return{name:t.name,precedence:t.precedence??50,recursive:s,pattern:o,delimiter:t.delimiter,match:l=>{let i=l.text.slice(l.offset).match(n);return i?{content:i[1],raw:i[0],consumed:i[0].length}:null},toHTML:(l,i)=>{let c=s?i.inline(l.content):m(l.content);return`<${t.htmlTag}>${c}</${t.htmlTag}>`},selector:r,toMarkdown:(l,i)=>!s&&l.parentNode?.nodeName==="PRE"?i.children(l):t.delimiter+i.children(l)+t.delimiter}}function m(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function S(t){return t.trim().split(/\s+/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join("")}function x(t,e,n,o){let a=new RegExp("^"+" ".repeat(n)+"([\\*\\-]|\\d+\\.)\\s"),s=/^\d+\./.test(t[e].trim())?"ol":"ul",l=[],i=e;for(;i<t.length;){let d=t[i];if(/^\s*$/.test(d))break;let h=d.match(/^(\s*)/)[1].length;if(h<n)break;if(h>n){let g=x(t,i,h,o);l[l.length-1].sub=g.html,i=g.end;continue}if(!a.test(d))break;l.push({text:d.replace(a,""),sub:""}),i++}return{html:"<"+s+">"+l.map(d=>"<li>"+o(d.text)+d.sub+"</li>").join("")+"</"+s+">",end:i}}function I(t,e,n){let o=t.nodeName==="OL",a=" ".repeat(e),r=[];Array.from(t.children).forEach((l,i)=>{let c=o?i+1+". ":"- ",d="",h="";Array.from(l.childNodes).forEach(g=>{g.nodeType===1&&(g.nodeName==="UL"||g.nodeName==="OL")?h+=I(g,e+1,n):d+=n.node(g)}),r.push(a+c+d.trim()),h&&r.push(h)});let s=r.join(`
|
||||||
|
`);return e===0?`
|
||||||
|
|
||||||
|
`+s+`
|
||||||
|
|
||||||
|
`:s}function R(t,e){let n=t[e];return!!(/^(`{3,})/.test(n)||/^(\*{3,}|-{3,}|_{3,})\s*$/.test(n)||/^#{1,6}\s/.test(n)||/^>\s?/.test(n)||/^[*\-]\s/.test(n)||/^\d+\.\s/.test(n)||n.indexOf("|")!==-1&&e+1<t.length&&/^\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?\s*$/.test(t[e+1]))}function k(t){return t.replace(/^\|/,"").replace(/\|$/,"").split("|").map(e=>e.trim())}function O(t){return k(t).map(e=>/^:-+:$/.test(e)?"center":/^-+:$/.test(e)?"right":null)}var p={PRE:{name:"fencedCode",match:t=>{let e=t.lines[t.index].match(/^(`{3,})(.*)/);if(!e)return null;let n=e[1],o=e[2].trim(),a=[],r=t.index+1;for(;r<t.lines.length&&!t.lines[r].startsWith(n);)a.push(t.lines[r++]);return{content:a.join(`
|
||||||
|
`),raw:"",consumed:r+1-t.index,meta:{lang:o}}},toHTML:t=>"<pre><code"+(t.meta?.lang?` class="language-${m(t.meta.lang)}"`:"")+">"+m(t.content)+"</code></pre>",selector:"PRE",toMarkdown:t=>{let e=t.querySelector("code");return"\n\n```"+((e?.getAttribute("class")||"").match(/language-(\S+)/)?.[1]||"")+`
|
||||||
|
`+(e?.textContent||t.textContent||"")+"\n```\n\n"}},HR:{name:"hr",match:t=>/^(\*{3,}|-{3,}|_{3,})\s*$/.test(t.lines[t.index])?{content:"",raw:"",consumed:1}:null,toHTML:()=>"<hr>",selector:"HR",toMarkdown:()=>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`},"H1,H2,H3,H4,H5,H6":{name:"heading",match:t=>{let e=t.lines[t.index].match(/^(#{1,6})\s+(.*)/);return e?{content:e[2].trim(),raw:"",consumed:1,meta:{level:String(e[1].length)}}:null},toHTML:(t,e)=>`<h${t.meta.level} id='${S(t.content)}'>${e.inline(t.content)}</h${t.meta.level}>`,selector:"H1,H2,H3,H4,H5,H6",toMarkdown:(t,e)=>`
|
||||||
|
|
||||||
|
`+"#".repeat(parseInt(t.nodeName[1]))+" "+e.children(t)+`
|
||||||
|
|
||||||
|
`},BLOCKQUOTE:{name:"blockquote",match:t=>{if(!/^>\s?/.test(t.lines[t.index]))return null;let e=[],n=t.index;for(;n<t.lines.length&&/^>\s?/.test(t.lines[n]);)e.push(t.lines[n++].replace(/^>\s?/,""));return{content:e.join(`
|
||||||
|
`),raw:"",consumed:n-t.index}},toHTML:(t,e)=>"<blockquote>"+e.block(t.content)+"</blockquote>",selector:"BLOCKQUOTE",toMarkdown:(t,e)=>`
|
||||||
|
|
||||||
|
`+e.children(t).trim().split(`
|
||||||
|
`).map(n=>"> "+n).join(`
|
||||||
|
`)+`
|
||||||
|
|
||||||
|
`},"UL,OL":{name:"list",match:t=>{let e=t.lines[t.index];return!/^[*\-]\s/.test(e)&&!/^\d+\.\s/.test(e)?null:{content:"",raw:"",consumed:0}},toHTML:t=>t.raw,selector:"UL,OL",toMarkdown:(t,e)=>I(t,0,e)},TABLE:{name:"table",match:t=>{let{lines:e,index:n}=t;if(e[n].indexOf("|")===-1||n+1>=e.length||!/^\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?\s*$/.test(e[n+1]))return null;let o=k(e[n]),a=O(e[n+1]),r=[],s=n+2;for(;s<e.length&&e[s].indexOf("|")!==-1&&!/^\s*$/.test(e[s]);)r.push(k(e[s++]));return{content:"",raw:"",consumed:s-n,meta:{headers:JSON.stringify(o),aligns:JSON.stringify(a),rows:JSON.stringify(r)}}},toHTML:(t,e)=>{let n=JSON.parse(t.meta.headers),o=JSON.parse(t.meta.aligns),a=JSON.parse(t.meta.rows);function r(i,c,d){let h=o[d]?` style="text-align:${o[d]}"`:"";return`<${i}${h}>${e.inline(c)}</${i}>`}let s="<thead><tr>"+n.map((i,c)=>r("th",i,c)).join("")+"</tr></thead>",l=a.map(i=>"<tr>"+i.map((c,d)=>r("td",c,d)).join("")+"</tr>").join("");return"<table>"+s+"<tbody>"+l+"</tbody></table>"},selector:"TABLE",toMarkdown:(t,e)=>{let n=Array.from(t.querySelectorAll("tr"));if(!n.length)return"";let o=Array.from(n[0].querySelectorAll("th,td")).map(s=>e.children(s).trim()),a=o.map(()=>"---"),r=["| "+o.join(" | ")+" |","| "+a.join(" | ")+" |"];return n.slice(1).forEach(s=>{let l=Array.from(s.querySelectorAll("td,th")).map(i=>e.children(i).trim());r.push("| "+l.join(" | ")+" |")}),`
|
||||||
|
|
||||||
|
`+r.join(`
|
||||||
|
`)+`
|
||||||
|
|
||||||
|
`}},P:{name:"paragraph",match:t=>{let e=[],n=t.index;for(;n<t.lines.length&&!/^\s*$/.test(t.lines[n])&&!R(t.lines,n);)e.push(t.lines[n++]);return e.length?{content:e.join(`
|
||||||
|
`),raw:"",consumed:n-t.index}:null},toHTML:(t,e)=>"<p>"+e.inline(t.content)+"</p>",selector:"P",toMarkdown:(t,e)=>`
|
||||||
|
|
||||||
|
`+e.children(t)+`
|
||||||
|
|
||||||
|
`}},f={CODE:u({name:"code",delimiter:"`",htmlTag:"code",precedence:10,recursive:!1}),A:{name:"link",match:t=>{let e=t.text.slice(t.offset).match(/^\[([^\]]+)\]\(([^)]+)\)/);return e?{content:e[1],raw:e[0],consumed:e[0].length,meta:{href:e[2]}}:null},toHTML:(t,e)=>'<a href="'+m(t.meta.href)+'">'+e.inline(t.content)+"</a>",selector:"A",toMarkdown:(t,e)=>"["+e.children(t)+"]("+(t.getAttribute("href")||"")+")"},_boldItalic:{...u({name:"boldItalic",delimiter:"***",htmlTag:"em",precedence:30}),toHTML:(t,e)=>"<em><strong>"+e.inline(t.content)+"</strong></em>",selector:(t=>!1),toMarkdown:()=>""},"STRONG,B":u({name:"bold",delimiter:"**",htmlTag:"strong",aliases:"B",precedence:40}),"EM,I":u({name:"italic",delimiter:"*",htmlTag:"em",aliases:"I",precedence:50})},w={...p,...f};var T=class{constructor(e={}){let n;if(e.tags)n=e.tags;else if(e.exclude){let s=new Set(e.exclude);n=Object.fromEntries(Object.entries(w).filter(([,l])=>!s.has(l.name)))}else n=w;let o=Object.values(n),a=new Set(Object.values(p).map(s=>s.name)),r=new Set(Object.values(f).map(s=>s.name));this.blockTags=o.filter(s=>a.has(s.name)||!r.has(s.name)&&!s.pattern),this.inlineTags=o.filter(s=>r.has(s.name)||s.pattern),this.tags=new Map;for(let[s,l]of Object.entries(n))for(let i of s.split(",").map(c=>c.trim()).filter(Boolean)){if(i.startsWith("_"))continue;let c=this.tags.get(i);if(c&&c!==l)throw new Error(`HTML tag "${i}" is claimed by both "${c.name}" and "${l.name}". Use the exclude option to remove one before adding the other.`);this.tags.set(i,l)}this.validateInlineTags()}validateInlineTags(){let e=this.inlineTags.filter(n=>n.delimiter).map(n=>({name:n.name,delimiter:n.delimiter,precedence:n.precedence??50}));for(let n=0;n<e.length;n++)for(let o=n+1;o<e.length;o++){let a=e[n],r=e[o],s=r.delimiter.startsWith(a.delimiter),l=a.delimiter.startsWith(r.delimiter);if(!s&&!l)continue;let i=a.delimiter.length>r.delimiter.length?a:r,c=a.delimiter.length>r.delimiter.length?r:a;if(i.precedence>=c.precedence)throw new Error(`Inline tag "${i.name}" (delimiter "${i.delimiter}") must have lower precedence than "${c.name}" (delimiter "${c.delimiter}") because its delimiter is a prefix match. Got ${i.name}=${i.precedence}, ${c.name}=${c.precedence}.`)}}toHTML(e){return this.processBlocks(e)}toMarkdown(e){let n=document.createElement("div");return n.innerHTML=e,this.nodeToMd(n).replace(/\n{3,}/g,`
|
||||||
|
|
||||||
|
`).trim()}processBlocks(e){let n=e.replace(/\r\n/g,`
|
||||||
|
`).split(`
|
||||||
|
`),o=[],a=0;for(;a<n.length;){if(/^\s*$/.test(n[a])){a++;continue}let r=!1;for(let s of this.blockTags){let l={lines:n,index:a,text:"",offset:0},i=s.match(l);if(i){if(s.name==="list"){let c=x(n,a,0,d=>this.processInline(d));o.push(c.html),a=c.end}else o.push(s.toHTML(i,this.makeConverter())),a+=i.consumed;r=!0;break}}r||a++}return o.join(`
|
||||||
|
`)}processInline(e){let n=[...this.inlineTags].sort((r,s)=>(r.precedence??50)-(s.precedence??50)),o=[],a=e;for(let r of n){let s=r.recursive??!0;if(r.name==="link")a=a.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(l,i,c)=>{let d=i;return/\x00P\d+\x00/.test(d)?d=d.replace(/\x00P(\d+)\x00/g,(g,L)=>o[parseInt(L)]):d=this.processInline(d),o.push('<a href="'+m(c)+'">'+d+"</a>"),"\0P"+(o.length-1)+"\0"});else if(!s&&r.pattern){let l=r.pattern;l.lastIndex=0,a=a.replace(l,(i,c)=>(o.push(r.toHTML({content:c,raw:"",consumed:0},this.makeConverter())),"\0P"+(o.length-1)+"\0"))}}a=m(a);for(let r of n){let s=r.recursive??!0;if(r.name==="link"||!s)continue;let l=r.pattern;l&&(l.lastIndex=0,a=a.replace(l,(i,c)=>{let d=c.replace(/\x00P(\d+)\x00/g,(g,L)=>o[parseInt(L)]),h=r.name==="boldItalic"?null:(r.selector||"").split(",")[0].toLowerCase();return r.name==="boldItalic"?"<em><strong>"+d+"</strong></em>":`<${h}>${d}</${h}>`}))}return a=a.replace(/\x00P(\d+)\x00/g,(r,s)=>o[parseInt(s)]),a}nodeToMd(e){if(e.nodeType===3)return e.textContent||"";if(e.nodeType!==1)return"";let n=e,o=this.tags.get(n.nodeName);return o?o.toMarkdown(n,this.makeConverter()):this.childrenToMd(e)}childrenToMd(e){return Array.from(e.childNodes).map(n=>this.nodeToMd(n)).join("")}makeConverter(){return{inline:e=>this.processInline(e),block:e=>this.processBlocks(e),children:e=>this.childrenToMd(e),node:e=>this.nodeToMd(e)}}},C=new T;var b=C;var H=class{constructor(e){this.name=e.name,this.wiki=e.wiki,this.precedence=50}setEditable(){}toMarkdown(e){return e}toHTML(e){return e}},M=class{constructor(e){this.api=e.api||null,this.element=document.getElementById(e.editorId||"ribbit"),this.states={VIEW:"view"},this.cachedHTML=null,this.cachedMarkdown=null,this.state=null,this.changed=!1,this.enabledPlugins={},(e.plugins||[]).forEach(n=>{this.enabledPlugins[n.name]=new n({name:n.name,wiki:this})})}run(){this.element.classList.add("loaded"),this.view()}plugins(){return Object.values(this.enabledPlugins).sort((e,n)=>e.precedence-n.precedence)}getState(){return this.state}setState(e){this.state=e,Object.values(this.states).forEach(n=>{n===e?this.element.classList.add(n):this.element.classList.remove(n)})}markdownToHTML(e){return b.toHTML(e)}getHTML(){return(this.changed||!this.cachedHTML)&&(this.cachedHTML=this.markdownToHTML(this.getMarkdown())),this.cachedHTML}getMarkdown(){return this.cachedMarkdown||(this.cachedMarkdown=this.element.textContent||""),this.cachedMarkdown}view(){this.getState()!==this.states.VIEW&&(this.element.innerHTML=this.getHTML(),this.setState(this.states.VIEW),this.element.contentEditable="false")}};function $(t){return t.trim().split(/\s+/g).map(e=>{let n=e.toLowerCase();return n.charAt(0).toUpperCase()+n.slice(1)})}function y(t){let e=document.createElement("textarea");return e.innerHTML=t,e.value}function E(t){return t.replace(/[\u00A0-\u9999<>&]/g,e=>"&#"+e.charCodeAt(0)+";")}var v=class extends M{run(){this.states={VIEW:"view",EDIT:"edit",WYSIWYG:"wysiwyg"},this.#e(),this.plugins().forEach(e=>{e.setEditable()}),this.element.classList.add("loaded"),this.view()}#e(){this.element.addEventListener("input",()=>{this.state!==this.states.VIEW&&(this.changed=!0)})}htmlToMarkdown(e){return b.toMarkdown(e||this.element.innerHTML)}getMarkdown(){if(this.getState()===this.states.EDIT){let e=this.element.innerHTML;e=e.replace(/<(?:div|br)>/ig,""),e=e.replace(/<\/div>/ig,`
|
||||||
|
`),this.cachedMarkdown=y(e)}else this.getState()===this.states.WYSIWYG&&(this.cachedMarkdown=this.htmlToMarkdown(this.element.innerHTML));return this.cachedMarkdown||(this.cachedMarkdown=this.element.textContent||""),this.cachedMarkdown}wysiwyg(){this.getState()!==this.states.WYSIWYG&&(this.changed=!1,this.element.contentEditable="true",this.element.innerHTML=this.getHTML(),Array.from(this.element.querySelectorAll(".macro")).forEach(e=>{let n=e;n.dataset.editable==="false"&&(n.contentEditable="false",n.style.opacity="0.5")}),this.setState(this.states.WYSIWYG))}edit(){this.state!==this.states.EDIT&&(this.changed=!1,this.element.contentEditable="true",this.element.innerHTML=E(this.getMarkdown()),this.setState(this.states.EDIT))}insertAtCursor(e){let n=window.getSelection(),o=n.getRangeAt(0);o.deleteContents(),o.insertNode(e),o.setStartAfter(e),this.element.focus(),n.removeAllRanges(),n.addRange(o)}};window.HopDown=T;window.hopdown=b;window.inlineTag=u;window.defaultTags=w;window.defaultBlockTags=p;window.defaultInlineTags=f;window.Ribbit=M;window.RibbitEditor=v;window.RibbitPlugin=H;window.camelCase=$;window.decodeHtmlEntities=y;window.encodeHtmlEntities=E;})();
|
||||||
|
|
@ -1,976 +0,0 @@
|
||||||
var TurndownService = (function () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function extend (destination) {
|
|
||||||
for (var i = 1; i < arguments.length; i++) {
|
|
||||||
var source = arguments[i];
|
|
||||||
for (var key in source) {
|
|
||||||
if (source.hasOwnProperty(key)) destination[key] = source[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return destination
|
|
||||||
}
|
|
||||||
|
|
||||||
function repeat (character, count) {
|
|
||||||
return Array(count + 1).join(character)
|
|
||||||
}
|
|
||||||
|
|
||||||
function trimLeadingNewlines (string) {
|
|
||||||
return string.replace(/^\n*/, '')
|
|
||||||
}
|
|
||||||
|
|
||||||
function trimTrailingNewlines (string) {
|
|
||||||
// avoid match-at-end regexp bottleneck, see #370
|
|
||||||
var indexEnd = string.length;
|
|
||||||
while (indexEnd > 0 && string[indexEnd - 1] === '\n') indexEnd--;
|
|
||||||
return string.substring(0, indexEnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
function trimNewlines (string) {
|
|
||||||
return trimTrailingNewlines(trimLeadingNewlines(string))
|
|
||||||
}
|
|
||||||
|
|
||||||
var blockElements = [
|
|
||||||
'ADDRESS', 'ARTICLE', 'ASIDE', 'AUDIO', 'BLOCKQUOTE', 'BODY', 'CANVAS',
|
|
||||||
'CENTER', 'DD', 'DIR', 'DIV', 'DL', 'DT', 'FIELDSET', 'FIGCAPTION', 'FIGURE',
|
|
||||||
'FOOTER', 'FORM', 'FRAMESET', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEADER',
|
|
||||||
'HGROUP', 'HR', 'HTML', 'ISINDEX', 'LI', 'MAIN', 'MENU', 'NAV', 'NOFRAMES',
|
|
||||||
'NOSCRIPT', 'OL', 'OUTPUT', 'P', 'PRE', 'SECTION', 'TABLE', 'TBODY', 'TD',
|
|
||||||
'TFOOT', 'TH', 'THEAD', 'TR', 'UL'
|
|
||||||
];
|
|
||||||
|
|
||||||
function isBlock (node) {
|
|
||||||
return is(node, blockElements)
|
|
||||||
}
|
|
||||||
|
|
||||||
var voidElements = [
|
|
||||||
'AREA', 'BASE', 'BR', 'COL', 'COMMAND', 'EMBED', 'HR', 'IMG', 'INPUT',
|
|
||||||
'KEYGEN', 'LINK', 'META', 'PARAM', 'SOURCE', 'TRACK', 'WBR'
|
|
||||||
];
|
|
||||||
|
|
||||||
function isVoid (node) {
|
|
||||||
return is(node, voidElements)
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasVoid (node) {
|
|
||||||
return has(node, voidElements)
|
|
||||||
}
|
|
||||||
|
|
||||||
var meaningfulWhenBlankElements = [
|
|
||||||
'A', 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TH', 'TD', 'IFRAME', 'SCRIPT',
|
|
||||||
'AUDIO', 'VIDEO'
|
|
||||||
];
|
|
||||||
|
|
||||||
function isMeaningfulWhenBlank (node) {
|
|
||||||
return is(node, meaningfulWhenBlankElements)
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasMeaningfulWhenBlank (node) {
|
|
||||||
return has(node, meaningfulWhenBlankElements)
|
|
||||||
}
|
|
||||||
|
|
||||||
function is (node, tagNames) {
|
|
||||||
return tagNames.indexOf(node.nodeName) >= 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function has (node, tagNames) {
|
|
||||||
return (
|
|
||||||
node.getElementsByTagName &&
|
|
||||||
tagNames.some(function (tagName) {
|
|
||||||
return node.getElementsByTagName(tagName).length
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var rules = {};
|
|
||||||
|
|
||||||
rules.paragraph = {
|
|
||||||
filter: 'p',
|
|
||||||
|
|
||||||
replacement: function (content) {
|
|
||||||
return '\n\n' + content + '\n\n'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rules.lineBreak = {
|
|
||||||
filter: 'br',
|
|
||||||
|
|
||||||
replacement: function (content, node, options) {
|
|
||||||
return options.br + '\n'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rules.heading = {
|
|
||||||
filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
|
|
||||||
|
|
||||||
replacement: function (content, node, options) {
|
|
||||||
var hLevel = Number(node.nodeName.charAt(1));
|
|
||||||
|
|
||||||
if (options.headingStyle === 'setext' && hLevel < 3) {
|
|
||||||
var underline = repeat((hLevel === 1 ? '=' : '-'), content.length);
|
|
||||||
return (
|
|
||||||
'\n\n' + content + '\n' + underline + '\n\n'
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return '\n\n' + repeat('#', hLevel) + ' ' + content + '\n\n'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rules.blockquote = {
|
|
||||||
filter: 'blockquote',
|
|
||||||
|
|
||||||
replacement: function (content) {
|
|
||||||
content = trimNewlines(content).replace(/^/gm, '> ');
|
|
||||||
return '\n\n' + content + '\n\n'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rules.list = {
|
|
||||||
filter: ['ul', 'ol'],
|
|
||||||
|
|
||||||
replacement: function (content, node) {
|
|
||||||
var parent = node.parentNode;
|
|
||||||
if (parent.nodeName === 'LI' && parent.lastElementChild === node) {
|
|
||||||
return '\n' + content
|
|
||||||
} else {
|
|
||||||
return '\n\n' + content + '\n\n'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rules.listItem = {
|
|
||||||
filter: 'li',
|
|
||||||
|
|
||||||
replacement: function (content, node, options) {
|
|
||||||
var prefix = options.bulletListMarker + ' ';
|
|
||||||
var parent = node.parentNode;
|
|
||||||
if (parent.nodeName === 'OL') {
|
|
||||||
var start = parent.getAttribute('start');
|
|
||||||
var index = Array.prototype.indexOf.call(parent.children, node);
|
|
||||||
prefix = (start ? Number(start) + index : index + 1) + '. ';
|
|
||||||
}
|
|
||||||
var isParagraph = /\n$/.test(content);
|
|
||||||
content = trimNewlines(content) + (isParagraph ? '\n' : '');
|
|
||||||
content = content.replace(/\n/gm, '\n' + ' '.repeat(prefix.length)); // indent
|
|
||||||
return (
|
|
||||||
prefix + content + (node.nextSibling ? '\n' : '')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rules.indentedCodeBlock = {
|
|
||||||
filter: function (node, options) {
|
|
||||||
return (
|
|
||||||
options.codeBlockStyle === 'indented' &&
|
|
||||||
node.nodeName === 'PRE' &&
|
|
||||||
node.firstChild &&
|
|
||||||
node.firstChild.nodeName === 'CODE'
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
replacement: function (content, node, options) {
|
|
||||||
return (
|
|
||||||
'\n\n ' +
|
|
||||||
node.firstChild.textContent.replace(/\n/g, '\n ') +
|
|
||||||
'\n\n'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rules.fencedCodeBlock = {
|
|
||||||
filter: function (node, options) {
|
|
||||||
return (
|
|
||||||
options.codeBlockStyle === 'fenced' &&
|
|
||||||
node.nodeName === 'PRE' &&
|
|
||||||
node.firstChild &&
|
|
||||||
node.firstChild.nodeName === 'CODE'
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
replacement: function (content, node, options) {
|
|
||||||
var className = node.firstChild.getAttribute('class') || '';
|
|
||||||
var language = (className.match(/language-(\S+)/) || [null, ''])[1];
|
|
||||||
var code = node.firstChild.textContent;
|
|
||||||
|
|
||||||
var fenceChar = options.fence.charAt(0);
|
|
||||||
var fenceSize = 3;
|
|
||||||
var fenceInCodeRegex = new RegExp('^' + fenceChar + '{3,}', 'gm');
|
|
||||||
|
|
||||||
var match;
|
|
||||||
while ((match = fenceInCodeRegex.exec(code))) {
|
|
||||||
if (match[0].length >= fenceSize) {
|
|
||||||
fenceSize = match[0].length + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var fence = repeat(fenceChar, fenceSize);
|
|
||||||
|
|
||||||
return (
|
|
||||||
'\n\n' + fence + language + '\n' +
|
|
||||||
code.replace(/\n$/, '') +
|
|
||||||
'\n' + fence + '\n\n'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rules.horizontalRule = {
|
|
||||||
filter: 'hr',
|
|
||||||
|
|
||||||
replacement: function (content, node, options) {
|
|
||||||
return '\n\n' + options.hr + '\n\n'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rules.inlineLink = {
|
|
||||||
filter: function (node, options) {
|
|
||||||
return (
|
|
||||||
options.linkStyle === 'inlined' &&
|
|
||||||
node.nodeName === 'A' &&
|
|
||||||
node.getAttribute('href')
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
replacement: function (content, node) {
|
|
||||||
var href = node.getAttribute('href');
|
|
||||||
if (href) href = href.replace(/([()])/g, '\\$1');
|
|
||||||
var title = cleanAttribute(node.getAttribute('title'));
|
|
||||||
if (title) title = ' "' + title.replace(/"/g, '\\"') + '"';
|
|
||||||
return '[' + content + '](' + href + title + ')'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rules.referenceLink = {
|
|
||||||
filter: function (node, options) {
|
|
||||||
return (
|
|
||||||
options.linkStyle === 'referenced' &&
|
|
||||||
node.nodeName === 'A' &&
|
|
||||||
node.getAttribute('href')
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
replacement: function (content, node, options) {
|
|
||||||
var href = node.getAttribute('href');
|
|
||||||
var title = cleanAttribute(node.getAttribute('title'));
|
|
||||||
if (title) title = ' "' + title + '"';
|
|
||||||
var replacement;
|
|
||||||
var reference;
|
|
||||||
|
|
||||||
switch (options.linkReferenceStyle) {
|
|
||||||
case 'collapsed':
|
|
||||||
replacement = '[' + content + '][]';
|
|
||||||
reference = '[' + content + ']: ' + href + title;
|
|
||||||
break
|
|
||||||
case 'shortcut':
|
|
||||||
replacement = '[' + content + ']';
|
|
||||||
reference = '[' + content + ']: ' + href + title;
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
var id = this.references.length + 1;
|
|
||||||
replacement = '[' + content + '][' + id + ']';
|
|
||||||
reference = '[' + id + ']: ' + href + title;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.references.push(reference);
|
|
||||||
return replacement
|
|
||||||
},
|
|
||||||
|
|
||||||
references: [],
|
|
||||||
|
|
||||||
append: function (options) {
|
|
||||||
var references = '';
|
|
||||||
if (this.references.length) {
|
|
||||||
references = '\n\n' + this.references.join('\n') + '\n\n';
|
|
||||||
this.references = []; // Reset references
|
|
||||||
}
|
|
||||||
return references
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rules.emphasis = {
|
|
||||||
filter: ['em', 'i'],
|
|
||||||
|
|
||||||
replacement: function (content, node, options) {
|
|
||||||
if (!content.trim()) return ''
|
|
||||||
return options.emDelimiter + content + options.emDelimiter
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rules.strong = {
|
|
||||||
filter: ['strong', 'b'],
|
|
||||||
|
|
||||||
replacement: function (content, node, options) {
|
|
||||||
if (!content.trim()) return ''
|
|
||||||
return options.strongDelimiter + content + options.strongDelimiter
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rules.code = {
|
|
||||||
filter: function (node) {
|
|
||||||
var hasSiblings = node.previousSibling || node.nextSibling;
|
|
||||||
var isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings;
|
|
||||||
|
|
||||||
return node.nodeName === 'CODE' && !isCodeBlock
|
|
||||||
},
|
|
||||||
|
|
||||||
replacement: function (content) {
|
|
||||||
if (!content) return ''
|
|
||||||
content = content.replace(/\r?\n|\r/g, ' ');
|
|
||||||
|
|
||||||
var extraSpace = /^`|^ .*?[^ ].* $|`$/.test(content) ? ' ' : '';
|
|
||||||
var delimiter = '`';
|
|
||||||
var matches = content.match(/`+/gm) || [];
|
|
||||||
while (matches.indexOf(delimiter) !== -1) delimiter = delimiter + '`';
|
|
||||||
|
|
||||||
return delimiter + extraSpace + content + extraSpace + delimiter
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rules.image = {
|
|
||||||
filter: 'img',
|
|
||||||
|
|
||||||
replacement: function (content, node) {
|
|
||||||
var alt = cleanAttribute(node.getAttribute('alt'));
|
|
||||||
var src = node.getAttribute('src') || '';
|
|
||||||
var title = cleanAttribute(node.getAttribute('title'));
|
|
||||||
var titlePart = title ? ' "' + title + '"' : '';
|
|
||||||
return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : ''
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function cleanAttribute (attribute) {
|
|
||||||
return attribute ? attribute.replace(/(\n+\s*)+/g, '\n') : ''
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manages a collection of rules used to convert HTML to Markdown
|
|
||||||
*/
|
|
||||||
|
|
||||||
function Rules (options) {
|
|
||||||
this.options = options;
|
|
||||||
this._keep = [];
|
|
||||||
this._remove = [];
|
|
||||||
|
|
||||||
this.blankRule = {
|
|
||||||
replacement: options.blankReplacement
|
|
||||||
};
|
|
||||||
|
|
||||||
this.keepReplacement = options.keepReplacement;
|
|
||||||
|
|
||||||
this.defaultRule = {
|
|
||||||
replacement: options.defaultReplacement
|
|
||||||
};
|
|
||||||
|
|
||||||
this.array = [];
|
|
||||||
for (var key in options.rules) this.array.push(options.rules[key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Rules.prototype = {
|
|
||||||
add: function (key, rule) {
|
|
||||||
this.array.unshift(rule);
|
|
||||||
},
|
|
||||||
|
|
||||||
keep: function (filter) {
|
|
||||||
this._keep.unshift({
|
|
||||||
filter: filter,
|
|
||||||
replacement: this.keepReplacement
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
remove: function (filter) {
|
|
||||||
this._remove.unshift({
|
|
||||||
filter: filter,
|
|
||||||
replacement: function () {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
forNode: function (node) {
|
|
||||||
if (node.isBlank) return this.blankRule
|
|
||||||
var rule;
|
|
||||||
|
|
||||||
if ((rule = findRule(this.array, node, this.options))) return rule
|
|
||||||
if ((rule = findRule(this._keep, node, this.options))) return rule
|
|
||||||
if ((rule = findRule(this._remove, node, this.options))) return rule
|
|
||||||
|
|
||||||
return this.defaultRule
|
|
||||||
},
|
|
||||||
|
|
||||||
forEach: function (fn) {
|
|
||||||
for (var i = 0; i < this.array.length; i++) fn(this.array[i], i);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function findRule (rules, node, options) {
|
|
||||||
for (var i = 0; i < rules.length; i++) {
|
|
||||||
var rule = rules[i];
|
|
||||||
if (filterValue(rule, node, options)) return rule
|
|
||||||
}
|
|
||||||
return void 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterValue (rule, node, options) {
|
|
||||||
var filter = rule.filter;
|
|
||||||
if (typeof filter === 'string') {
|
|
||||||
if (filter === node.nodeName.toLowerCase()) return true
|
|
||||||
} else if (Array.isArray(filter)) {
|
|
||||||
if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true
|
|
||||||
} else if (typeof filter === 'function') {
|
|
||||||
if (filter.call(rule, node, options)) return true
|
|
||||||
} else {
|
|
||||||
throw new TypeError('`filter` needs to be a string, array, or function')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The collapseWhitespace function is adapted from collapse-whitespace
|
|
||||||
* by Luc Thevenard.
|
|
||||||
*
|
|
||||||
* The MIT License (MIT)
|
|
||||||
*
|
|
||||||
* Copyright (c) 2014 Luc Thevenard <lucthevenard@gmail.com>
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* collapseWhitespace(options) removes extraneous whitespace from an the given element.
|
|
||||||
*
|
|
||||||
* @param {Object} options
|
|
||||||
*/
|
|
||||||
function collapseWhitespace (options) {
|
|
||||||
var element = options.element;
|
|
||||||
var isBlock = options.isBlock;
|
|
||||||
var isVoid = options.isVoid;
|
|
||||||
var isPre = options.isPre || function (node) {
|
|
||||||
return node.nodeName === 'PRE'
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!element.firstChild || isPre(element)) return
|
|
||||||
|
|
||||||
var prevText = null;
|
|
||||||
var keepLeadingWs = false;
|
|
||||||
|
|
||||||
var prev = null;
|
|
||||||
var node = next(prev, element, isPre);
|
|
||||||
|
|
||||||
while (node !== element) {
|
|
||||||
if (node.nodeType === 3 || node.nodeType === 4) { // Node.TEXT_NODE or Node.CDATA_SECTION_NODE
|
|
||||||
var text = node.data.replace(/[ \r\n\t]+/g, ' ');
|
|
||||||
|
|
||||||
if ((!prevText || / $/.test(prevText.data)) &&
|
|
||||||
!keepLeadingWs && text[0] === ' ') {
|
|
||||||
text = text.substr(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// `text` might be empty at this point.
|
|
||||||
if (!text) {
|
|
||||||
node = remove(node);
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
node.data = text;
|
|
||||||
|
|
||||||
prevText = node;
|
|
||||||
} else if (node.nodeType === 1) { // Node.ELEMENT_NODE
|
|
||||||
if (isBlock(node) || node.nodeName === 'BR') {
|
|
||||||
if (prevText) {
|
|
||||||
prevText.data = prevText.data.replace(/ $/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
prevText = null;
|
|
||||||
keepLeadingWs = false;
|
|
||||||
} else if (isVoid(node) || isPre(node)) {
|
|
||||||
// Avoid trimming space around non-block, non-BR void elements and inline PRE.
|
|
||||||
prevText = null;
|
|
||||||
keepLeadingWs = true;
|
|
||||||
} else if (prevText) {
|
|
||||||
// Drop protection if set previously.
|
|
||||||
keepLeadingWs = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
node = remove(node);
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var nextNode = next(prev, node, isPre);
|
|
||||||
prev = node;
|
|
||||||
node = nextNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevText) {
|
|
||||||
prevText.data = prevText.data.replace(/ $/, '');
|
|
||||||
if (!prevText.data) {
|
|
||||||
remove(prevText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* remove(node) removes the given node from the DOM and returns the
|
|
||||||
* next node in the sequence.
|
|
||||||
*
|
|
||||||
* @param {Node} node
|
|
||||||
* @return {Node} node
|
|
||||||
*/
|
|
||||||
function remove (node) {
|
|
||||||
var next = node.nextSibling || node.parentNode;
|
|
||||||
|
|
||||||
node.parentNode.removeChild(node);
|
|
||||||
|
|
||||||
return next
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* next(prev, current, isPre) returns the next node in the sequence, given the
|
|
||||||
* current and previous nodes.
|
|
||||||
*
|
|
||||||
* @param {Node} prev
|
|
||||||
* @param {Node} current
|
|
||||||
* @param {Function} isPre
|
|
||||||
* @return {Node}
|
|
||||||
*/
|
|
||||||
function next (prev, current, isPre) {
|
|
||||||
if ((prev && prev.parentNode === current) || isPre(current)) {
|
|
||||||
return current.nextSibling || current.parentNode
|
|
||||||
}
|
|
||||||
|
|
||||||
return current.firstChild || current.nextSibling || current.parentNode
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set up window for Node.js
|
|
||||||
*/
|
|
||||||
|
|
||||||
var root = (typeof window !== 'undefined' ? window : {});
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Parsing HTML strings
|
|
||||||
*/
|
|
||||||
|
|
||||||
function canParseHTMLNatively () {
|
|
||||||
var Parser = root.DOMParser;
|
|
||||||
var canParse = false;
|
|
||||||
|
|
||||||
// Adapted from https://gist.github.com/1129031
|
|
||||||
// Firefox/Opera/IE throw errors on unsupported types
|
|
||||||
try {
|
|
||||||
// WebKit returns null on unsupported types
|
|
||||||
if (new Parser().parseFromString('', 'text/html')) {
|
|
||||||
canParse = true;
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
return canParse
|
|
||||||
}
|
|
||||||
|
|
||||||
function createHTMLParser () {
|
|
||||||
var Parser = function () {};
|
|
||||||
|
|
||||||
{
|
|
||||||
if (shouldUseActiveX()) {
|
|
||||||
Parser.prototype.parseFromString = function (string) {
|
|
||||||
var doc = new window.ActiveXObject('htmlfile');
|
|
||||||
doc.designMode = 'on'; // disable on-page scripts
|
|
||||||
doc.open();
|
|
||||||
doc.write(string);
|
|
||||||
doc.close();
|
|
||||||
return doc
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
Parser.prototype.parseFromString = function (string) {
|
|
||||||
var doc = document.implementation.createHTMLDocument('');
|
|
||||||
doc.open();
|
|
||||||
doc.write(string);
|
|
||||||
doc.close();
|
|
||||||
return doc
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Parser
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldUseActiveX () {
|
|
||||||
var useActiveX = false;
|
|
||||||
try {
|
|
||||||
document.implementation.createHTMLDocument('').open();
|
|
||||||
} catch (e) {
|
|
||||||
if (root.ActiveXObject) useActiveX = true;
|
|
||||||
}
|
|
||||||
return useActiveX
|
|
||||||
}
|
|
||||||
|
|
||||||
var HTMLParser = canParseHTMLNatively() ? root.DOMParser : createHTMLParser();
|
|
||||||
|
|
||||||
function RootNode (input, options) {
|
|
||||||
var root;
|
|
||||||
if (typeof input === 'string') {
|
|
||||||
var doc = htmlParser().parseFromString(
|
|
||||||
// DOM parsers arrange elements in the <head> and <body>.
|
|
||||||
// Wrapping in a custom element ensures elements are reliably arranged in
|
|
||||||
// a single element.
|
|
||||||
'<x-turndown id="turndown-root">' + input + '</x-turndown>',
|
|
||||||
'text/html'
|
|
||||||
);
|
|
||||||
root = doc.getElementById('turndown-root');
|
|
||||||
} else {
|
|
||||||
root = input.cloneNode(true);
|
|
||||||
}
|
|
||||||
collapseWhitespace({
|
|
||||||
element: root,
|
|
||||||
isBlock: isBlock,
|
|
||||||
isVoid: isVoid,
|
|
||||||
isPre: options.preformattedCode ? isPreOrCode : null
|
|
||||||
});
|
|
||||||
|
|
||||||
return root
|
|
||||||
}
|
|
||||||
|
|
||||||
var _htmlParser;
|
|
||||||
function htmlParser () {
|
|
||||||
_htmlParser = _htmlParser || new HTMLParser();
|
|
||||||
return _htmlParser
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPreOrCode (node) {
|
|
||||||
return node.nodeName === 'PRE' || node.nodeName === 'CODE'
|
|
||||||
}
|
|
||||||
|
|
||||||
function Node (node, options) {
|
|
||||||
node.isBlock = isBlock(node);
|
|
||||||
node.isCode = node.nodeName === 'CODE' || node.parentNode.isCode;
|
|
||||||
node.isBlank = isBlank(node);
|
|
||||||
node.flankingWhitespace = flankingWhitespace(node, options);
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
function isBlank (node) {
|
|
||||||
return (
|
|
||||||
!isVoid(node) &&
|
|
||||||
!isMeaningfulWhenBlank(node) &&
|
|
||||||
/^\s*$/i.test(node.textContent) &&
|
|
||||||
!hasVoid(node) &&
|
|
||||||
!hasMeaningfulWhenBlank(node)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function flankingWhitespace (node, options) {
|
|
||||||
if (node.isBlock || (options.preformattedCode && node.isCode)) {
|
|
||||||
return { leading: '', trailing: '' }
|
|
||||||
}
|
|
||||||
|
|
||||||
var edges = edgeWhitespace(node.textContent);
|
|
||||||
|
|
||||||
// abandon leading ASCII WS if left-flanked by ASCII WS
|
|
||||||
if (edges.leadingAscii && isFlankedByWhitespace('left', node, options)) {
|
|
||||||
edges.leading = edges.leadingNonAscii;
|
|
||||||
}
|
|
||||||
|
|
||||||
// abandon trailing ASCII WS if right-flanked by ASCII WS
|
|
||||||
if (edges.trailingAscii && isFlankedByWhitespace('right', node, options)) {
|
|
||||||
edges.trailing = edges.trailingNonAscii;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { leading: edges.leading, trailing: edges.trailing }
|
|
||||||
}
|
|
||||||
|
|
||||||
function edgeWhitespace (string) {
|
|
||||||
var m = string.match(/^(([ \t\r\n]*)(\s*))(?:(?=\S)[\s\S]*\S)?((\s*?)([ \t\r\n]*))$/);
|
|
||||||
return {
|
|
||||||
leading: m[1], // whole string for whitespace-only strings
|
|
||||||
leadingAscii: m[2],
|
|
||||||
leadingNonAscii: m[3],
|
|
||||||
trailing: m[4], // empty for whitespace-only strings
|
|
||||||
trailingNonAscii: m[5],
|
|
||||||
trailingAscii: m[6]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isFlankedByWhitespace (side, node, options) {
|
|
||||||
var sibling;
|
|
||||||
var regExp;
|
|
||||||
var isFlanked;
|
|
||||||
|
|
||||||
if (side === 'left') {
|
|
||||||
sibling = node.previousSibling;
|
|
||||||
regExp = / $/;
|
|
||||||
} else {
|
|
||||||
sibling = node.nextSibling;
|
|
||||||
regExp = /^ /;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sibling) {
|
|
||||||
if (sibling.nodeType === 3) {
|
|
||||||
isFlanked = regExp.test(sibling.nodeValue);
|
|
||||||
} else if (options.preformattedCode && sibling.nodeName === 'CODE') {
|
|
||||||
isFlanked = false;
|
|
||||||
} else if (sibling.nodeType === 1 && !isBlock(sibling)) {
|
|
||||||
isFlanked = regExp.test(sibling.textContent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return isFlanked
|
|
||||||
}
|
|
||||||
|
|
||||||
var reduce = Array.prototype.reduce;
|
|
||||||
var escapes = [
|
|
||||||
[/\\/g, '\\\\'],
|
|
||||||
[/\*/g, '\\*'],
|
|
||||||
[/^-/g, '\\-'],
|
|
||||||
[/^\+ /g, '\\+ '],
|
|
||||||
[/^(=+)/g, '\\$1'],
|
|
||||||
[/^(#{1,6}) /g, '\\$1 '],
|
|
||||||
[/`/g, '\\`'],
|
|
||||||
[/^~~~/g, '\\~~~'],
|
|
||||||
[/\[/g, '\\['],
|
|
||||||
[/\]/g, '\\]'],
|
|
||||||
[/^>/g, '\\>'],
|
|
||||||
[/_/g, '\\_'],
|
|
||||||
[/^(\d+)\. /g, '$1\\. ']
|
|
||||||
];
|
|
||||||
|
|
||||||
function TurndownService (options) {
|
|
||||||
if (!(this instanceof TurndownService)) return new TurndownService(options)
|
|
||||||
|
|
||||||
var defaults = {
|
|
||||||
rules: rules,
|
|
||||||
headingStyle: 'setext',
|
|
||||||
hr: '* * *',
|
|
||||||
bulletListMarker: '*',
|
|
||||||
codeBlockStyle: 'indented',
|
|
||||||
fence: '```',
|
|
||||||
emDelimiter: '_',
|
|
||||||
strongDelimiter: '**',
|
|
||||||
linkStyle: 'inlined',
|
|
||||||
linkReferenceStyle: 'full',
|
|
||||||
br: ' ',
|
|
||||||
preformattedCode: false,
|
|
||||||
blankReplacement: function (content, node) {
|
|
||||||
return node.isBlock ? '\n\n' : ''
|
|
||||||
},
|
|
||||||
keepReplacement: function (content, node) {
|
|
||||||
return node.isBlock ? '\n\n' + node.outerHTML + '\n\n' : node.outerHTML
|
|
||||||
},
|
|
||||||
defaultReplacement: function (content, node) {
|
|
||||||
return node.isBlock ? '\n\n' + content + '\n\n' : content
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.options = extend({}, defaults, options);
|
|
||||||
this.rules = new Rules(this.options);
|
|
||||||
}
|
|
||||||
|
|
||||||
TurndownService.prototype = {
|
|
||||||
/**
|
|
||||||
* The entry point for converting a string or DOM node to Markdown
|
|
||||||
* @public
|
|
||||||
* @param {String|HTMLElement} input The string or DOM node to convert
|
|
||||||
* @returns A Markdown representation of the input
|
|
||||||
* @type String
|
|
||||||
*/
|
|
||||||
|
|
||||||
turndown: function (input) {
|
|
||||||
if (!canConvert(input)) {
|
|
||||||
throw new TypeError(
|
|
||||||
input + ' is not a string, or an element/document/fragment node.'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input === '') return ''
|
|
||||||
|
|
||||||
var output = process.call(this, new RootNode(input, this.options));
|
|
||||||
return postProcess.call(this, output)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add one or more plugins
|
|
||||||
* @public
|
|
||||||
* @param {Function|Array} plugin The plugin or array of plugins to add
|
|
||||||
* @returns The Turndown instance for chaining
|
|
||||||
* @type Object
|
|
||||||
*/
|
|
||||||
|
|
||||||
use: function (plugin) {
|
|
||||||
if (Array.isArray(plugin)) {
|
|
||||||
for (var i = 0; i < plugin.length; i++) this.use(plugin[i]);
|
|
||||||
} else if (typeof plugin === 'function') {
|
|
||||||
plugin(this);
|
|
||||||
} else {
|
|
||||||
throw new TypeError('plugin must be a Function or an Array of Functions')
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a rule
|
|
||||||
* @public
|
|
||||||
* @param {String} key The unique key of the rule
|
|
||||||
* @param {Object} rule The rule
|
|
||||||
* @returns The Turndown instance for chaining
|
|
||||||
* @type Object
|
|
||||||
*/
|
|
||||||
|
|
||||||
addRule: function (key, rule) {
|
|
||||||
this.rules.add(key, rule);
|
|
||||||
return this
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keep a node (as HTML) that matches the filter
|
|
||||||
* @public
|
|
||||||
* @param {String|Array|Function} filter The unique key of the rule
|
|
||||||
* @returns The Turndown instance for chaining
|
|
||||||
* @type Object
|
|
||||||
*/
|
|
||||||
|
|
||||||
keep: function (filter) {
|
|
||||||
this.rules.keep(filter);
|
|
||||||
return this
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a node that matches the filter
|
|
||||||
* @public
|
|
||||||
* @param {String|Array|Function} filter The unique key of the rule
|
|
||||||
* @returns The Turndown instance for chaining
|
|
||||||
* @type Object
|
|
||||||
*/
|
|
||||||
|
|
||||||
remove: function (filter) {
|
|
||||||
this.rules.remove(filter);
|
|
||||||
return this
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Escapes Markdown syntax
|
|
||||||
* @public
|
|
||||||
* @param {String} string The string to escape
|
|
||||||
* @returns A string with Markdown syntax escaped
|
|
||||||
* @type String
|
|
||||||
*/
|
|
||||||
|
|
||||||
escape: function (string) {
|
|
||||||
return escapes.reduce(function (accumulator, escape) {
|
|
||||||
return accumulator.replace(escape[0], escape[1])
|
|
||||||
}, string)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reduces a DOM node down to its Markdown string equivalent
|
|
||||||
* @private
|
|
||||||
* @param {HTMLElement} parentNode The node to convert
|
|
||||||
* @returns A Markdown representation of the node
|
|
||||||
* @type String
|
|
||||||
*/
|
|
||||||
|
|
||||||
function process (parentNode) {
|
|
||||||
var self = this;
|
|
||||||
return reduce.call(parentNode.childNodes, function (output, node) {
|
|
||||||
node = new Node(node, self.options);
|
|
||||||
|
|
||||||
var replacement = '';
|
|
||||||
if (node.nodeType === 3) {
|
|
||||||
replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue);
|
|
||||||
} else if (node.nodeType === 1) {
|
|
||||||
replacement = replacementForNode.call(self, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
return join(output, replacement)
|
|
||||||
}, '')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends strings as each rule requires and trims the output
|
|
||||||
* @private
|
|
||||||
* @param {String} output The conversion output
|
|
||||||
* @returns A trimmed version of the ouput
|
|
||||||
* @type String
|
|
||||||
*/
|
|
||||||
|
|
||||||
function postProcess (output) {
|
|
||||||
var self = this;
|
|
||||||
this.rules.forEach(function (rule) {
|
|
||||||
if (typeof rule.append === 'function') {
|
|
||||||
output = join(output, rule.append(self.options));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts an element node to its Markdown equivalent
|
|
||||||
* @private
|
|
||||||
* @param {HTMLElement} node The node to convert
|
|
||||||
* @returns A Markdown representation of the node
|
|
||||||
* @type String
|
|
||||||
*/
|
|
||||||
|
|
||||||
function replacementForNode (node) {
|
|
||||||
var rule = this.rules.forNode(node);
|
|
||||||
var content = process.call(this, node);
|
|
||||||
var whitespace = node.flankingWhitespace;
|
|
||||||
if (whitespace.leading || whitespace.trailing) content = content.trim();
|
|
||||||
return (
|
|
||||||
whitespace.leading +
|
|
||||||
rule.replacement(content, node, this.options) +
|
|
||||||
whitespace.trailing
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Joins replacement to the current output with appropriate number of new lines
|
|
||||||
* @private
|
|
||||||
* @param {String} output The current conversion output
|
|
||||||
* @param {String} replacement The string to append to the output
|
|
||||||
* @returns Joined output
|
|
||||||
* @type String
|
|
||||||
*/
|
|
||||||
|
|
||||||
function join (output, replacement) {
|
|
||||||
var s1 = trimTrailingNewlines(output);
|
|
||||||
var s2 = trimLeadingNewlines(replacement);
|
|
||||||
var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
|
|
||||||
var separator = '\n\n'.substring(0, nls);
|
|
||||||
|
|
||||||
return s1 + separator + s2
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether an input can be converted
|
|
||||||
* @private
|
|
||||||
* @param {String|HTMLElement} input Describe this parameter
|
|
||||||
* @returns Describe what it returns
|
|
||||||
* @type String|Object|Array|Boolean|Number
|
|
||||||
*/
|
|
||||||
|
|
||||||
function canConvert (input) {
|
|
||||||
return (
|
|
||||||
input != null && (
|
|
||||||
typeof input === 'string' ||
|
|
||||||
(input.nodeType && (
|
|
||||||
input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11
|
|
||||||
))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return TurndownService;
|
|
||||||
|
|
||||||
}());
|
|
||||||
Loading…
Reference in New Issue
Block a user