diff --git a/.cargo/config.toml b/.cargo/config.toml
index df2c8ac4..b732349c 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -28,24 +28,23 @@ TS_RS_IMPORT_EXTENSION = "ts"
# -------------------------
#
# Link the Microsoft C runtime statically on Windows. The default, dynamic
-# linkage, makes every Windows artifact -- the Server executable and the
-# VSCode extension's `.node` addon alike -- import `VCRUNTIME140.dll`, which
-# Windows does not ship: it arrives only with the Visual C++ redistributable.
-# A machine without that redistributable therefore fails to load the addon
-# with `ERROR_MOD_NOT_FOUND`, which Windows reports against the addon's own
-# path, so the artifact which is present is accused of being missing (see the
-# diagnosis in `extensions/VSCode/src/index.js`). The UCRT half of the C
-# runtime, named by the `api-ms-win-crt-*` imports, is part of Windows itself
-# and needs no such treatment.
+# linkage, makes every Windows artifact -- the Server executable and the VSCode
+# extension's `.node` addon alike -- import `VCRUNTIME140.dll`, which Windows
+# does not ship: it arrives only with the Visual C++ redistributable. A machine
+# without that redistributable therefore fails to load the addon with
+# `ERROR_MOD_NOT_FOUND`, which Windows reports against the addon's own path, so
+# the artifact which is present is accused of being missing (see the diagnosis
+# in `extensions/VSCode/src/index.js`). The UCRT half of the C runtime, named by
+# the `api-ms-win-crt-*` imports, is part of Windows itself and needs no such
+# treatment.
#
# Nothing crosses the C runtime boundary here. The addon talks to Node through
# N-API, which passes no allocations, locales or `FILE*`s between the two, and
-# it embeds the whole Server rather than linking to a second Rust artifact --
-# so the usual hazard of a statically linked runtime inside a DLL does not
-# arise.
+# it embeds the whole Server rather than linking to a second Rust artifact -- so
+# the usual hazard of a statically linked runtime inside a DLL does not arise.
#
-# This applies to the host triple as well as to a cross build, so Cargo hands
-# it to build scripts and proc macros too whenever `--target` is absent.
+# This applies to the host triple as well as to a cross build, so Cargo hands it
+# to build scripts and proc macros too whenever `--target` is absent.
#
# The `cfg` form covers `i686` and `aarch64` alongside `x86_64` -- the three
# MSVC triples the VSCode loader searches for -- so adding one of them to the
@@ -54,3 +53,15 @@ TS_RS_IMPORT_EXTENSION = "ts"
# included.
[target.'cfg(all(windows, target_env = "msvc"))']
rustflags = ["-C", "target-feature=+crt-static"]
+
+# Security
+# --------
+#
+# Set a minimum publish age to combat supply-chain attacks. Copied from
+# [Dependency Cool-Downs Land in Stable](https://weeklyrust.substack.com/p/dependency-cool-downs-land-in-stable).
+# Not yet released, so produces a warning.
+[resolver]
+incompatible-publish-age = "deny"
+
+[registry]
+global-min-publish-age = "7 days" # or "14 days", "1 week", "6 months" etc.
\ No newline at end of file
diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh
index 95b59c62..0de7b015 100755
--- a/.devcontainer/postCreateCommand.sh
+++ b/.devcontainer/postCreateCommand.sh
@@ -8,6 +8,6 @@ if [ -x /usr/local/bin/chrome ] && [ ! -e /usr/local/bin/google-chrome ]; then
sudo ln -s /usr/local/bin/chrome /usr/local/bin/google-chrome
fi
-cd server
+#
./bt install --dev
./bt build
\ No newline at end of file
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
index 0fe4e90f..3a8a5c09 100644
--- a/.github/workflows/check.yml
+++ b/.github/workflows/check.yml
@@ -50,9 +50,11 @@ jobs:
## builder
## server
## extensions/VSCode
+ # On Windows, GitHub Actions runs these commands in PowerShell, which runs
+ # `bt.ps1`; elsewhere, bash runs `bt`.
+ #
- name: Run tests
run: |
rustup update
- cd server
./bt install --dev
./bt full
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a55a543f..aa66a5d5 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -144,12 +144,15 @@ jobs:
- name: Install dependencies
run: |
${{ matrix.packages_install }}
+ # dist generated this file; the `bt` calls in this step are hand-added,
+ # which `allow-dirty = ["ci"]` in `dist-workspace.toml` permits.
+ #
- name: Build artifacts
run: |
- server/bt prerelease
+ ./bt prerelease
# Actually do builds and make zips and whatnot
dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json
- server/bt postrelease ${{ matrix.dist_args }} ${{ needs.plan.outputs.tag-flag }}
+ ./bt postrelease ${{ matrix.dist_args }} ${{ needs.plan.outputs.tag-flag }}
echo "dist ran successfully"
- id: cargo-dist
name: Post-build
diff --git a/.ignore b/.ignore
new file mode 100644
index 00000000..a423113c
--- /dev/null
+++ b/.ignore
@@ -0,0 +1,30 @@
+# Copyright (C) 2026 Bryan A. Jones.
+#
+# This file is part of the CodeChat Editor.
+#
+# The CodeChat Editor is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
+#
+# The CodeChat Editor is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# the CodeChat Editor. If not, see
+# [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/).
+#
+# `.ignore` -- Show hidden directories to the CodeChat Editor
+# ===========================================================
+#
+# The CodeChat Editor's project cache walks the project with the
+# [ignore](https://docs.rs/ignore) crate, which skips hidden files and
+# directories unless an ignore file whitelists them. These directories contain
+# fragments gathered elsewhere in the project, such as the calls to `bt`
+# gathered in `docs/implementation.md`. ripgrep honors this file as well.
+!.github/
+!.devcontainer/
+
+# CodeChat Editor lexer: python
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 13cc1bd6..531c7b21 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,15 @@ Changelog
* No changes.
+Version 0.3.1 -- 2026-Oct-21
+----------------------------
+
+* Support navigation of links in gather elements, fragments, and cross
+ references.
+* Clicking on a link activates the TinyMCE editor when the link's doc block is
+ pure HTML, instead of navigating to the link.
+* Improved HTML to Markdown conversion.
+
Version 0.3.0 -- 2026-Oct-09
----------------------------
diff --git a/CLAUDE.md b/CLAUDE.md
index 847a4190..452a0329 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -32,18 +32,50 @@ void foo();
// since they are separated by a code block.
```
+### Cross-references, gather elements, and fragments
+
+To refer to text in another section of the current document or any file in this
+project:
+
+1. Assign a unique id for that section: `section
+ title`; in a code file, this must be placed in a doc block. Any HTML
+ element with an id can be the target of either a hyperlink or a
+ cross-reference.
+2. Refer to it using a hyperlink or cross-reference. For example, the following
+ syntax links directly to the `` inside this section's heading:
+ `[Foo](#cc-sZ0Glsgjhu)` (link text is "Foo") / `` (link text comes from the cross-referenced span:
+ "Cross-references, gather elements, and fragments"); the same syntax (a bare
+ id with no file) also links to any file in the project.
+
+When implementing a specification:
+
+1. Label each doc block preceding non-contiguous section of code of more than
+ three lines with a fragment placed at the end of the doc block: ``.
+2. Gather these fragments in a subheading following the specification: `Implementation of [title of this section]
`.
+
+Each id must be unique throughout the entire project. To create an id:
+
+1. Randomly generate an id of the form `cc-[10 random ASCII letters or
+ numbers]`.
+2. grep the project to ensure this id is unique.
+
Architecture
------------
-A Visual Studio Code extension in `extensions/VSCode` exchanges messages with
-the CodeChat Editor Server, located in `server/` (also termed the Server), which
-also exchanges messages with the CodeChat Editor Client (also termed the Client)
-located in `client/`.
+A Visual Studio Code extension in `extensions/VSCode` or the standalone server
+in `extensions/standalone` exchanges messages with the CodeChat Editor Server,
+located in `server/` (also termed the Server), which also exchanges messages
+with the CodeChat Editor Client (also termed the Client) located in `client/`.
Project build
-------------
-All build commands must be executed from the `server/` directory.
+Execute these build commands, which run the [`bt`](#cc-1s8bUScadp) script, from
+the root of the project.
* To build the entire project, execute `./bt build`.
* To format and lint the entire project, execute `./bt flint`.
@@ -139,6 +171,8 @@ Because comments become doc blocks, these mechanical rules apply:
* Denote paragraphs using an empty comment (`//` or `///`), not an empty line.
* A comment on the same line as code is never a doc block; it stays part of the
code block.
+* When commenting out code, use `/*** ... */` to prevent the code from being
+ treated as a doc block.
### File structure
@@ -148,8 +182,8 @@ Source files follow this template (a few predate it; match it in new files):
2. The GPL copyright and license block, copied verbatim from a neighboring file
rather than retyped. In Rust it uses plain `//`, so it stays a doc block
separate from the title which follows it.
-3. A single level-1 heading titling the file: the file name in a monospaced font
- -- matching the file's actual name -- then `--`, then a short description.
+3. A single level-1 heading titling the file: the file name in backticks --
+ matching the file's actual name -- then `--`, then a short description.
4. The file-level design narrative, if the file needs one.
5. A `Modules` section, if the file has submodules, then an `Imports` section.
In Rust its subsections are `### Standard library`, `### Third-party`, and
@@ -186,7 +220,7 @@ use std::collections::HashMap;
### TypeScript specifics
-* Use `//` tags instead of `/**` blocks.
+* Use `//` comments instead of `/**` blocks.
* Document parameters as described above instead of using `@param` tags.
### Editing existing comments
diff --git a/Cargo.lock b/Cargo.lock
index 40f2d991..d4d0b77e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -21,9 +21,9 @@ dependencies = [
[[package]]
name = "actix-files"
-version = "0.7.0"
+version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed87c765e9e5be096cc29b43d04b932209892ec871d05fe255f0ae1ccfed9a06"
+checksum = "db96438ec043d438f36bc211e260f0bec3de11dd0921feebe6b776e5a77ff3f9"
dependencies = [
"actix-http",
"actix-service",
@@ -40,13 +40,14 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"v_htmlescape",
+ "winapi-util",
]
[[package]]
name = "actix-http"
-version = "3.18.12"
+version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2ab7d72c0bb26f10df574ea560177b4de2f27ee8f421b2c1e9b75628bd9db23"
+checksum = "b796ba3c974e5133f6ca1a2c4e97c5941ea1a46d202df83344afaa18219f1a62"
dependencies = [
"actix-codec",
"actix-service",
@@ -88,7 +89,7 @@ checksum = "367f814ad4afbac74f07df5001214da65f65e185c90ef56c4dd8df23f8695b9b"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
]
[[package]]
@@ -119,9 +120,9 @@ dependencies = [
[[package]]
name = "actix-server"
-version = "2.9.8"
+version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5c8e46e9b40172e4cc67adcd172bd42e932a5a0aa7f603eee88e60f70452f22"
+checksum = "5e296908c4d6f3a191e6b9873dbdfec8023350a89129d2b470f61d17233931ee"
dependencies = [
"actix-rt",
"actix-service",
@@ -155,9 +156,9 @@ dependencies = [
[[package]]
name = "actix-web"
-version = "4.15.0"
+version = "4.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbacab3593b6b4f7be815076fc52d60a83c873426824675417e2abdd229e2e36"
+checksum = "67756b0fc99e07b8702b9a856e3d6ec036387243c2ce6310070d1e8f02680bb0"
dependencies = [
"actix-codec",
"actix-http",
@@ -192,6 +193,7 @@ dependencies = [
"smallvec",
"socket2",
"time",
+ "tokio",
"tracing",
"url",
]
@@ -205,7 +207,7 @@ dependencies = [
"actix-router",
"proc-macro2",
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
]
[[package]]
@@ -269,15 +271,15 @@ dependencies = [
[[package]]
name = "alloc-no-stdlib"
-version = "2.0.4"
+version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
+checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a"
[[package]]
name = "alloc-stdlib"
-version = "0.2.4"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195"
+checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1"
dependencies = [
"alloc-no-stdlib",
]
@@ -290,9 +292,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "ammonia"
-version = "4.2.1"
+version = "4.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "850babe4998b8b9e2dad65a56be4ee1bb977bc3cb3934fa2b28feb2297c30a55"
+checksum = "1f45dfa685e1ec33b1bf1a3b93b8047cbdbef41b6040deebbe6bea7720a45574"
dependencies = [
"cssparser 0.38.0",
"html5ever 0.40.1",
@@ -367,9 +369,9 @@ checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
[[package]]
name = "arc-swap"
-version = "1.9.2"
+version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b"
+checksum = "380962b3d93b99642e50b9febf72c071e97e8b32d86eae9cf4ef582bb960ae6e"
dependencies = [
"rustversion",
]
@@ -421,7 +423,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
]
[[package]]
@@ -438,9 +440,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "aws-lc-rs"
-version = "1.18.1"
+version = "1.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e"
+checksum = "faac5829c2b74c28f830747e7818ccfb684261b5f48a1118b1e2a13d36dfab13"
dependencies = [
"aws-lc-sys",
"zeroize",
@@ -448,9 +450,9 @@ dependencies = [
[[package]]
name = "aws-lc-sys"
-version = "0.45.0"
+version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27"
+checksum = "f1622d8446a2d4b2ce0c7eefc73dd43a99779028d5ee5c2dd8073a658ba8a2bc"
dependencies = [
"cc",
"cmake",
@@ -498,9 +500,9 @@ dependencies = [
[[package]]
name = "brotli"
-version = "8.0.4"
+version = "9.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3"
+checksum = "f8b851b75c23ca7873623d612fe49bd1989aeb03d08fb9432187eb253d3d4c6b"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
@@ -509,9 +511,9 @@ dependencies = [
[[package]]
name = "brotli-decompressor"
-version = "5.0.3"
+version = "6.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583"
+checksum = "941cd9bd4ddab83cb46fa5a2d428f1c857b24ac78cb876cf7beb710840934bd7"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
@@ -566,9 +568,9 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.7.0"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50a649af8a827553c29fb0cb4bd4a6f1a0dd695bd3232b9bc98bd9c8a3ffbb8b"
+checksum = "877bf67ac8e52449eb2f9e98a344ea163beb70dd1f9eebe3cc5357dcfeaee4df"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -650,7 +652,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
]
[[package]]
@@ -679,7 +681,7 @@ dependencies = [
[[package]]
name = "codechat-editor-server"
-version = "0.3.0"
+version = "0.3.1"
dependencies = [
"actix-files",
"actix-rt",
@@ -740,7 +742,7 @@ dependencies = [
[[package]]
name = "codechat-editor-standalone"
-version = "0.3.0"
+version = "0.3.1"
dependencies = [
"actix-http",
"actix-rt",
@@ -768,7 +770,7 @@ dependencies = [
[[package]]
name = "codechat-editor-vscode-extension"
-version = "0.3.0"
+version = "0.3.1"
dependencies = [
"codechat-editor-server",
"log",
@@ -786,9 +788,9 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "combine"
-version = "4.6.8"
+version = "4.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e"
+checksum = "4356eba6976435498d05a311ee9fc486da3bc81c200d3f0d7dd9703b41913f92"
dependencies = [
"bytes",
"memchr",
@@ -918,9 +920,9 @@ dependencies = [
[[package]]
name = "crc32fast"
-version = "1.5.2"
+version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78"
+checksum = "0ba106e844045ddc02c20768ac4b0b7cb45f2cc2ecbd833ac9f5dacb183c2055"
dependencies = [
"cfg-if",
]
@@ -999,7 +1001,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d045de693cb712d0b22c6a64be5b953f67b3ce00ab5ad3dd5d8b441886ab8e1a"
dependencies = [
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
]
[[package]]
@@ -1016,9 +1018,9 @@ checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06"
[[package]]
name = "deranged"
-version = "0.5.8"
+version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
+checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e"
[[package]]
name = "derive_more"
@@ -1074,9 +1076,9 @@ dependencies = [
[[package]]
name = "dirs"
-version = "6.0.0"
+version = "7.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
+checksum = "8d57d423b3c82e89b9a24ca3091fee61f456a26edbd28d26c65906f4bc1dcd8f"
dependencies = [
"dirs-sys",
]
@@ -1111,7 +1113,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
]
[[package]]
@@ -1211,9 +1213,9 @@ dependencies = [
[[package]]
name = "equivalent"
-version = "1.0.2"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826"
[[package]]
name = "errno"
@@ -1258,9 +1260,9 @@ dependencies = [
[[package]]
name = "find-msvc-tools"
-version = "0.1.14"
+version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
+checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48"
[[package]]
name = "flate2"
@@ -1391,7 +1393,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
]
[[package]]
@@ -1535,7 +1537,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "htmd"
version = "0.5.5"
-source = "git+https://github.com/bjones1/htmd.git?branch=dom-interface#68e54497edf1be23dbf73fe1bd6bdf6170b729cc"
+source = "git+https://github.com/bjones1/htmd.git?branch=dom-interface#2ee01b1603d596df48f8e00261dc10f87dd686e2"
dependencies = [
"html5ever 0.39.0",
"markup5ever_rcdom",
@@ -1619,9 +1621,9 @@ dependencies = [
[[package]]
name = "http-range"
-version = "0.1.5"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
+checksum = "2149c4b4b83390c71fab669730347d578ec6bdf759f2160c581ad8b8819733b4"
[[package]]
name = "httparse"
@@ -1909,9 +1911,9 @@ dependencies = [
[[package]]
name = "impl-more"
-version = "0.3.8"
+version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30c0cddce6b7505483307994f60d97c4b156020e1504d0cf1f05a21b1b325e64"
+checksum = "2d3a73c82a0b0747dba739b380c046a140b5ae747234bf701df3460282da7193"
[[package]]
name = "indexmap"
@@ -2143,9 +2145,9 @@ checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb"
[[package]]
name = "libc"
-version = "0.2.190"
+version = "0.2.191"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
+checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
[[package]]
name = "libloading"
@@ -2285,9 +2287,9 @@ dependencies = [
[[package]]
name = "lru"
-version = "0.18.5"
+version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef9ac18847474e638e3702b76c65d4eb93428471a74778ef0f1be711717f89b5"
+checksum = "4a9f71e3591c7ec44d3e563e87bf14ddc2bada8519dca046e5b0e560cd004e2d"
dependencies = [
"hashbrown 0.17.1",
]
@@ -2480,7 +2482,7 @@ dependencies = [
"napi-derive-backend",
"proc-macro2",
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
]
[[package]]
@@ -2493,7 +2495,7 @@ dependencies = [
"proc-macro2",
"quote",
"semver",
- "syn 3.0.7",
+ "syn 3.0.9",
]
[[package]]
@@ -2517,6 +2519,18 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
+[[package]]
+name = "nix"
+version = "0.31.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+]
+
[[package]]
name = "nohash-hasher"
version = "0.2.0"
@@ -3143,9 +3157,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pest"
-version = "2.9.3"
+version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b568374ba38b33a6c627141f891faf16902b08d2db26b8ede1bcb0a15b1919fa"
+checksum = "fd47a460a9cbc3b3429c7e22bf0d76653e779319bae45a499ca010c9a990bb23"
dependencies = [
"memchr",
"psm",
@@ -3155,9 +3169,9 @@ dependencies = [
[[package]]
name = "pest_derive"
-version = "2.9.3"
+version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b66e184b924cebaaff20ab2256ca52f12332d528a39aa76553b5d96f92aacf7f"
+checksum = "203e46697708f7519ab1608393cf89239cafb46b8e6d08a8ffe359359b11ef44"
dependencies = [
"pest",
"pest_generator",
@@ -3165,22 +3179,22 @@ dependencies = [
[[package]]
name = "pest_generator"
-version = "2.9.3"
+version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a87478d267e4de54a626af9754f2f0f58e927aac6ed0575fe89bc05ad6851694"
+checksum = "e84ae6340a458d3d6cfc73cf328cf3cfe90b2484cd0de25eb66aa954615ed4d5"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
]
[[package]]
name = "pest_meta"
-version = "2.9.3"
+version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f986f248b4241ac359b831f6139aaa34e03b08a37b6caf7e201a33f95c869e1"
+checksum = "b57f92f32b3dbce6598fd8f5cdf1f06a6072570b8103fa21728fa9036923e710"
dependencies = [
"pest",
]
@@ -3445,9 +3459,9 @@ dependencies = [
[[package]]
name = "pulldown-cmark"
-version = "0.13.4"
+version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e"
+checksum = "278e9268c93b9e498a2da46c34d90ba93a3255eb99009646398f34bc73ab36eb"
dependencies = [
"bitflags",
"memchr",
@@ -3747,9 +3761,9 @@ dependencies = [
[[package]]
name = "rustls"
-version = "0.23.45"
+version = "0.23.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
+checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf"
dependencies = [
"aws-lc-rs",
"log",
@@ -3762,9 +3776,9 @@ dependencies = [
[[package]]
name = "rustls-native-certs"
-version = "0.8.4"
+version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
+checksum = "4a073f5dc7469f984c52ad2752b63b0807745133b6de880b7b64c1ac4c48aec4"
dependencies = [
"openssl-probe",
"rustls-pki-types",
@@ -3838,9 +3852,9 @@ checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f"
[[package]]
name = "rustls-webpki"
-version = "0.103.15"
+version = "0.103.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
+checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281"
dependencies = [
"aws-lc-rs",
"ring",
@@ -3871,9 +3885,9 @@ dependencies = [
[[package]]
name = "schannel"
-version = "0.1.29"
+version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
+checksum = "9cb23c424d8ee841bb4d813677ca237e2a610067d594fec130f87d6093495ab5"
dependencies = [
"windows-sys 0.61.2",
]
@@ -3962,15 +3976,16 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
]
[[package]]
name = "serde_json"
-version = "1.0.151"
+version = "1.0.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
+checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
dependencies = [
+ "foldhash 0.2.0",
"indexmap",
"itoa",
"memchr",
@@ -3987,7 +4002,7 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
]
[[package]]
@@ -4028,9 +4043,9 @@ dependencies = [
[[package]]
name = "sha2"
-version = "0.11.0"
+version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
+checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -4237,9 +4252,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "3.0.7"
+version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d62a2e0561533f2ca2561d0cf27fd9fedb640a1bf2616ff5d5c80d99017faadc"
+checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
dependencies = [
"proc-macro2",
"quote",
@@ -4263,7 +4278,7 @@ checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
]
[[package]]
@@ -4335,13 +4350,13 @@ dependencies = [
[[package]]
name = "thirtyfour"
-version = "0.37.7"
+version = "0.38.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8df97330642045e09a763b839ebedc6e76eb77bf202dad474898f5a631b4dc46"
+checksum = "4ee66d96cfd32e01668efd6ec62caf5f7000438d771a9d673bf0c727bc05fbb6"
dependencies = [
"arc-swap",
"async-trait",
- "base64 0.22.1",
+ "base64 0.23.1",
"bytes",
"cfg-if",
"const_format",
@@ -4369,13 +4384,13 @@ dependencies = [
[[package]]
name = "thirtyfour-macros"
-version = "0.3.0"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d712d0fd1988fb67a3fd89d10ead116544448bad01e43d326bd64dd8e09b5ea4"
+checksum = "94196cd236b8087d4059b7cfba1e1c864f20262ecb38aa7b1da9e66d6f07ab77"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.119",
+ "syn 3.0.9",
]
[[package]]
@@ -4415,7 +4430,7 @@ checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
]
[[package]]
@@ -4486,9 +4501,9 @@ checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
[[package]]
name = "tokio"
-version = "1.53.2"
+version = "1.54.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044"
+checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3"
dependencies = [
"bytes",
"libc",
@@ -4509,7 +4524,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
]
[[package]]
@@ -4714,9 +4729,9 @@ dependencies = [
[[package]]
name = "typed-path"
-version = "0.12.3"
+version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e"
+checksum = "54ec2755506471f54baf5e4bb5499d8e510cc1caf271270e778a736bd2d7336c"
[[package]]
name = "typemap-ors"
@@ -4753,9 +4768,9 @@ checksum = "0825d2e09dcae814a4273c1e1435979d414ede25b2ee235b1545da6682483765"
[[package]]
name = "unicode-ident"
-version = "1.0.26"
+version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
+checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
[[package]]
name = "unicode-segmentation"
@@ -4945,7 +4960,7 @@ dependencies = [
"bumpalo",
"proc-macro2",
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
"wasm-bindgen-shared",
]
@@ -5033,9 +5048,9 @@ dependencies = [
[[package]]
name = "webpki-root-certs"
-version = "1.0.9"
+version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b"
+checksum = "58ec80e73fd8baf57bf0d9615554c75348bda990d6d5bbeab964dd068319fecb"
dependencies = [
"rustls-pki-types",
]
@@ -5460,24 +5475,24 @@ checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
"synstructure",
]
[[package]]
name = "zerocopy"
-version = "0.8.62"
+version = "0.8.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86502bf56ac7c77571a32e2647bb2a15894565e981fb2a48d7bde2d91c965a9d"
+checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.8.62"
+version = "0.8.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5457206954b06561e2608c7e19cf58b1926586d999c246eebe4502f7e2039d1a"
+checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f"
dependencies = [
"proc-macro2",
"quote",
@@ -5501,7 +5516,7 @@ checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
"synstructure",
]
@@ -5543,19 +5558,20 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
]
[[package]]
name = "zip"
-version = "8.6.0"
+version = "9.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b"
+checksum = "6e04976da629d3b04204ba8105b396a193af8ad2583b2c79bfb3ab8af580a11e"
dependencies = [
"crc32fast",
"flate2",
"indexmap",
"memchr",
+ "nix",
"typed-path",
"zopfli",
]
@@ -5574,9 +5590,9 @@ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
[[package]]
name = "zopfli"
-version = "0.8.3"
+version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
+checksum = "aaf7fc5d30c28483d93805c4a5e12b05bbb52407fa67c5f8bd552374cd01fb11"
dependencies = [
"bumpalo",
"crc32fast",
@@ -5586,27 +5602,27 @@ dependencies = [
[[package]]
name = "zstd"
-version = "0.13.3"
+version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
+checksum = "db3ae9bf90af6e40197bef767eeefc46ad6397420fe873830bd85e5fbbfb3c70"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
-version = "7.3.0"
+version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64d80649ab6db9d9f6f9c80a40becd948eda4714a0a5ac8c4d157a32231c7882"
+checksum = "ded520f16b29e1a9b771c722221b2b0a0398c2316d9bb3c42b458a5414d7434b"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
-version = "2.1.1+zstd.1.5.7"
+version = "2.1.2+zstd.1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aeec9eaf2dffbbd09201e23bd0ffcbaa33bb8e9266a10734fd7ed90a85eca078"
+checksum = "481ada9c543b2323cbb7bbdbb5249177fbf0188ed959ed2c15e4b22421c84ed3"
dependencies = [
"cc",
"pkg-config",
diff --git a/Cargo.toml b/Cargo.toml
index 78661501..556547a8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,8 +16,7 @@
# the CodeChat Editor. If not, see
# [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/).
#
-# `Cargo.toml` -- Workspace manifest for the CodeChat Editor
-# ==========================================================
+# # `Cargo.toml` -- Workspace manifest for the CodeChat Editor
#
# Every Rust package in this repository except `builder` is a member of this one
# workspace. Sharing one `target/` only avoids recompilation when the packages
@@ -64,7 +63,7 @@ homepage = "https://codechat-editor.onrender.com/fw/fsb/opt/render/project/src/R
keywords = ["literate programming"]
license = "GPL-3.0-only"
repository = "https://github.com/bjones1/CodeChat_Editor"
-version = "0.3.0"
+version = "0.3.1"
# Shared dependencies
# -------------------
diff --git a/server/bt b/bt
similarity index 67%
rename from server/bt
rename to bt
index 47788c5c..0a3e0986 100755
--- a/server/bt
+++ b/bt
@@ -16,10 +16,13 @@
# You should have received a copy of the GNU General Public License along with
# the CodeChat Editor. If not, see
# [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/).
-#
-# # `bt` -- Shortcut to run the CodeChat Editor build tool
-# See [passing all command-line arguments](https://www.scivision.dev/powershell-pass-all-command-args/).
-cargo run --manifest-path=$(dirname "${BASH_SOURCE[0]}")/../builder/Cargo.toml -- "$@"
+# `bt` -- Shortcut to run the CodeChat Editor build tool
+# ======================================================
+#
+# See
+# [passing all command-line arguments](https://www.scivision.dev/powershell-pass-all-command-args/).
+#
+cargo run --manifest-path=$(dirname "${BASH_SOURCE[0]}")/builder/Cargo.toml -- "$@"
-# CodeChat Editor lexer: sh.
\ No newline at end of file
+# CodeChat Editor lexer: sh.
diff --git a/server/bt.ps1 b/bt.ps1
similarity index 68%
rename from server/bt.ps1
rename to bt.ps1
index 834665b3..cae8b1df 100644
--- a/server/bt.ps1
+++ b/bt.ps1
@@ -15,12 +15,15 @@
# You should have received a copy of the GNU General Public License along with
# the CodeChat Editor. If not, see
# [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/).
-#
-# # `bt` -- Shortcut to run the CodeChat Editor build tool
-# See [passing all command-line arguments](https://www.scivision.dev/powershell-pass-all-command-args/).
-$manifest_path = Join-Path -Path $PSScriptRoot -ChildPath ../builder/Cargo.toml
+# `bt.ps1` -- Shortcut to run the CodeChat Editor build tool
+# ==========================================================
+#
+# See
+# [passing all command-line arguments](https://www.scivision.dev/powershell-pass-all-command-args/).
+#
+$manifest_path = Join-Path -Path $PSScriptRoot -ChildPath builder/Cargo.toml
echo $manifest_path
cargo run --manifest-path=$manifest_path -- $args
-# CodeChat Editor lexer: python
\ No newline at end of file
+# CodeChat Editor lexer: python
diff --git a/builder/Cargo.lock b/builder/Cargo.lock
index d1a211de..035858c0 100644
--- a/builder/Cargo.lock
+++ b/builder/Cargo.lock
@@ -128,7 +128,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
]
[[package]]
@@ -175,9 +175,9 @@ checksum = "a74858bcfe44b22016cb49337d7b6f04618c58e5dbfdef61b06b8c434324a0bc"
[[package]]
name = "defmt"
-version = "1.1.1"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
+checksum = "e4a0795ac740cb54d7b3775c9c17345f6aaecf884c01733b39d6017738e0e3e1"
dependencies = [
"bitflags",
"defmt-macros",
@@ -185,21 +185,21 @@ dependencies = [
[[package]]
name = "defmt-macros"
-version = "1.1.1"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
+checksum = "840bb056bd99c495c9188198a258ab787c79f306d864db3e6e8451442d6d8366"
dependencies = [
"defmt-parser",
"proc-macro2",
"quote",
- "syn 2.0.119",
+ "syn 3.0.9",
]
[[package]]
name = "defmt-parser"
-version = "1.0.0"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
+checksum = "71958f69fb2de856c5dd51ac9377e986ca1aa5919f931bf5395a807277f889a0"
dependencies = [
"thiserror",
]
@@ -295,9 +295,9 @@ dependencies = [
[[package]]
name = "libc"
-version = "0.2.190"
+version = "0.2.191"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
+checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
[[package]]
name = "log"
@@ -440,7 +440,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
]
[[package]]
@@ -462,9 +462,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "3.0.7"
+version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d62a2e0561533f2ca2561d0cf27fd9fedb640a1bf2616ff5d5c80d99017faadc"
+checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
dependencies = [
"proc-macro2",
"quote",
@@ -488,14 +488,14 @@ checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
dependencies = [
"proc-macro2",
"quote",
- "syn 3.0.7",
+ "syn 3.0.9",
]
[[package]]
name = "unicode-ident"
-version = "1.0.26"
+version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
+checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
[[package]]
name = "unicode-width"
diff --git a/builder/Cargo.toml b/builder/Cargo.toml
index ca89ffe5..72798750 100644
--- a/builder/Cargo.toml
+++ b/builder/Cargo.toml
@@ -30,7 +30,7 @@ version = "0.1.0"
#
# This package is deliberately *not* a member of the workspace rooted at the
# repository root, which excludes it; this empty table makes it a workspace root
-# of its own, See the note in the root `Cargo.toml` for the full explanation.
+# of its own; see for the full explanation.
#
# It must follow `[package]`, which `cargo sort` requires to come first.
[workspace]
diff --git a/client/package.json5 b/client/package.json5
index b399b07b..91ac6f99 100644
--- a/client/package.json5
+++ b/client/package.json5
@@ -43,7 +43,7 @@
url: 'https://github.com/bjones1/CodeChat_editor',
},
type: 'module',
- version: '0.3.0',
+ version: '0.3.1',
dependencies: {
'@codemirror/commands': '^6.11.1',
'@codemirror/lang-cpp': '^6.0.3',
@@ -61,46 +61,46 @@
'@codemirror/legacy-modes': '^6.5.5',
'@codemirror/lint': '^6.9.7',
'@codemirror/state': '^6.7.6',
- '@codemirror/view': '^6.43.14',
+ '@codemirror/view': '^6.43.15',
'@hpcc-js/wasm-graphviz': '^1.29.2',
'@mathjax/mathjax-newcm-font': '^4.1.3',
codemirror: '^6.0.2',
mathjax: '^4.1.3',
- mermaid: '^12.1.0',
+ mermaid: '^12.2.0',
'pdfjs-dist': '^6.4.299',
- tinymce: '^8.9.3',
+ tinymce: '^8.10.0',
'toastify-js': '^1.12.0',
},
devDependencies: {
- '@eslint/css': '^2.0.0',
+ '@eslint/css': '^2.0.1',
'@eslint/js': '^10.0.1',
'@types/chai': '^5.2.3',
'@types/mocha': '^10.0.10',
- '@types/node': '^26.6.4',
+ '@types/node': '^26.6.5',
'@types/toastify-js': '^1.12.4',
- '@typescript-eslint/eslint-plugin': '^8.71.1',
- '@typescript-eslint/parser': '^8.71.1',
+ '@typescript-eslint/eslint-plugin': '^8.72.0',
+ '@typescript-eslint/parser': '^8.72.0',
'@typescript/native': 'npm:typescript@^7.0.2',
/* The accessibility audit engine driven by the Server's `overall_a11y`
browser tests. */
'axe-core': '^4.14.0',
chai: '^6.3.0',
esbuild: '^0.28.2',
- eslint: '^10.12.0',
+ eslint: '^10.13.0',
'eslint-config-prettier': '^10.1.8',
'eslint-plugin-import': '^2.32.0',
- 'eslint-plugin-prettier': '^5.5.6',
+ 'eslint-plugin-prettier': '^5.5.7',
globals: '^17.13.0',
mocha: '^12.0.3',
- 'npm-check-updates': '^23.1.0',
- prettier: '^3.9.9',
+ 'npm-check-updates': '^23.1.1',
+ prettier: '^3.9.10',
/* Typescript 7 support for eslint is blocked per
[this issue](https://github.com/typescript-eslint/typescript-eslint/issues/10940).
Instead, see
[Running Typescript 7.0 Side-by-Side with TypeScript 6.0](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/#running-side-by-side-with-typescript-6.0)
for a workaround. */
typescript: 'npm:@typescript/typescript6@^6.0.2',
- 'typescript-eslint': '^8.71.1',
+ 'typescript-eslint': '^8.72.0',
},
scripts: {
test: 'echo "Error: no test specified" && exit 1',
diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml
index 69d8f88d..d85237ed 100644
--- a/client/pnpm-lock.yaml
+++ b/client/pnpm-lock.yaml
@@ -57,8 +57,8 @@ importers:
specifier: ^6.7.6
version: 6.7.6
'@codemirror/view':
- specifier: ^6.43.14
- version: 6.43.14
+ specifier: ^6.43.15
+ version: 6.43.15
'@hpcc-js/wasm-graphviz':
specifier: ^1.29.2
version: 1.29.2
@@ -72,24 +72,24 @@ importers:
specifier: ^4.1.3
version: 4.1.3
mermaid:
- specifier: ^12.1.0
- version: 12.1.0
+ specifier: ^12.2.0
+ version: 12.2.0
pdfjs-dist:
specifier: ^6.4.299
version: 6.4.299
tinymce:
- specifier: ^8.9.3
- version: 8.9.3
+ specifier: ^8.10.0
+ version: 8.10.0
toastify-js:
specifier: ^1.12.0
version: 1.12.0
devDependencies:
'@eslint/css':
- specifier: ^2.0.0
- version: 2.0.0
+ specifier: ^2.0.1
+ version: 2.0.1
'@eslint/js':
specifier: ^10.0.1
- version: 10.0.1(eslint@10.12.0(supports-color@8.1.1))
+ version: 10.0.1(eslint@10.13.0(supports-color@8.1.1))
'@types/chai':
specifier: ^5.2.3
version: 5.2.3
@@ -97,17 +97,17 @@ importers:
specifier: ^10.0.10
version: 10.0.10
'@types/node':
- specifier: ^26.6.4
- version: 26.6.4
+ specifier: ^26.6.5
+ version: 26.6.5
'@types/toastify-js':
specifier: ^1.12.4
version: 1.12.4
'@typescript-eslint/eslint-plugin':
- specifier: ^8.71.1
- version: 8.71.1(@typescript-eslint/parser@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1))(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1)
+ specifier: ^8.72.0
+ version: 8.72.0(@typescript-eslint/parser@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1))(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1)
'@typescript-eslint/parser':
- specifier: ^8.71.1
- version: 8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1)
+ specifier: ^8.72.0
+ version: 8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1)
'@typescript/native':
specifier: npm:typescript@^7.0.2
version: typescript@7.0.2
@@ -121,17 +121,17 @@ importers:
specifier: ^0.28.2
version: 0.28.2
eslint:
- specifier: ^10.12.0
- version: 10.12.0(supports-color@8.1.1)
+ specifier: ^10.13.0
+ version: 10.13.0(supports-color@8.1.1)
eslint-config-prettier:
specifier: ^10.1.8
- version: 10.1.8(eslint@10.12.0(supports-color@8.1.1))
+ version: 10.1.8(eslint@10.13.0(supports-color@8.1.1))
eslint-plugin-import:
specifier: ^2.32.0
- version: 2.32.0(@typescript-eslint/parser@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1))(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1)
+ version: 2.32.0(@typescript-eslint/parser@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1))(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1)
eslint-plugin-prettier:
- specifier: ^5.5.6
- version: 5.5.6(eslint-config-prettier@10.1.8(eslint@10.12.0(supports-color@8.1.1)))(eslint@10.12.0(supports-color@8.1.1))(prettier@3.9.9)
+ specifier: ^5.5.7
+ version: 5.5.7(eslint-config-prettier@10.1.8(eslint@10.13.0(supports-color@8.1.1)))(eslint@10.13.0(supports-color@8.1.1))(prettier@3.9.10)
globals:
specifier: ^17.13.0
version: 17.13.0
@@ -139,17 +139,17 @@ importers:
specifier: ^12.0.3
version: 12.0.3
npm-check-updates:
- specifier: ^23.1.0
- version: 23.1.0
+ specifier: ^23.1.1
+ version: 23.1.1
prettier:
- specifier: ^3.9.9
- version: 3.9.9
+ specifier: ^3.9.10
+ version: 3.9.10
typescript:
specifier: npm:@typescript/typescript6@^6.0.2
version: '@typescript/typescript6@6.0.2'
typescript-eslint:
- specifier: ^8.71.1
- version: 8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1)
+ specifier: ^8.72.0
+ version: 8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1)
packages:
@@ -180,8 +180,8 @@ packages:
'@chevrotain/utils@13.2.0':
resolution: {integrity: sha512-SzKYY68rseOrb9KILVzPsl6SjkQtdWw01SRGB5aCVI5kFcHltQsOYV3JB0Ffst72DgkvHCyDQ0NLwxtmCCf97Q==}
- '@codemirror/autocomplete@6.20.3':
- resolution: {integrity: sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==}
+ '@codemirror/autocomplete@6.20.4':
+ resolution: {integrity: sha512-yF8RhOf2SXlg00+qtupzLhrglcas61boqaONiNcbPIebHCpJocpqTBA6dyrtUrQO47hqyTdeob/3XnYilT7TaQ==}
'@codemirror/commands@6.11.1':
resolution: {integrity: sha512-O/4hG3SC1YwcmQ0d2UVNDs+AsaNWd1iHVxbTeEBuqH+6bExAiPK3iS/BvpY6rZGURALv4ZD3sIgcCmRvw3ehBg==}
@@ -237,8 +237,11 @@ packages:
'@codemirror/streamparser@6.0.0':
resolution: {integrity: sha512-JTFPF6xrfjt0mnfGPgzcLiMwYmvIyNaoizp8BYzv8FE8KrT6GBGDZ190rtt+O7Wke3gG/MFHyiY9YGNeimof4w==}
- '@codemirror/view@6.43.14':
- resolution: {integrity: sha512-hC45YnmJNR1rz42OF57BLoQNFMV12/13drnvibDsR2quNDmldcb4WvYVV81hFQg56i9lcqQMEDqilhX2QDcGQQ==}
+ '@codemirror/view@6.43.15':
+ resolution: {integrity: sha512-HHsfpqFeHyXLs983pSRe38Xajs0vFWZQ2BuX0m7Ki8w3jZ5iD8cuR3lBz2/5/X53Dhf32SBHKh6rtSTB6tKeGw==}
+
+ '@dual-bundle/import-meta-resolve@4.2.1':
+ resolution: {integrity: sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==}
'@esbuild/aix-ppc64@0.28.2':
resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==}
@@ -418,12 +421,12 @@ packages:
resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
- '@eslint/css-tree@4.1.1':
- resolution: {integrity: sha512-A+s89eP+yDAPkKjnIOfjlkGifg5J7GPn01nWx14UjBVWktfkxOU4975F9ZZ13xNxkGiblNcBaAqgKhnQrW6SWA==}
+ '@eslint/css-tree@4.1.2':
+ resolution: {integrity: sha512-rVcxLmwSkEL3aWS3vAgsTRrQHoYE1b0XcgDtKHDJ92XOkyUpcbS/g0tUJKhvCtlEhYzgKBWsgDd+Z/aPhd166w==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
- '@eslint/css@2.0.0':
- resolution: {integrity: sha512-lODVRbtxOXgcSOWNvv3gE4A8oRTSUHefA0tX9zrWUD57F+5RTLGDX9I8bnHQROO8nWb0hrmke0uO23pTlNfZ0A==}
+ '@eslint/css@2.0.1':
+ resolution: {integrity: sha512-1qIQFzuFeTaHF3ewzsj/HUDC9RTDwfBHGWdFvpmOG0R/Tb3Mt+17yltbE10/LK+oglS1fP9+tiG5UzMvWgQY9g==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
'@eslint/js@10.0.1':
@@ -530,79 +533,79 @@ packages:
resolution: {integrity: sha512-j37dj/h6p6pots1L7pLPPNuABEEu9WP4OYg9pW1g+nYG331j0+3canE9OagtYdytYvdSAMG+2fKU5cxep6LgjA==}
engines: {node: '>=22.12.0'}
- '@napi-rs/canvas-android-arm64@1.0.10':
- resolution: {integrity: sha512-0sDMvme+7fj6bHERUaBs5SvEyWaORc91VUkaB4RrtG7p6dBrBNGy1of8cyEKDxygd1FlHtW+Thunj8lSixzY7A==}
+ '@napi-rs/canvas-android-arm64@1.0.12':
+ resolution: {integrity: sha512-Z8hg9BD7ABaZNhdM8O96IrfBaYLM7KzO/YyKA8eU9BDdc5fZJRTWyOhSAyOv59/ari6bOYa+p96h7zN6wbJRDA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
- '@napi-rs/canvas-darwin-arm64@1.0.10':
- resolution: {integrity: sha512-pdHL1TEfFMrnchRYqmPWXKN7hR4XtOYmzJqnh4XhpfWD4/eeSokOGVzrdFP/iQ3sfr2rf0939gOGq9uCp8BGmg==}
+ '@napi-rs/canvas-darwin-arm64@1.0.12':
+ resolution: {integrity: sha512-dRtZCi/u1IOZyRjEDCA73PqgWakf5689K2jfv4uPRYGMJvEqtV+Eq7MIQkBJLmdfUPe7AktSiBZRml2pggnjRQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
- '@napi-rs/canvas-darwin-x64@1.0.10':
- resolution: {integrity: sha512-iuuk5plGxAcxurxv2ycF2yR20y5zvaBZ4heQdhPpg3EMFD21PhlY1V+YwSRmYRWzbvKL474UYdw3WIg2qfafyw==}
+ '@napi-rs/canvas-darwin-x64@1.0.12':
+ resolution: {integrity: sha512-YwvMgK5NBXwPY1AjZGXfPCG3hp/OFGM0Kw+JZHCOR/FYaeSOftaZRyxaDEGkJQSLEUZZIMNJXKCWgZYSzgWZEA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
- '@napi-rs/canvas-linux-arm-gnueabihf@1.0.10':
- resolution: {integrity: sha512-kH7GFR5Unpm77pKSEsmkqvzeQCRD7/MQ7upmeLLjg4XpQ62dX/R6THFsp6vkF1Pkco2aKUq9KhoLM4W5twdk7g==}
+ '@napi-rs/canvas-linux-arm-gnueabihf@1.0.12':
+ resolution: {integrity: sha512-LlOsZy8qh+qJq1euEvDZqa9muACS1fL1Ag38c7J+axAnA8NeXazMtqmUyKb4NVyft6DqbU/MesEj3C0bAFnv4Q==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
- '@napi-rs/canvas-linux-arm64-gnu@1.0.10':
- resolution: {integrity: sha512-0/Hj7IwM9pmzdwxiqkp4YLJ8dKqASBrWQ3Y7dWUSU/4dgvCL9O6YNvX7xVUJjonQvdD3IE9ESS/nXa5srQqoVw==}
+ '@napi-rs/canvas-linux-arm64-gnu@1.0.12':
+ resolution: {integrity: sha512-nvuqhi85GzaFl/6sid4qf7wwjHuGU6gfIuDyywUb/fB+kp6Fp3h8GyIAKss/QPil3zVO2r8g6O9ah+IOUoBrIw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
- '@napi-rs/canvas-linux-arm64-musl@1.0.10':
- resolution: {integrity: sha512-fmjKM0P2MLF9O6XzCQsRjhB+jlLzaf6tFQU36hMPMsYup+VThYvpjjhqNLOc/Ee3tHUE3r13H2BSJx/yXz4pFA==}
+ '@napi-rs/canvas-linux-arm64-musl@1.0.12':
+ resolution: {integrity: sha512-Bf6+9PoEr9K06P/oyMab7zZOpW90KimbIuNsy/vL0VhZZKsb0B0rCC78cp0jXhK6PIM7v8t6uX1sn7isOZ8KEw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [musl]
- '@napi-rs/canvas-linux-riscv64-gnu@1.0.10':
- resolution: {integrity: sha512-O/BUrGwrs3pVP9PpD/3++ZiOpjHMJlmiB7aBgswP8rbe4egPfiFkFxmcCczoLvZa72TaQX5lJ8Udedb6rc/Hvg==}
+ '@napi-rs/canvas-linux-riscv64-gnu@1.0.12':
+ resolution: {integrity: sha512-j7VcZduLSRx60nv/wbdyH+fyB5QriWGuO8GrvMDlFlt7GNUKAw5lHAHpcuuQW3QwLOe+3P+3qryzyT3sW5pCqQ==}
engines: {node: '>= 10'}
cpu: [riscv64]
os: [linux]
libc: [glibc]
- '@napi-rs/canvas-linux-x64-gnu@1.0.10':
- resolution: {integrity: sha512-48HkZPQeAN/R+9NPpY64tceoyCUW5xYYtHKZnC+BG11qiihXJCbH+xfbgGU+OdYp1Q4s84HDl9ILU0KBK6SBOQ==}
+ '@napi-rs/canvas-linux-x64-gnu@1.0.12':
+ resolution: {integrity: sha512-twDb8ZNgoCF8EG6ka+Lk4etU+ea9lv6NiV5ptIf+thz3LgWuHBbt85c+YgL1s05VsHe4WYWK0f+GhR45IuqARg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
- '@napi-rs/canvas-linux-x64-musl@1.0.10':
- resolution: {integrity: sha512-QYHVi7WZ9v+Wm9OQyHLMTnJ8QiBPVX94BgbCHFJC/4bCFMfvX7fRzo4FJPgkRP0ISz3ulYNow/FCbQOPjzhYjg==}
+ '@napi-rs/canvas-linux-x64-musl@1.0.12':
+ resolution: {integrity: sha512-mtZbBeNCdGA0MYQxivPmMOp5JMH80LaJLQeEMg7Nne517fvgGb+DWTJTmoE/qUW8YJ4nkiP2vQyr1XVGrH/Ktg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
- '@napi-rs/canvas-win32-arm64-msvc@1.0.10':
- resolution: {integrity: sha512-bWK+YajM/8sL0mMgLtug2s063FVBPzFV4QAn987CQomMKlmzrBY6Trz43KY+24hq7IOa3qNhUAgP/XMEF50ZEQ==}
+ '@napi-rs/canvas-win32-arm64-msvc@1.0.12':
+ resolution: {integrity: sha512-HgGRuBUkFL8U99ogwUnTz3SmwUu7WKjmj0eOVWRqVZTPPyfWGEIMr+ycb8oDoNZcKM9vSiF1rpy3r+UIqqaKqw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
- '@napi-rs/canvas-win32-x64-msvc@1.0.10':
- resolution: {integrity: sha512-9WtzW85PaIhtB27iXaR/9RrLCqHLbqD3Z8Q+kh17NjFxwQXFXU5vWbYH4vEH+drM307TTyK3hidZgpt09G5T3g==}
+ '@napi-rs/canvas-win32-x64-msvc@1.0.12':
+ resolution: {integrity: sha512-SEEfblIAnYeS4UkeFPp9jw6ZWuHxaTqumUuksMgnMXmPLW8Ofb21uu7cujhw+lk4E9V3aWgGAE9f8g+CdoAU0Q==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
- '@napi-rs/canvas@1.0.10':
- resolution: {integrity: sha512-V+qptzsGnPMFxEbhwc6eDNMe5eDrfiBIp8qGpuRjAVIX4Kn2zxvwQ1Xco6Fwe+3tYIe3iLW8LM9D+SvjyJhqbQ==}
+ '@napi-rs/canvas@1.0.12':
+ resolution: {integrity: sha512-PSwhRdvizfuiSDlhE4Ld2jl/JqkrbSFFyZO0ycamE6mBpf1H0uUF3Tm8yQaVDeAwCNNDzxjRfrN22KmWLTDwcw==}
engines: {node: '>= 10'}
'@pkgr/core@0.3.6':
@@ -729,8 +732,8 @@ packages:
'@types/mocha@10.0.10':
resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==}
- '@types/node@26.6.4':
- resolution: {integrity: sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg==}
+ '@types/node@26.6.5':
+ resolution: {integrity: sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg==}
'@types/toastify-js@1.12.4':
resolution: {integrity: sha512-zfZHU4tKffPCnZRe7pjv/eFKzTVHozKewFCKaCjZ4gFinKgJRz/t0bkZiMCXJxPhv/ZoeDGNOeRD09R0kQZ/nw==}
@@ -738,63 +741,63 @@ packages:
'@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
- '@typescript-eslint/eslint-plugin@8.71.1':
- resolution: {integrity: sha512-nNlBf6HuotqMgZvSkWD5hNdemiBIxQ2NmrlgfcUIeZRs+BbakjscCQRq/Hb74EFVnFiSWWdL12JruqwBy3ERDw==}
+ '@typescript-eslint/eslint-plugin@8.72.0':
+ resolution: {integrity: sha512-aysKgysPX9XOv/dk95UkvUDe++Q3n3dJPG9CEWT3RUccxfkPJ+Qn6nhKPDhZXU6Ofo9kgwGWwKyQCnZIXTgngg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- '@typescript-eslint/parser': ^8.71.1
+ '@typescript-eslint/parser': ^8.72.0
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/parser@8.71.1':
- resolution: {integrity: sha512-oM/5sAqz/l1v/zYK1qTOPrn2+JIrBGC1V6uNYucRO5gh6eT9vWJ/RhPbGE59byvZEUQ4VP6XkLBaS5Jti5jpvw==}
+ '@typescript-eslint/parser@8.72.0':
+ resolution: {integrity: sha512-0O+x3ynciD6zuXb1CKSu165haQV6ey53DKhHHUnujP7U+XlltJG+/keT0Xhd1HXCze+IHf7Tvt39ovyepX/LNw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/project-service@8.71.1':
- resolution: {integrity: sha512-Oijc9RUsohHjncg6iGi7gCtjnEjYt9FWv9u6ygK8uKcHK5eLjLAkqERQ9BaytbpceOFdk6kifPCS/zAZE+A99g==}
+ '@typescript-eslint/project-service@8.72.0':
+ resolution: {integrity: sha512-bzHngBbmIgzj6TsTjVUCntGx6FFLgE7mPjpO0PzHycOaZHXqS43XnrkFIHRS16LLr/xO6FHSXG3GJFo/F4bSBQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/scope-manager@8.71.1':
- resolution: {integrity: sha512-1YUDZXdTnXLlob03SFHBipdyMBsDVm7zoSz0ytTJ39dk5J1TgsTK03VlR/dLXG/RLLUrPcYbD4rp23BmHxHP0w==}
+ '@typescript-eslint/scope-manager@8.72.0':
+ resolution: {integrity: sha512-JewPyJOYcXBSAS7PIgufscHOwZqhTGyBRiQJhhuDln66tMchxvvt7nJ90JKKIoOelmdHA3kAQbm9cEUHc4JSEA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/tsconfig-utils@8.71.1':
- resolution: {integrity: sha512-oTvkml5SxXhgg+WWKhod5qWQICDXtOB+/Hi9VdPKFAANJ2b7YOgMffgvzXuqog7R6A/JtrzRf128dsxummyWhg==}
+ '@typescript-eslint/tsconfig-utils@8.72.0':
+ resolution: {integrity: sha512-0vbxCf7xLVPSjcOdm6eQbgViH8EDJ8xnl1laLT9yoqSz2YmcSfSg5Vdl0JUvZf6rcuJ0ZivJIuMD4edTg8obaA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/type-utils@8.71.1':
- resolution: {integrity: sha512-kH3t3jZCYv2PZSBEWPKTariLrCaoF1CLoPitYFBJOhAGEwWnSRAtGEifrfrpBa4K9JlwWNZotgA5w3/wLSk/FQ==}
+ '@typescript-eslint/type-utils@8.72.0':
+ resolution: {integrity: sha512-gEXxmmbSTSOVTC37IYYvremOM31v+UN2W772bJxmVYPhAOPSUxtacyb7wkm1EhkYKU6KzFJsdU44DCV4to0LWw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/types@8.71.1':
- resolution: {integrity: sha512-ybYnPTUwg3VQQOFEiBPLs8Jxl1ry2vXdn1kVKguomBbvxpetdu3MKTNDqFNUrlG8ylP9MpZosmXMFYlGQHk1oQ==}
+ '@typescript-eslint/types@8.72.0':
+ resolution: {integrity: sha512-Adfoy59kxNEjT5E5fnyrp7GpHYuWs8ONbdohu4ez6IU46qDq8LltG90tLmkucZB2uhYL8mqBjtXqGd8yhfjXqQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/typescript-estree@8.71.1':
- resolution: {integrity: sha512-pSKSEK1JJpHs6KiKVtABCE4iJK5d7FLFzDXQNBQ2oRY7LEd3UwOt3TZemTgu9syTIf56dJWlTn/pdmrO9oKUXQ==}
+ '@typescript-eslint/typescript-estree@8.72.0':
+ resolution: {integrity: sha512-gwLfFgWcw15tN2KXDCJvxEOQjPn+P3j/n3eSqkZ5sX4VpW9Cosodr+tWwBZRvJT3TcKx6HnPRGZMCNAiP1Xhug==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/utils@8.71.1':
- resolution: {integrity: sha512-CBjT6gfAz3DW2j9Q4moFnqt208Aq+506sVGXfpRcyAeqn0EpayL0mjdWoCO2t3RC7GewNrFxdeehuEaPg6DMzQ==}
+ '@typescript-eslint/utils@8.72.0':
+ resolution: {integrity: sha512-0up3oArJZBYnzOaEXKo8DmI43QvMZyPoFEt9LWzRJcyl/RcJjfKKPebknN1VbTgXBf3M/KggUh5uUMdURLnseg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/visitor-keys@8.71.1':
- resolution: {integrity: sha512-0GxiUGqMU0qXJt58cBsiG3DP8lUmBaFo2vChtUX+CT7PnR3v/VGLLrzVP4K9r1lNiZXjHYefMBbPunplr09v6w==}
+ '@typescript-eslint/visitor-keys@8.72.0':
+ resolution: {integrity: sha512-E2mroviasaWecDzTGiGMtuEguuLVOeRggnrIn93CJuxYJzQBHbyymlT08aVQydbBal5538o+JkBKXh2sRuCyIA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript/typescript-aix-ppc64@7.0.2':
@@ -937,8 +940,8 @@ packages:
ajv@6.15.0:
resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==}
- argparse@2.0.1:
- resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+ argparse@3.0.3:
+ resolution: {integrity: sha512-Sn98ftV3KfsO8xfLPHG6oT8y+b0yvbrLgZjgPDXsharNY15tTAjqIsIju4S32nl6/iEUNNE4v+yL2Zhm8ooP3w==}
array-buffer-byte-length@1.0.2:
resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
@@ -1027,14 +1030,14 @@ packages:
codemirror@6.0.2:
resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==}
+ commander@15.0.0:
+ resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==}
+ engines: {node: '>=22.12.0'}
+
commander@7.2.0:
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
engines: {node: '>= 10'}
- commander@8.3.0:
- resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
- engines: {node: '>= 12'}
-
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
@@ -1061,8 +1064,8 @@ packages:
peerDependencies:
cytoscape: ^3.2.0
- cytoscape@3.34.3:
- resolution: {integrity: sha512-yfYGhRcGAntq6YBD583j4n0Eg3jIxvWmZtz/5uz9UYkeIStSlMxuUja+ec5j3iBD8nv1rwaOAYMW09tBdkSeaQ==}
+ cytoscape@3.34.4:
+ resolution: {integrity: sha512-msY99tMY8iWTg+sIPGfSqDcPNOt4xcj+v/K/7qUFaCUxxlqnMkUYLBjRUzn9W5CU/R2hrmtJa5+UtovrEM2ctg==}
engines: {node: '>=0.10'}
d3-array@2.12.1:
@@ -1261,8 +1264,8 @@ packages:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'}
- dompurify@3.4.16:
- resolution: {integrity: sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ==}
+ dompurify@3.4.17:
+ resolution: {integrity: sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==}
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
@@ -1355,8 +1358,8 @@ packages:
'@typescript-eslint/parser':
optional: true
- eslint-plugin-prettier@5.5.6:
- resolution: {integrity: sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==}
+ eslint-plugin-prettier@5.5.7:
+ resolution: {integrity: sha512-WjHyn3GrFd+ZR0rzsLiM4zJZ35PZMOE0SUIxzGmnWNG0126yNfJKJzObmmkYDwHJfGlkz3PSlzyG9ez/LJntwg==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
'@types/eslint': '>=8.0.0'
@@ -1381,8 +1384,8 @@ packages:
resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
- eslint@10.12.0:
- resolution: {integrity: sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA==}
+ eslint@10.13.0:
+ resolution: {integrity: sha512-UohDjD4zSzEwhf58M3O7vc/ycc8O1I7dNlhtD0WxE8Px8EHNJKtUwzBqrzeGhisRuv2ag4sAYswszCJRgKXYUg==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
hasBin: true
peerDependencies:
@@ -1547,8 +1550,8 @@ packages:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
- ignore@7.0.12:
- resolution: {integrity: sha512-/8UvqAPU9DGTI9k4mxtf49U37Isfwr8Uts96+SBHIkFxPJnHS0Ew4f00sM4Scd8V8EjM0jUNtVDdv1kPdt35lg==}
+ ignore@7.0.13:
+ resolution: {integrity: sha512-2E8Q6rh+1RRSq/YdaGsNfXsEZy9FVqv6BSSrkmZJP7BnaoPnRfKL+YaofPvP+5rzdk144qSm6i4+hvPgUcbV4Q==}
engines: {node: '>= 4'}
import-meta-resolve@4.2.0:
@@ -1683,8 +1686,8 @@ packages:
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
- js-yaml@5.4.3:
- resolution: {integrity: sha512-yzzNtgczwgoVHNigCyKTXcQJU4Y8t/ikU4UxXW0BvrLfHTGWZhsNcOT2s/a7vn81/4KqVnNmHjaUEPlvWnNwVw==}
+ js-yaml@5.5.0:
+ resolution: {integrity: sha512-paNr4LFIFPjeeSmwNYdrb8FafLLG/Cr2Af3GjbsHJ5CxIjH4r0c2emlmRdAtpPAieP1gRVzk6buBbVP5gBMGAg==}
hasBin: true
json-schema-traverse@0.4.1:
@@ -1697,8 +1700,8 @@ packages:
resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
hasBin: true
- katex@0.16.47:
- resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==}
+ katex@0.18.10:
+ resolution: {integrity: sha512-/B6p9eY9DX7aHBfpkHdpirDTZ5QH9xTwL9y837PWuzuv41O5jFdqNQwVQEQsimY0/iVNkwBY4+4hMfhQ7d4Dbw==}
hasBin: true
keyv@5.6.0:
@@ -1740,11 +1743,11 @@ packages:
mathjax@4.1.3:
resolution: {integrity: sha512-BN/8Pkgn7G1pIDYJqd9md+JHsE/jydSYbyOZnSdSA0WziuVO8mRxdYiWFumkVVly/8U+hm9DpIIoWuvySverzw==}
- mdn-data@2.35.0:
- resolution: {integrity: sha512-k+1+dEIm2z/BEfdvYzT/fIyAGeDBo/1AJNJI4ilFXJRYoxP/3Ds+GP8shAOIq8jYXY9N5nGB3Ag104NsagmLAA==}
+ mdn-data@2.37.2:
+ resolution: {integrity: sha512-rz9YOBGItdSbe7HcSArUbhiAv4SB/4DKRYg96CSFS2CcIYMbUvBtZlzA1NCbk2AdgbmYwMqwNblLpR441lLeiQ==}
- mermaid@12.1.0:
- resolution: {integrity: sha512-wlVCp+8eTupfCeeFvoZNNiTuHrvag0P2jz/ILgb/f/6jkVokUefOcujefi8qUe/j2asHiSePncVsz/xzzA80LQ==}
+ mermaid@12.2.0:
+ resolution: {integrity: sha512-w7nr/SFrLO7BaVTs3dQTvYSV6nYSBj0Yy9DjBhsIJtAcHqRzXSq4Pg32ujHZ/AdWsAMNEvvhbm5dt9E2tOiHzw==}
engines: {node: '>=22.12.0'}
minimatch@10.2.6:
@@ -1776,8 +1779,8 @@ packages:
resolution: {integrity: sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==}
engines: {node: '>= 0.4'}
- npm-check-updates@23.1.0:
- resolution: {integrity: sha512-u6Odx/JDpDXNqXp5pPsVN0RhuWkNrBe4Sz26J7yWJcEadopKhQgcB+bFq2PK35iJY6vc6h1PbnXmnwfiOFQl/w==}
+ npm-check-updates@23.1.1:
+ resolution: {integrity: sha512-UzYDvvsQRkmXR59g9SqsQxygYp7AhFpfEcOOGOJaB/P7pF/XlaqIYLK3UjUGfMHH1+aS2nZvJRaIsk2nk0z5ag==}
engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0, npm: '>=10.0.0'}
hasBin: true
@@ -1875,8 +1878,8 @@ packages:
resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==}
engines: {node: '>=6.0.0'}
- prettier@3.9.9:
- resolution: {integrity: sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==}
+ prettier@3.9.10:
+ resolution: {integrity: sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==}
engines: {node: '>=14'}
hasBin: true
@@ -2020,8 +2023,8 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
- synckit@0.11.13:
- resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==}
+ synckit@0.12.3:
+ resolution: {integrity: sha512-U+/AXT6LWsxnj2vz9HeUuPWBNgdWxjyIEjEPdbJARC9txmgPzo2VEdqnFo4hHaNyMPtycyVJrSaQYeQv/PoyyA==}
engines: {node: ^14.18.0 || >=16.0.0}
tinyexec@1.3.1:
@@ -2032,8 +2035,8 @@ packages:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'}
- tinymce@8.9.3:
- resolution: {integrity: sha512-T6Z541dm8R34/rgotB40/j2lwrE3eKARbaoet37/I4d3jq+S7Ul3Q7bNZUQY2WtXOvD9P5aTVbgcYI1nKQoGqg==}
+ tinymce@8.10.0:
+ resolution: {integrity: sha512-/e+/B+VdbzO02hLzfr3EaoYKYdr3phC/yitG7I9vpi2NqYvtb04UxO/Ay4y9VPqDaQHyigdFnHv2zuTgF7mnZQ==}
toastify-js@1.12.0:
resolution: {integrity: sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==}
@@ -2071,8 +2074,8 @@ packages:
resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==}
engines: {node: '>= 0.4'}
- typescript-eslint@8.71.1:
- resolution: {integrity: sha512-oiNVPC3/NbV1FpaHU07l8O6ynqG9v4PAiW2WvTrAmZdR0f2HI/gE668ElMLCK7Xbsu5k2fK5C4fXNiq05j7/nQ==}
+ typescript-eslint@8.72.0:
+ resolution: {integrity: sha512-r26Fkc8As+mZvqp7SLsMmNFyYUup/6CjK7/eHBHmhOw6GH6n6YFxHVFrG0jN1c5CMpDzelLPxvgeCOzx7BjN+A==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
@@ -2098,8 +2101,8 @@ packages:
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
- uuid@14.0.2:
- resolution: {integrity: sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ==}
+ uuid@14.0.3:
+ resolution: {integrity: sha512-Sc5u1VVln93y1Xnz4R+CunOZc7R9LUcTmhj7Rc7Eg3HVcf7rVn2FOoa812fe0W8p3jD7xeoGSYbfqCGGH2B6xw==}
hasBin: true
w3c-keyname@2.2.8:
@@ -2173,18 +2176,18 @@ snapshots:
'@chevrotain/utils@13.2.0': {}
- '@codemirror/autocomplete@6.20.3':
+ '@codemirror/autocomplete@6.20.4':
dependencies:
'@codemirror/language': 6.13.1
'@codemirror/state': 6.7.6
- '@codemirror/view': 6.43.14
+ '@codemirror/view': 6.43.15
'@lezer/common': 1.5.3
'@codemirror/commands@6.11.1':
dependencies:
'@codemirror/language': 6.13.1
'@codemirror/state': 6.7.6
- '@codemirror/view': 6.43.14
+ '@codemirror/view': 6.43.15
'@lezer/common': 1.5.3
'@codemirror/lang-cpp@6.0.3':
@@ -2194,7 +2197,7 @@ snapshots:
'@codemirror/lang-css@6.3.1':
dependencies:
- '@codemirror/autocomplete': 6.20.3
+ '@codemirror/autocomplete': 6.20.4
'@codemirror/language': 6.13.1
'@codemirror/state': 6.7.6
'@lezer/common': 1.5.3
@@ -2202,7 +2205,7 @@ snapshots:
'@codemirror/lang-go@6.0.1':
dependencies:
- '@codemirror/autocomplete': 6.20.3
+ '@codemirror/autocomplete': 6.20.4
'@codemirror/language': 6.13.1
'@codemirror/state': 6.7.6
'@lezer/common': 1.5.3
@@ -2210,12 +2213,12 @@ snapshots:
'@codemirror/lang-html@6.4.12':
dependencies:
- '@codemirror/autocomplete': 6.20.3
+ '@codemirror/autocomplete': 6.20.4
'@codemirror/lang-css': 6.3.1
'@codemirror/lang-javascript': 6.2.5
'@codemirror/language': 6.13.1
'@codemirror/state': 6.7.6
- '@codemirror/view': 6.43.14
+ '@codemirror/view': 6.43.15
'@lezer/common': 1.5.3
'@lezer/css': 1.3.9
'@lezer/html': 1.3.13
@@ -2227,11 +2230,11 @@ snapshots:
'@codemirror/lang-javascript@6.2.5':
dependencies:
- '@codemirror/autocomplete': 6.20.3
+ '@codemirror/autocomplete': 6.20.4
'@codemirror/language': 6.13.1
'@codemirror/lint': 6.9.7
'@codemirror/state': 6.7.6
- '@codemirror/view': 6.43.14
+ '@codemirror/view': 6.43.15
'@lezer/common': 1.5.3
'@lezer/javascript': 1.5.6
@@ -2242,7 +2245,7 @@ snapshots:
'@codemirror/lang-python@6.2.1':
dependencies:
- '@codemirror/autocomplete': 6.20.3
+ '@codemirror/autocomplete': 6.20.4
'@codemirror/language': 6.13.1
'@codemirror/state': 6.7.6
'@lezer/common': 1.5.3
@@ -2255,7 +2258,7 @@ snapshots:
'@codemirror/lang-sql@6.10.0':
dependencies:
- '@codemirror/autocomplete': 6.20.3
+ '@codemirror/autocomplete': 6.20.4
'@codemirror/language': 6.13.1
'@codemirror/state': 6.7.6
'@lezer/common': 1.5.3
@@ -2264,7 +2267,7 @@ snapshots:
'@codemirror/lang-yaml@6.1.3':
dependencies:
- '@codemirror/autocomplete': 6.20.3
+ '@codemirror/autocomplete': 6.20.4
'@codemirror/language': 6.13.1
'@codemirror/state': 6.7.6
'@lezer/common': 1.5.3
@@ -2276,7 +2279,7 @@ snapshots:
dependencies:
'@codemirror/state': 6.7.6
'@codemirror/streamparser': 6.0.0
- '@codemirror/view': 6.43.14
+ '@codemirror/view': 6.43.15
'@lezer/common': 1.5.3
'@lezer/highlight': 1.2.5
'@lezer/lr': 1.4.11
@@ -2289,13 +2292,13 @@ snapshots:
'@codemirror/lint@6.9.7':
dependencies:
'@codemirror/state': 6.7.6
- '@codemirror/view': 6.43.14
+ '@codemirror/view': 6.43.15
crelt: 1.0.7
'@codemirror/search@6.7.2':
dependencies:
'@codemirror/state': 6.7.6
- '@codemirror/view': 6.43.14
+ '@codemirror/view': 6.43.15
crelt: 1.0.7
'@codemirror/state@6.7.6':
@@ -2304,13 +2307,15 @@ snapshots:
'@codemirror/streamparser@6.0.0': {}
- '@codemirror/view@6.43.14':
+ '@codemirror/view@6.43.15':
dependencies:
'@codemirror/state': 6.7.6
crelt: 1.0.7
style-mod: 4.1.4
w3c-keyname: 2.2.8
+ '@dual-bundle/import-meta-resolve@4.2.1': {}
+
'@esbuild/aix-ppc64@0.28.2':
optional: true
@@ -2389,9 +2394,9 @@ snapshots:
'@esbuild/win32-x64@0.28.2':
optional: true
- '@eslint-community/eslint-utils@4.10.1(eslint@10.12.0(supports-color@8.1.1))':
+ '@eslint-community/eslint-utils@4.10.1(eslint@10.13.0(supports-color@8.1.1))':
dependencies:
- eslint: 10.12.0(supports-color@8.1.1)
+ eslint: 10.13.0(supports-color@8.1.1)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {}
@@ -2412,20 +2417,20 @@ snapshots:
dependencies:
'@types/json-schema': 7.0.15
- '@eslint/css-tree@4.1.1':
+ '@eslint/css-tree@4.1.2':
dependencies:
- mdn-data: 2.35.0
+ mdn-data: 2.37.2
source-map-js: 1.2.2
- '@eslint/css@2.0.0':
+ '@eslint/css@2.0.1':
dependencies:
'@eslint/core': 1.2.1
- '@eslint/css-tree': 4.1.1
+ '@eslint/css-tree': 4.1.2
'@eslint/plugin-kit': 0.7.3
- '@eslint/js@10.0.1(eslint@10.12.0(supports-color@8.1.1))':
+ '@eslint/js@10.0.1(eslint@10.13.0(supports-color@8.1.1))':
optionalDependencies:
- eslint: 10.12.0(supports-color@8.1.1)
+ eslint: 10.13.0(supports-color@8.1.1)
'@eslint/object-schema@3.0.5': {}
@@ -2546,52 +2551,52 @@ snapshots:
dependencies:
'@chevrotain/types': 13.2.0
- '@napi-rs/canvas-android-arm64@1.0.10':
+ '@napi-rs/canvas-android-arm64@1.0.12':
optional: true
- '@napi-rs/canvas-darwin-arm64@1.0.10':
+ '@napi-rs/canvas-darwin-arm64@1.0.12':
optional: true
- '@napi-rs/canvas-darwin-x64@1.0.10':
+ '@napi-rs/canvas-darwin-x64@1.0.12':
optional: true
- '@napi-rs/canvas-linux-arm-gnueabihf@1.0.10':
+ '@napi-rs/canvas-linux-arm-gnueabihf@1.0.12':
optional: true
- '@napi-rs/canvas-linux-arm64-gnu@1.0.10':
+ '@napi-rs/canvas-linux-arm64-gnu@1.0.12':
optional: true
- '@napi-rs/canvas-linux-arm64-musl@1.0.10':
+ '@napi-rs/canvas-linux-arm64-musl@1.0.12':
optional: true
- '@napi-rs/canvas-linux-riscv64-gnu@1.0.10':
+ '@napi-rs/canvas-linux-riscv64-gnu@1.0.12':
optional: true
- '@napi-rs/canvas-linux-x64-gnu@1.0.10':
+ '@napi-rs/canvas-linux-x64-gnu@1.0.12':
optional: true
- '@napi-rs/canvas-linux-x64-musl@1.0.10':
+ '@napi-rs/canvas-linux-x64-musl@1.0.12':
optional: true
- '@napi-rs/canvas-win32-arm64-msvc@1.0.10':
+ '@napi-rs/canvas-win32-arm64-msvc@1.0.12':
optional: true
- '@napi-rs/canvas-win32-x64-msvc@1.0.10':
+ '@napi-rs/canvas-win32-x64-msvc@1.0.12':
optional: true
- '@napi-rs/canvas@1.0.10':
+ '@napi-rs/canvas@1.0.12':
optionalDependencies:
- '@napi-rs/canvas-android-arm64': 1.0.10
- '@napi-rs/canvas-darwin-arm64': 1.0.10
- '@napi-rs/canvas-darwin-x64': 1.0.10
- '@napi-rs/canvas-linux-arm-gnueabihf': 1.0.10
- '@napi-rs/canvas-linux-arm64-gnu': 1.0.10
- '@napi-rs/canvas-linux-arm64-musl': 1.0.10
- '@napi-rs/canvas-linux-riscv64-gnu': 1.0.10
- '@napi-rs/canvas-linux-x64-gnu': 1.0.10
- '@napi-rs/canvas-linux-x64-musl': 1.0.10
- '@napi-rs/canvas-win32-arm64-msvc': 1.0.10
- '@napi-rs/canvas-win32-x64-msvc': 1.0.10
+ '@napi-rs/canvas-android-arm64': 1.0.12
+ '@napi-rs/canvas-darwin-arm64': 1.0.12
+ '@napi-rs/canvas-darwin-x64': 1.0.12
+ '@napi-rs/canvas-linux-arm-gnueabihf': 1.0.12
+ '@napi-rs/canvas-linux-arm64-gnu': 1.0.12
+ '@napi-rs/canvas-linux-arm64-musl': 1.0.12
+ '@napi-rs/canvas-linux-riscv64-gnu': 1.0.12
+ '@napi-rs/canvas-linux-x64-gnu': 1.0.12
+ '@napi-rs/canvas-linux-x64-musl': 1.0.12
+ '@napi-rs/canvas-win32-arm64-msvc': 1.0.12
+ '@napi-rs/canvas-win32-x64-msvc': 1.0.12
optional: true
'@pkgr/core@0.3.6': {}
@@ -2734,7 +2739,7 @@ snapshots:
'@types/mocha@10.0.10': {}
- '@types/node@26.6.4':
+ '@types/node@26.6.5':
dependencies:
undici-types: 8.9.0
@@ -2743,72 +2748,72 @@ snapshots:
'@types/trusted-types@2.0.7':
optional: true
- '@typescript-eslint/eslint-plugin@8.71.1(@typescript-eslint/parser@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1))(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1)':
+ '@typescript-eslint/eslint-plugin@8.72.0(@typescript-eslint/parser@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1))(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
'@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1)
- '@typescript-eslint/scope-manager': 8.71.1
- '@typescript-eslint/type-utils': 8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1)
- '@typescript-eslint/utils': 8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1)
- '@typescript-eslint/visitor-keys': 8.71.1
- eslint: 10.12.0(supports-color@8.1.1)
- ignore: 7.0.12
+ '@typescript-eslint/parser': 8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@typescript-eslint/scope-manager': 8.72.0
+ '@typescript-eslint/type-utils': 8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@typescript-eslint/utils': 8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@typescript-eslint/visitor-keys': 8.72.0
+ eslint: 10.13.0(supports-color@8.1.1)
+ ignore: 7.0.13
natural-compare: 1.4.0
ts-api-utils: 2.5.0(@typescript/typescript6@6.0.2)
typescript: '@typescript/typescript6@6.0.2'
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1)':
+ '@typescript-eslint/parser@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@typescript-eslint/scope-manager': 8.71.1
- '@typescript-eslint/types': 8.71.1
- '@typescript-eslint/typescript-estree': 8.71.1(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- '@typescript-eslint/visitor-keys': 8.71.1
+ '@typescript-eslint/scope-manager': 8.72.0
+ '@typescript-eslint/types': 8.72.0
+ '@typescript-eslint/typescript-estree': 8.72.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@typescript-eslint/visitor-keys': 8.72.0
debug: 4.4.3(supports-color@8.1.1)
- eslint: 10.12.0(supports-color@8.1.1)
+ eslint: 10.13.0(supports-color@8.1.1)
typescript: '@typescript/typescript6@6.0.2'
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/project-service@8.71.1(@typescript/typescript6@6.0.2)(supports-color@8.1.1)':
+ '@typescript-eslint/project-service@8.72.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1)':
dependencies:
- '@typescript-eslint/tsconfig-utils': 8.71.1(@typescript/typescript6@6.0.2)
- '@typescript-eslint/types': 8.71.1
+ '@typescript-eslint/tsconfig-utils': 8.72.0(@typescript/typescript6@6.0.2)
+ '@typescript-eslint/types': 8.72.0
debug: 4.4.3(supports-color@8.1.1)
typescript: '@typescript/typescript6@6.0.2'
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/scope-manager@8.71.1':
+ '@typescript-eslint/scope-manager@8.72.0':
dependencies:
- '@typescript-eslint/types': 8.71.1
- '@typescript-eslint/visitor-keys': 8.71.1
+ '@typescript-eslint/types': 8.72.0
+ '@typescript-eslint/visitor-keys': 8.72.0
- '@typescript-eslint/tsconfig-utils@8.71.1(@typescript/typescript6@6.0.2)':
+ '@typescript-eslint/tsconfig-utils@8.72.0(@typescript/typescript6@6.0.2)':
dependencies:
typescript: '@typescript/typescript6@6.0.2'
- '@typescript-eslint/type-utils@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1)':
+ '@typescript-eslint/type-utils@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@typescript-eslint/types': 8.71.1
- '@typescript-eslint/typescript-estree': 8.71.1(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- '@typescript-eslint/utils': 8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@typescript-eslint/types': 8.72.0
+ '@typescript-eslint/typescript-estree': 8.72.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@typescript-eslint/utils': 8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1)
debug: 4.4.3(supports-color@8.1.1)
- eslint: 10.12.0(supports-color@8.1.1)
+ eslint: 10.13.0(supports-color@8.1.1)
ts-api-utils: 2.5.0(@typescript/typescript6@6.0.2)
typescript: '@typescript/typescript6@6.0.2'
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/types@8.71.1': {}
+ '@typescript-eslint/types@8.72.0': {}
- '@typescript-eslint/typescript-estree@8.71.1(@typescript/typescript6@6.0.2)(supports-color@8.1.1)':
+ '@typescript-eslint/typescript-estree@8.72.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1)':
dependencies:
- '@typescript-eslint/project-service': 8.71.1(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- '@typescript-eslint/tsconfig-utils': 8.71.1(@typescript/typescript6@6.0.2)
- '@typescript-eslint/types': 8.71.1
- '@typescript-eslint/visitor-keys': 8.71.1
+ '@typescript-eslint/project-service': 8.72.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@typescript-eslint/tsconfig-utils': 8.72.0(@typescript/typescript6@6.0.2)
+ '@typescript-eslint/types': 8.72.0
+ '@typescript-eslint/visitor-keys': 8.72.0
debug: 4.4.3(supports-color@8.1.1)
minimatch: 10.2.6
semver: 7.8.5
@@ -2818,20 +2823,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1)':
+ '@typescript-eslint/utils@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(supports-color@8.1.1))
- '@typescript-eslint/scope-manager': 8.71.1
- '@typescript-eslint/types': 8.71.1
- '@typescript-eslint/typescript-estree': 8.71.1(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- eslint: 10.12.0(supports-color@8.1.1)
+ '@eslint-community/eslint-utils': 4.10.1(eslint@10.13.0(supports-color@8.1.1))
+ '@typescript-eslint/scope-manager': 8.72.0
+ '@typescript-eslint/types': 8.72.0
+ '@typescript-eslint/typescript-estree': 8.72.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ eslint: 10.13.0(supports-color@8.1.1)
typescript: '@typescript/typescript6@6.0.2'
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/visitor-keys@8.71.1':
+ '@typescript-eslint/visitor-keys@8.72.0':
dependencies:
- '@typescript-eslint/types': 8.71.1
+ '@typescript-eslint/types': 8.72.0
eslint-visitor-keys: 5.0.1
'@typescript/typescript-aix-ppc64@7.0.2':
@@ -2916,7 +2921,7 @@ snapshots:
json-schema-traverse: 0.4.1
uri-js: 4.4.1
- argparse@2.0.1: {}
+ argparse@3.0.3: {}
array-buffer-byte-length@1.0.2:
dependencies:
@@ -3034,17 +3039,17 @@ snapshots:
codemirror@6.0.2:
dependencies:
- '@codemirror/autocomplete': 6.20.3
+ '@codemirror/autocomplete': 6.20.4
'@codemirror/commands': 6.11.1
'@codemirror/language': 6.13.1
'@codemirror/lint': 6.9.7
'@codemirror/search': 6.7.2
'@codemirror/state': 6.7.6
- '@codemirror/view': 6.43.14
+ '@codemirror/view': 6.43.15
- commander@7.2.0: {}
+ commander@15.0.0: {}
- commander@8.3.0: {}
+ commander@7.2.0: {}
concat-map@0.0.1: {}
@@ -3064,17 +3069,17 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
- cytoscape-cose-bilkent@4.1.0(cytoscape@3.34.3):
+ cytoscape-cose-bilkent@4.1.0(cytoscape@3.34.4):
dependencies:
cose-base: 1.0.3
- cytoscape: 3.34.3
+ cytoscape: 3.34.4
- cytoscape-fcose@2.2.0(cytoscape@3.34.3):
+ cytoscape-fcose@2.2.0(cytoscape@3.34.4):
dependencies:
cose-base: 2.2.0
- cytoscape: 3.34.3
+ cytoscape: 3.34.4
- cytoscape@3.34.3: {}
+ cytoscape@3.34.4: {}
d3-array@2.12.1:
dependencies:
@@ -3304,7 +3309,7 @@ snapshots:
dependencies:
esutils: 2.0.3
- dompurify@3.4.16:
+ dompurify@3.4.17:
optionalDependencies:
'@types/trusted-types': 2.0.7
@@ -3441,9 +3446,9 @@ snapshots:
escape-string-regexp@4.0.0: {}
- eslint-config-prettier@10.1.8(eslint@10.12.0(supports-color@8.1.1)):
+ eslint-config-prettier@10.1.8(eslint@10.13.0(supports-color@8.1.1)):
dependencies:
- eslint: 10.12.0(supports-color@8.1.1)
+ eslint: 10.13.0(supports-color@8.1.1)
eslint-import-resolver-node@0.3.10(supports-color@8.1.1):
dependencies:
@@ -3453,17 +3458,17 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.14.0(@typescript-eslint/parser@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1))(eslint-import-resolver-node@0.3.10(supports-color@8.1.1))(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1):
+ eslint-module-utils@2.14.0(@typescript-eslint/parser@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1))(eslint-import-resolver-node@0.3.10(supports-color@8.1.1))(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1):
dependencies:
debug: 3.2.7(supports-color@8.1.1)
optionalDependencies:
- '@typescript-eslint/parser': 8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1)
- eslint: 10.12.0(supports-color@8.1.1)
+ '@typescript-eslint/parser': 8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1)
+ eslint: 10.13.0(supports-color@8.1.1)
eslint-import-resolver-node: 0.3.10(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1))(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1):
+ eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1))(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.2.0
@@ -3472,9 +3477,9 @@ snapshots:
array.prototype.flatmap: 1.3.3
debug: 3.2.7(supports-color@8.1.1)
doctrine: 2.1.0
- eslint: 10.12.0(supports-color@8.1.1)
+ eslint: 10.13.0(supports-color@8.1.1)
eslint-import-resolver-node: 0.3.10(supports-color@8.1.1)
- eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1))(eslint-import-resolver-node@0.3.10(supports-color@8.1.1))(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1)
+ eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1))(eslint-import-resolver-node@0.3.10(supports-color@8.1.1))(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1)
hasown: 2.0.4
is-core-module: 2.17.0
is-glob: 4.0.3
@@ -3486,20 +3491,20 @@ snapshots:
string.prototype.trimend: 1.0.10
tsconfig-paths: 3.15.0
optionalDependencies:
- '@typescript-eslint/parser': 8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@typescript-eslint/parser': 8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1)
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@10.12.0(supports-color@8.1.1)))(eslint@10.12.0(supports-color@8.1.1))(prettier@3.9.9):
+ eslint-plugin-prettier@5.5.7(eslint-config-prettier@10.1.8(eslint@10.13.0(supports-color@8.1.1)))(eslint@10.13.0(supports-color@8.1.1))(prettier@3.9.10):
dependencies:
- eslint: 10.12.0(supports-color@8.1.1)
- prettier: 3.9.9
+ eslint: 10.13.0(supports-color@8.1.1)
+ prettier: 3.9.10
prettier-linter-helpers: 1.0.1
- synckit: 0.11.13
+ synckit: 0.12.3
optionalDependencies:
- eslint-config-prettier: 10.1.8(eslint@10.12.0(supports-color@8.1.1))
+ eslint-config-prettier: 10.1.8(eslint@10.13.0(supports-color@8.1.1))
eslint-scope@9.1.2:
dependencies:
@@ -3512,9 +3517,9 @@ snapshots:
eslint-visitor-keys@5.0.1: {}
- eslint@10.12.0(supports-color@8.1.1):
+ eslint@10.13.0(supports-color@8.1.1):
dependencies:
- '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(supports-color@8.1.1))
+ '@eslint-community/eslint-utils': 4.10.1(eslint@10.13.0(supports-color@8.1.1))
'@eslint-community/regexpp': 4.12.2
'@eslint/config-array': 0.23.5(supports-color@8.1.1)
'@eslint/config-helpers': 0.7.0
@@ -3699,7 +3704,7 @@ snapshots:
ignore@5.3.2: {}
- ignore@7.0.12: {}
+ ignore@7.0.13: {}
import-meta-resolve@4.2.0: {}
@@ -3833,9 +3838,9 @@ snapshots:
isexe@2.0.0: {}
- js-yaml@5.4.3:
+ js-yaml@5.5.0:
dependencies:
- argparse: 2.0.1
+ argparse: 3.0.3
json-schema-traverse@0.4.1: {}
@@ -3845,9 +3850,9 @@ snapshots:
dependencies:
minimist: 1.2.8
- katex@0.16.47:
+ katex@0.18.10:
dependencies:
- commander: 8.3.0
+ commander: 15.0.0
keyv@5.6.0:
dependencies:
@@ -3880,9 +3885,9 @@ snapshots:
dependencies:
'@mathjax/mathjax-newcm-font': 4.1.3
- mdn-data@2.35.0: {}
+ mdn-data@2.37.2: {}
- mermaid@12.1.0:
+ mermaid@12.2.0:
dependencies:
'@braintree/sanitize-url': 7.1.2
'@iconify/utils': 3.1.7
@@ -3890,23 +3895,23 @@ snapshots:
'@types/d3': 7.4.3
'@upsetjs/venn.js': 2.0.0
chevrotain: 13.2.0
- cytoscape: 3.34.3
- cytoscape-cose-bilkent: 4.1.0(cytoscape@3.34.3)
- cytoscape-fcose: 2.2.0(cytoscape@3.34.3)
+ cytoscape: 3.34.4
+ cytoscape-cose-bilkent: 4.1.0(cytoscape@3.34.4)
+ cytoscape-fcose: 2.2.0(cytoscape@3.34.4)
d3: 7.9.0
d3-sankey: 0.12.3
dagre-d3-es: 7.0.14
dayjs: 1.11.23
- dompurify: 3.4.16
+ dompurify: 3.4.17
elkjs: 0.9.3
es-toolkit: 1.52.0
- katex: 0.16.47
+ katex: 0.18.10
khroma: 2.1.0
marked: 16.4.2
roughjs: 4.6.6
stylis: 4.4.0
ts-dedent: 2.3.0
- uuid: 14.0.2
+ uuid: 14.0.3
minimatch@10.2.6:
dependencies:
@@ -3930,7 +3935,7 @@ snapshots:
glob: 13.0.6
is-path-inside: 4.0.0
is-unicode-supported: 0.1.0
- js-yaml: 5.4.3
+ js-yaml: 5.5.0
minimatch: 10.2.6
ms: 2.1.3
picocolors: 1.1.1
@@ -3950,7 +3955,7 @@ snapshots:
object.entries: 1.1.9
semver: 6.3.1
- npm-check-updates@23.1.0: {}
+ npm-check-updates@23.1.1: {}
object-inspect@1.13.4: {}
@@ -4033,7 +4038,7 @@ snapshots:
pdfjs-dist@6.4.299:
optionalDependencies:
- '@napi-rs/canvas': 1.0.10
+ '@napi-rs/canvas': 1.0.12
picocolors@1.1.1: {}
@@ -4054,7 +4059,7 @@ snapshots:
dependencies:
fast-diff: 1.3.0
- prettier@3.9.9: {}
+ prettier@3.9.10: {}
punycode@2.3.1: {}
@@ -4232,8 +4237,9 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
- synckit@0.11.13:
+ synckit@0.12.3:
dependencies:
+ '@dual-bundle/import-meta-resolve': 4.2.1
'@pkgr/core': 0.3.6
tinyexec@1.3.1: {}
@@ -4243,7 +4249,7 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.7)
picomatch: 4.0.7
- tinymce@8.9.3: {}
+ tinymce@8.10.0: {}
toastify-js@1.12.0: {}
@@ -4296,13 +4302,13 @@ snapshots:
possible-typed-array-names: 1.1.0
reflect.getprototypeof: 1.0.10
- typescript-eslint@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1):
+ typescript-eslint@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1):
dependencies:
- '@typescript-eslint/eslint-plugin': 8.71.1(@typescript-eslint/parser@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1))(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1)
- '@typescript-eslint/parser': 8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1)
- '@typescript-eslint/typescript-estree': 8.71.1(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- '@typescript-eslint/utils': 8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1)
- eslint: 10.12.0(supports-color@8.1.1)
+ '@typescript-eslint/eslint-plugin': 8.72.0(@typescript-eslint/parser@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1))(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@typescript-eslint/parser': 8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1)
+ '@typescript-eslint/typescript-estree': 8.72.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@typescript-eslint/utils': 8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0(supports-color@8.1.1))(supports-color@8.1.1)
+ eslint: 10.13.0(supports-color@8.1.1)
typescript: '@typescript/typescript6@6.0.2'
transitivePeerDependencies:
- supports-color
@@ -4345,7 +4351,7 @@ snapshots:
dependencies:
punycode: 2.3.1
- uuid@14.0.2: {}
+ uuid@14.0.3: {}
w3c-keyname@2.2.8: {}
diff --git a/client/src/CodeChatEditor-test.mts b/client/src/CodeChatEditor-test.mts
index 15011c55..05312fc1 100644
--- a/client/src/CodeChatEditor-test.mts
+++ b/client/src/CodeChatEditor-test.mts
@@ -17,10 +17,10 @@
// `CodeChatEditor-test.mts` -- Tests for the CodeChat Editor client
// =================================================================
//
-// To run tests, change to the `server/` directory then run `./bt client-build`
-// to build any changes to the Client, followed by `cargo test --test overall
-// test_client`. Or, add a `?test` parameter to any web page served by the
-// CodeChat Editor Server.
+// To run tests, run [`./bt client-build`](#cc-1s8bUScadp) from the root of the
+// project to build any changes to the Client, then run `cargo test --test
+// overall test_client` from the `server/` directory. Or, add a `?test`
+// parameter to any web page served by the CodeChat Editor Server.
//
// Imports
// -------
@@ -227,9 +227,9 @@ window.CodeChatEditor_test = () => {
.querySelector(".cc-fragment-doc-contents")!
.getBoundingClientRect().left;
// ...and the code on the next line must begin in that same
- // column: each line number fills a gutter of the same width, and
- // the four spaces which follow the code's match the four the doc
- // block is indented by.
+ // column: each line number fills a gutter of the same width,
+ // and the four spaces which follow the code's match the four
+ // the doc block is indented by.
const codeText = Array.from(code.childNodes).find(
(node) => node.nodeType === Node.TEXT_NODE,
) as Text;
diff --git a/client/src/CodeChatEditor.mts b/client/src/CodeChatEditor.mts
index 055f63a3..6eb80839 100644
--- a/client/src/CodeChatEditor.mts
+++ b/client/src/CodeChatEditor.mts
@@ -646,37 +646,78 @@ const onNavigate = (navigateEvent: NavigateEvent) => {
// specifically those that TinyMCE generates (since they're always set to open
// in a new tab).
const onClick = (event: MouseEvent) => {
- // TinyMCE by default tries to open all links in a new tab. Look for and fix
- // these.
+ // TinyMCE's link plugin follows a link by clicking a temporary link to the
+ // same URL, appended to the page outside any doc block, which opens in a
+ // new tab. Look for and fix these. Leave alone a link in a doc block, where
+ // a plain click places the cursor whatever the link's target; `Link
+ // navigation` in [implementation.md](../../docs/implementation.md) lists
+ // the gestures which follow such a link.
if (
+ !event.defaultPrevented &&
event.target instanceof HTMLAnchorElement &&
- event.target.target === "_blank"
+ event.target.target === "_blank" &&
+ event.target.closest(".CodeChat-doc-contents") === null &&
+ !isLeftToBrowser(event.target)
) {
- // Get the URL from the link.
- const url = event.target.href;
-
- // If it's to a CodeChat Editor file, then load it as such.
- if (event.target.origin === window.location.origin) {
- // Ignore the "new tab" target, which doesn't make sense when there
- // is a 1:1 relationship between the active IDE file and the file
- // being edited in the CodeChat Editor. If two tabs are open, which
- // is the current file for the IDE?
- event.preventDefault();
- saveThenNavigate(new URL(url));
- } else {
- // This is navigation to some external link. Let that proceed
- // without interruption in a pure browser environment. However,
- // VSCode will block navigation, since it's cross-origin (the root
- // iframe has no URL, in contrast with the localhost URL of the
- // CodeChat Editor Server). In this case, ask the Server to open the
- // requested link.
- if (window.location.pathname.startsWith("/vsc")) {
- event.preventDefault();
- parent.window.CodeChatEditorFramework.webSocketComm.sendMessage(
- { OpenUrl: url },
- );
- }
- }
+ event.preventDefault();
+ followLink(event.target);
+ }
+};
+
+// VSCode shows the Client through the Server's `/vsc` endpoints; see
+// [vscode.rs](../../server/src/ide/vscode.rs).
+const isVsCode = () => window.location.pathname.startsWith("/vsc");
+
+// Return true if the browser, rather than `followLink`, should follow a click
+// on this link: an external link outside VSCode. The browser then opens the
+// link as the click's modifier keys ask, such as in a background tab on a
+// Ctrl+click.
+export const isLeftToBrowser = (
+ // The clicked link.
+ link: HTMLAnchorElement,
+ // True to leave the click to the browser.
+): boolean => link.origin !== window.location.origin && !isVsCode();
+
+// Follow a link. Every way of following a link in the Client ends here, except
+// a click which `isLeftToBrowser` leaves to the browser, so that links act
+// alike wherever they are; see `Link navigation` in
+// [implementation.md](../../docs/implementation.md).
+export const followLink = (
+ // The link to follow.
+ link: HTMLAnchorElement,
+) => {
+ // TinyMCE keeps the `href` as written in `data-mce-href`.
+ const href =
+ link.getAttribute("data-mce-href") ?? link.getAttribute("href") ?? "";
+ if (href.startsWith("#")) {
+ // TODO: CodeMirror renders only the part of a file near its viewport,
+ // so this misses a target elsewhere in the file. Remove this once the
+ // `_same_file` tests in `server/tests/overall/overall_5.rs` pass
+ // without `ignore`.
+ const id = CSS.escape(href.slice(1));
+ document.querySelector(`[id="${id}"],[name="${id}"]`)?.scrollIntoView();
+ } else if (link.origin === window.location.origin) {
+ // This is a link to a CodeChat Editor file, so load it as such. Ignore
+ // the "new tab" target, which doesn't make sense when there is a 1:1
+ // relationship between the active IDE file and the file being edited
+ // in the CodeChat Editor. If two tabs are open, which is the current
+ // file for the IDE?
+ saveThenNavigate(new URL(link.href));
+ } else if (isVsCode()) {
+ // This is an external link, which VSCode will block, since it's
+ // cross-origin (the root iframe has no URL, in contrast with the
+ // localhost URL of the CodeChat Editor Server). Ask the Server to open
+ // it instead.
+ parent.window.CodeChatEditorFramework.webSocketComm.sendMessage({
+ OpenUrl: link.href,
+ });
+ } else {
+ // This is an external link which the browser won't follow, so open it
+ // here. `isLeftToBrowser` leaves a click on such a link to the browser,
+ // but the browser follows no link in editable content, TinyMCE cancels
+ // a click in non-editable content, and neither Alt+Enter nor a context
+ // menu item is a click on the link.
+ window.open(link.href, "_blank", "noopener,noreferrer");
}
};
diff --git a/client/src/CodeMirror-integration.mts b/client/src/CodeMirror-integration.mts
index 9050dae2..18d71a68 100644
--- a/client/src/CodeMirror-integration.mts
+++ b/client/src/CodeMirror-integration.mts
@@ -85,6 +85,8 @@ import {
tinymceInstance,
tinymce,
init,
+ followLink,
+ isLeftToBrowser,
} from "./CodeChatEditor.mjs";
import {
CodeChatForWeb,
@@ -834,6 +836,112 @@ const onDirty = (
});
};
+// Links in doc blocks
+// -------------------
+//
+// In the doc block holding TinyMCE, a plain click or tap on a link places the
+// cursor there, while a Ctrl+click (Cmd+click on macOS, where Ctrl+click opens
+// the context menu) follows it. The handlers below give links in every other
+// doc block the same behavior; otherwise, the browser follows a link on a plain
+// click and opens it in a new tab on a Ctrl+click. See `Link navigation` in
+// [implementation.md](../../docs/implementation.md).
+//
+// `codeMirrorLoad` adds these handlers to the element enclosing CodeMirror,
+// since `DocBlockWidget.ignoreEvent` hides mouse events in a doc block from the
+// handlers `DocBlockPlugin` defines.
+//
+// Return the link enclosing `target` if that link sits in a doc block which
+// doesn't hold TinyMCE.
+const linkOutsideTinymce = (
+ // The target of a mouse event.
+ target: EventTarget | null,
+ // The link, or `null` if there's none.
+): HTMLAnchorElement | null => {
+ const link = target instanceof Element ? target.closest("a[href]") : null;
+ return link instanceof HTMLAnchorElement &&
+ link.closest(`.CodeChat-doc-contents:not(#${TINYMCE_INST})`) !== null
+ ? link
+ : null;
+};
+
+// In the doc block holding TinyMCE, the Client follows a clicked link when
+// `tinymce.util.VK.metaKeyPressed` accepts the click (see `Following links` in
+// [tinymce-config.mts](tinymce-config.mts)), so `isFollowClick` repeats that
+// test: TinyMCE loads only once a doc block gains the focus, too late for a
+// click which arrives first. On macOS and iOS, which TinyMCE recognizes from the
+// user agent much as below, the test requires Cmd; elsewhere, it requires Ctrl
+// without Alt, since AltGr, which types characters on many Windows keyboard
+// layouts, reports both.
+const isMacOsOrIos = /iphone|ipad|mac os x/.test(
+ navigator.userAgent.toLowerCase(),
+);
+
+const isFollowClick = (
+ // A mouse event on a link.
+ event: MouseEvent,
+ // True if the event asks to follow the link.
+): boolean => (isMacOsOrIos ? event.metaKey : event.ctrlKey && !event.altKey);
+
+// Chromium gives a pressed link the focus, and the `focusin` handler in
+// `DocBlockPlugin` then moves TinyMCE into the link's doc block. However,
+// Chromium leaves the cursor where it was when the pressed link isn't editable,
+// so a plain press places the cursor where the press landed, for that handler
+// to carry into TinyMCE. A Ctrl+press leaves the focus, and therefore TinyMCE,
+// where it is, so that the link is still in place when its click arrives.
+const onDocBlockLinkMousedown = (
+ // A press anywhere in CodeMirror.
+ event: MouseEvent,
+) => {
+ const link = linkOutsideTinymce(event.target);
+ if (link === null || event.button !== 0) {
+ return;
+ }
+ if (isFollowClick(event)) {
+ event.preventDefault();
+ return;
+ }
+ // Chromium added the standard `caretPositionFromPoint` only in version 128;
+ // older browsers provide only the non-standard `caretRangeFromPoint`.
+ const selection = window.getSelection();
+ if (typeof document.caretPositionFromPoint === "function") {
+ const caret = document.caretPositionFromPoint(
+ event.clientX,
+ event.clientY,
+ );
+ if (caret !== null) {
+ selection?.collapse(caret.offsetNode, caret.offset);
+ }
+ } else {
+ const caret = document.caretRangeFromPoint(
+ event.clientX,
+ event.clientY,
+ );
+ if (caret !== null) {
+ selection?.collapse(caret.startContainer, caret.startOffset);
+ }
+ }
+};
+
+// A plain click which arrives before TinyMCE has replaced the pressed link,
+// such as a tap's, lands on that link; cancel it. A Ctrl+click lands on the
+// link, which its press left in place; follow it, unless that's left to the
+// browser.
+const onDocBlockLinkClick = (
+ // A click anywhere in CodeMirror.
+ event: MouseEvent,
+) => {
+ const link = linkOutsideTinymce(event.target);
+ if (link === null) {
+ return;
+ }
+ if (!isFollowClick(event)) {
+ event.preventDefault();
+ } else if (!isLeftToBrowser(link)) {
+ event.preventDefault();
+ followLink(link);
+ }
+};
+
// Keyboard navigation between code and doc blocks
// -----------------------------------------------
//
@@ -1195,10 +1303,13 @@ export const DocBlockPlugin = ViewPlugin.fromClass(
);
indentDiv.addEventListener("input", onIndentInput);
- // If the target is in the indent, not the contents, then the
- // following code isn't needed.
+ // The focus lands on the contents div or on a focusable element
+ // inside it, such as a link, which Chromium focuses when
+ // pressed (see `onDocBlockLinkMousedown`), or typeset math,
+ // which MathJax's menu makes focusable. If the target is in the
+ // indent instead, then the following code isn't needed.
if (
- !(eventTarget instanceof HTMLDivElement) ||
+ !(eventTarget instanceof Element) ||
eventTarget.closest(".CodeChat-doc-contents") === null
) {
return false;
@@ -1209,10 +1320,26 @@ export const DocBlockPlugin = ViewPlugin.fromClass(
startAutoUpdateTimer();
// See if this is already a TinyMCE instance; if not, move it
- // here.
- if (isTinymce) {
+ // here. Moving TinyMCE rebuilds the contents, so focus on
+ // embedded interactive content, such as an iframe or a
+ // ``, must leave TinyMCE where it is.
+ if (
+ isTinymce ||
+ eventTarget.closest("iframe, summary") !== null
+ ) {
// Nothing to do.
} else {
+ // Focus which reaches an element inside the contents from
+ // the keyboard, such as a link reached with the Tab key,
+ // leaves the selection where it was, perhaps in another doc
+ // block; place the cursor at that element's start instead.
+ const selection = window.getSelection();
+ if (
+ eventTarget !== contentsDiv &&
+ !contentsDiv.contains(selection?.anchorNode ?? null)
+ ) {
+ selection?.collapse(eventTarget, 0);
+ }
// Wait until the focus event completes; this causes the
// cursor position (the selection) to be set in the
// contenteditable div. Then, save that location.
@@ -1722,6 +1849,8 @@ export const codeMirrorLoad = async (
);
const codechatDiv = codechatBody.childNodes[0];
assert(codechatDiv instanceof HTMLDivElement);
+ codechatDiv.addEventListener("mousedown", onDocBlockLinkMousedown);
+ codechatDiv.addEventListener("click", onDocBlockLinkClick);
currentView = new EditorView({
parent: codechatDiv,
state,
diff --git a/client/src/tinymce-config.mts b/client/src/tinymce-config.mts
index ca65e97b..6bb9b82c 100644
--- a/client/src/tinymce-config.mts
+++ b/client/src/tinymce-config.mts
@@ -67,6 +67,9 @@ import "tinymce/plugins/visualchars/index.js";
/// import './plugins/powerpaste/plugin';
/// import './plugins/powerpaste/js/wordimport';
+// Import local code.
+import { followLink } from "./CodeChatEditor.mjs";
+
// Initialize TinyMCE.
export const init = async (
// Provide additional editor options; note that the basic setup provided
@@ -153,32 +156,193 @@ export const init = async (
"graphviz-graph[scale],wc-mermaid,xref[contenteditable|ref],fragment[contenteditable|following|id]",
// Per the [docs](https://www.tiny.cloud/docs/tinymce/latest/content-filtering/#custom_elements), `~` marks tags as an inline element, not a block element.
custom_elements: "graphviz-graph,wc-mermaid,~xref,~fragment",
+
+ // Setting the
+ // [context menu](https://www.tiny.cloud/docs/tinymce/latest/contextmenu/#contextmenu)
+ // replaces TinyMCE's default list, so this must also name the
+ // context menus of the plugins imported above, except the link
+ // plugin's, which `LINK_MENU` replaces.
+ contextmenu: `${LINK_MENU} image table`,
},
);
// Merge in additional setup code.
const oldSetup = combinedOptions.setup;
- combinedOptions.setup =
+ combinedOptions.setup = (editor: Editor) => {
+ oldSetup?.(editor);
// Add a "Format as code" button (generated by Gemini).
- (editor: Editor) => {
- oldSetup?.(editor);
- editor.ui.registry.addToggleButton("codeformat", {
- text: "<>",
- tooltip: "Format as code",
- onAction: () =>
- editor.execCommand("mceToggleFormat", false, "code"),
- onSetup: (api) => {
- const changed = editor.formatter.formatChanged(
- "code",
- (state) => api.setActive(state),
- );
- return () => changed.unbind();
- },
- });
- };
+ editor.ui.registry.addToggleButton("codeformat", {
+ text: "<>",
+ tooltip: "Format as code",
+ onAction: () =>
+ editor.execCommand("mceToggleFormat", false, "code"),
+ onSetup: (api) => {
+ const changed = editor.formatter.formatChanged(
+ "code",
+ (state) => api.setActive(state),
+ );
+ return () => changed.unbind();
+ },
+ });
+ takeLinkGestures(editor);
+ addLinkMenu(editor);
+ };
// Use these combined options to
// [init](https://www.tiny.cloud/docs/tinymce/latest/apis/tinymce.root/#init)
// TinyMCE.
return await tinymce.init(combinedOptions);
};
+
+// Following links
+// ---------------
+//
+// The Client, rather than TinyMCE's link plugin, follows the links in the
+// editor, through `followLink`, so that they act like links everywhere else in
+// the Client; see `Link navigation` in
+// [implementation.md](../../docs/implementation.md). The plugin follows a link
+// on Ctrl+click (Cmd+click on macOS) and Alt+Enter through its own private
+// code, not through an item the Client could replace, so the handlers below
+// take these gestures before the plugin sees them; handlers added during setup
+// run before those the plugin adds when the editor initializes. The link
+// context menu below likewise replaces the plugin's.
+//
+// Each link the code below finds is an HTML link, never an `a` element in
+// inline SVG, whose `href` isn't a URL: the Server's sanitizer removes inline
+// SVG (see `AMMONIA_OPTIONS` in
+// [processing.rs](../../server/src/processing.rs)), replacing the Client's copy
+// of any edit which added some.
+//
+// TODO: the "Open link" button of the plugin's quick link form, which the quick
+// toolbar's `quicklink` button opens, still follows a link to an id itself,
+// searching only the doc block holding TinyMCE. Route it through `followLink`
+// when fixing links to an id elsewhere in the current file (see the TODO in
+// `followLink`).
+const takeLinkGestures = (
+ // The editor whose links the Client follows.
+ editor: Editor,
+) => {
+ editor.on("click", (event) => {
+ const link =
+ event.target instanceof Element
+ ? event.target.closest("a[href]")
+ : null;
+ if (link !== null && tinymce.util.VK.metaKeyPressed(event)) {
+ // The plugin follows a clicked link even when an earlier handler
+ // canceled the click, so stop the plugin from seeing it.
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ followLink(link);
+ }
+ });
+
+ editor.on("keydown", (event) => {
+ if (
+ event.key === "Enter" &&
+ event.altKey &&
+ !event.ctrlKey &&
+ !event.metaKey &&
+ !event.shiftKey
+ ) {
+ const link = selectedLink(editor);
+ if (link !== null) {
+ // The plugin ignores a canceled keydown.
+ event.preventDefault();
+ followLink(link);
+ }
+ }
+ });
+};
+
+// Return the link which the keyboard follows, through Alt+Enter or the context
+// menu: the link holding the start of the selection, or else the first link in
+// the selection. A selected non-editable element encloses its links instead of
+// sitting in one, so this is its first link.
+const selectedLink = (
+ // The editor holding the selection.
+ editor: Editor,
+ // The link, or `null` if there's none.
+): HTMLAnchorElement | null => {
+ const range = editor.selection.getRng();
+ return (
+ editor.dom.getParent(
+ editor.selection.getStart(true),
+ "a[href]",
+ ) ??
+ editor.dom
+ .select("a[href]", editor.selection.getNode())
+ .find((link) => range.intersectsNode(link)) ??
+ null
+ );
+};
+
+// ### The link context menu
+//
+// This replaces the link plugin's context menu, whose "Open link" follows a
+// link to an id itself, and which offers nothing for non-editable content. The
+// Server hydrates cross-references, fragments, gather lists, and math as
+// non-editable content; see . This menu keeps
+// the plugin's items for editing an editable link, and adds its own "Open link"
+// for any link. TinyMCE shows its context menus on a long press as well as a
+// right click, so this serves touch devices too.
+//
+// The names under which TinyMCE registers this context menu and its "Open link"
+// item.
+const LINK_MENU = "codechatlink";
+const OPEN_LINK_ITEM = "codechatopenlink";
+
+const addLinkMenu = (
+ // The editor whose context menu this is.
+ editor: Editor,
+) => {
+ // The link which the context menu was most recently shown for.
+ let menuLink: HTMLAnchorElement | null = null;
+ // The target of the event which most recently opened the context menu.
+ // TinyMCE's theme starts listening for these events only once the editor
+ // initializes, so this listener, added during setup, runs first.
+ let menuEventTarget: EventTarget | null = null;
+ editor.on("contextmenu longpress", (event) => {
+ menuEventTarget = event.target;
+ });
+
+ editor.ui.registry.addContextMenu(LINK_MENU, {
+ update: (
+ // The element right-clicked or long-pressed, which is the target
+ // of the event opening the menu, or the start of the selection
+ // when TinyMCE judges that the keyboard opened the menu.
+ element,
+ ) => {
+ menuLink =
+ element === menuEventTarget
+ ? // A right click or long press beside a link offers no
+ // "Open link".
+ editor.dom.getParent(
+ element,
+ "a[href]",
+ )
+ : selectedLink(editor);
+ // Offer the plugin's
+ // [menu items](https://www.tiny.cloud/docs/tinymce/latest/link/#menu-items)
+ // for editing a link only in editable content, as the plugin's
+ // own menu does.
+ if (editor.dom.isEditable(element)) {
+ return menuLink === null
+ ? "link"
+ : `link unlink ${OPEN_LINK_ITEM}`;
+ }
+ return menuLink === null ? "" : OPEN_LINK_ITEM;
+ },
+ });
+
+ editor.ui.registry.addMenuItem(OPEN_LINK_ITEM, {
+ text: "Open link",
+ icon: "new-tab",
+ onAction: () => {
+ // Content the IDE sent while the menu was open may have replaced
+ // the link; don't follow a link which is no longer shown.
+ if (menuLink !== null && editor.getBody().contains(menuLink)) {
+ followLink(menuLink);
+ }
+ },
+ });
+};
diff --git a/docs/accessibility_review.md b/docs/accessibility_review.md
index e625de83..3fa442b3 100644
--- a/docs/accessibility_review.md
+++ b/docs/accessibility_review.md
@@ -35,10 +35,11 @@ This file is written by hand; only its evidence is generated. After changing
anything which affects the rendered page, refresh the evidence and then edit the
prose against it:
-1. Rebuild the Client if any of its TypeScript or CSS changed, by running `./bt
- client-build` from `server/`. The tests serve the *bundle*, so a source-only
- change stays invisible to them until it's rebuilt. This is the step which is
- easy to forget and which silently yields stale findings.
+1. Rebuild the Client if any of its TypeScript or CSS changed, by running
+ [`./bt client-build`](#cc-1s8bUScadp) from the root of the project. The tests
+ serve the *bundle*, so a source-only change stays invisible to them until
+ it's rebuilt. This is the step which is easy to forget and which silently
+ yields stale findings.
2. Run `cargo test --test overall a11y` from `server/`. Each scenario rewrites
`server/target/a11y/.json` -- the audit findings, the heading and
diff --git a/docs/design.md b/docs/design.md
index 3997f3ba..10440d37 100644
--- a/docs/design.md
+++ b/docs/design.md
@@ -27,13 +27,13 @@ To build from source
recommend the 64-bit toolset for Windows.
3. [Install NPM](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
(the Node.js package manager).
-4. In the `server/` directory:
+4. In the root directory of the project:
1. Run `./bt install --dev`.
2. Run `./bt build`.
- 3. Run `cargo run -- start ../README.md`.
+5. In the `server/` directory, run `cargo run -- start ../README.md`.
-Use `./bt` tool's options update all libraries (`update`), run all tests
-(`test`), and more.
+Use the [`bt`](#cc-1s8bUScadp) tool's options to update all libraries
+(`update`), run all tests (`test`), and more.
Vision
-------------------------
diff --git a/docs/implementation.md b/docs/implementation.md
index ed2321cb..093ce62b 100644
--- a/docs/implementation.md
+++ b/docs/implementation.md
@@ -16,8 +16,7 @@ You should have received a copy of the GNU General Public License along with the
CodeChat Editor. If not, see
[http://www.gnu.org/licenses/](http://www.gnu.org/licenses/).
-Implementation
-==============
+# Implementation
### System architecture
@@ -78,15 +77,13 @@ same capture event shape to the local CodeChat server through the existing
NAPI-RS bridge. The server hashes raw local file paths, removes any raw path
fields from event data before local persistence, writes every event to a durable
`capture-spool` FIFO directory under VS Code's global extension storage, and
-uploads batches to
-`POST /v1/capture/events` using
-`Authorization: Bearer `. CodeChat does not read database credentials
-from disk and does not connect directly to PostgreSQL; the database schema under
-`server/scripts/` documents the server-side table used by CaptureWebService.
-The previous local JSON database-secret configuration path is intentionally
-removed; any database writer role or password must remain server-side in the
-CaptureWebService deployment.
-The service endpoint is read only from the user/application-level
+uploads batches to `POST /v1/capture/events` using `Authorization: Bearer
+`. CodeChat does not read database credentials from disk and does not
+connect directly to PostgreSQL; the database schema under `server/scripts/`
+documents the server-side table used by CaptureWebService. The previous local
+JSON database-secret configuration path is intentionally removed; any database
+writer role or password must remain server-side in the CaptureWebService
+deployment. The service endpoint is read only from the user/application-level
`CodeChatEditor.Capture.ServiceBaseUrl` setting; workspace values are ignored so
a repository cannot redirect a stored token. Bearer-token requests require
HTTPS, with `http://localhost` and `http://127.0.0.1` allowed only for local
@@ -119,8 +116,7 @@ The verified extension-generated event stream includes `session_start`,
contract tests submit representative well-formed event batches for optional
study/debug/build event types through the same public HTTPS API.
-Architecture
-------------------------------------------
+## Architecture
Overall, the code is something like this:
@@ -144,8 +140,8 @@ files. Therefore, the overall strategy is:
which implement custom tags which only need local information. For example, a
GraphViz custom tag renders graphs based on a description of the graph inside
the tag. Likewise, MathJax interprets anything in matching math delimiters
- (..., for example), then
- transforms it back to text before saving.
+ (`...`, for example),
+ then transforms it back to text before saving.
* On save, the client sends its text back to the server, which de-transforms
custom tags which depend on information from other pages. If de-transforms
disagree with the provided text, then re-load the updated text after the save
@@ -200,10 +196,6 @@ On save:
* Save the file to disk.
* If dirty, re-load the file.
-### Cache data format
-
-Documented elsewhere.
-
### IDE/editor integration
The IDE extension client (IDE for short) and the CodeChat Editor Client (or
@@ -269,7 +261,10 @@ The app needs build support because of complexity:
to the VSCode extension, then a release published for that platform.
So, this project contains Rust code to automate this process -- see the
-[builder](../builder/Cargo.toml).
+[builder](../builder/Cargo.toml). The `bt` script simplifies invoking the
+builder.
+
+### `bt` (build tool) script
Misc topics
-----------
@@ -362,12 +357,6 @@ Future work
`h1::before {` `counter-reset: var(--section-counter-reset);` `content:
var(--section-counter-content);` `}`
-#### Example of non-editable text
-
-
-
### Numbering
On a page, the local TOC numbering comes only from heading tags. The CSS (which
@@ -399,8 +388,7 @@ with descriptions of each setting.
* Tabs vs spaces; newline type
* Substitutions
-Core development priorities
-------------------------------------------------------------------
+## Core development priorities
1. Bug fixes
2. Book support
@@ -505,13 +493,59 @@ The CodeChat Editor client supports four modes:
* Usual: the usual CodeMirror + TinyMCE editor
* View:
* For a ReadTheDocs / browsing experience: clicking on links navigates to them
- immediately, instead of bringing up a context menu. Still use CodeMirror for
- syntax highlighting, collapsing, etc.
+ immediately, instead of placing the cursor in them as Edit mode does. Still
+ use CodeMirror for syntax highlighting, collapsing, etc.
* Simple viewer:
* For text or binary files that aren't supported by the editor. In project
mode, this displays the TOC on the left and the file contents in the main
area; otherwise, it's only the main area. See: \.
+Link navigation
+---------------
+
+Note: this section is specification; all other sections are documentation.
+
+In Edit mode, a source file has one TinyMCE editor, which the Client moves into
+a doc block when that block gains focus; it stays there until another doc block
+gains focus. Since TinyMCE runs inline, nothing marks which block holds it, so
+links must behave alike in every doc block. In the block holding TinyMCE, a
+click or tap on a link places the cursor (or selects the link's non-editable
+element) without following it, while a Ctrl+click (Cmd+click on macOS) follows
+it. In any other doc block, the browser would follow the link on a plain click
+and open it in a new tab on a Ctrl+click. Instead, the Client makes a plain
+click or tap move TinyMCE into that block, placing the cursor where the click
+landed, or beside the link's non-editable element, since TinyMCE keeps the
+cursor out of non-editable content. A Ctrl+click follows the link, as in the
+block holding TinyMCE. Focusing a link from the keyboard, as the Tab key does,
+likewise moves TinyMCE into its block, placing the cursor at the link's start.
+
+The Client, not TinyMCE's link plugin, follows every link, through `followLink`:
+a link to an id scrolls the element with that id into view, a link to a CodeChat
+Editor file loads through `saveThenNavigate`, and an external link opens through
+the IDE in VSCode or in a new tab in a browser. This covers a Ctrl+click in any
+doc block; Alt+Enter in the block holding TinyMCE, which follows the link
+holding the start of the selection, or else the first link in the selection; and
+the "Open link" item of the context menu for a link in the block holding
+TinyMCE, whether or not the link is editable (see
+).
+
+The one exception is a Ctrl+click, in a browser, on an external link in a doc
+block which doesn't hold TinyMCE: the Client leaves that click to the browser,
+which opens the link as the click's modifier keys ask, such as in a background
+tab. The browser follows no link in editable content, and TinyMCE cancels a
+click in non-editable content, so in the block holding TinyMCE, `followLink`
+opens an external link in a new tab itself.
+
+TODO: the "Open link" button of the link plugin's quick link form, which the
+quick toolbar's link button opens, follows a link to an id itself, searching
+only the block holding TinyMCE. Route it through `followLink` once `followLink`
+finds an id anywhere in the current file (see the TODO in `followLink`).
+
+TODO: a right click or long press on a link in a doc block which doesn't hold
+TinyMCE should show TinyMCE's context menu (see
+). Remove this once
+`test_open_non_editable_link_py_unfocused` passes without its `ignore`.
+
Misc
----
diff --git a/docs/style_guide.cpp b/docs/style_guide.cpp
index 1d67bd3d..4270285e 100644
--- a/docs/style_guide.cpp
+++ b/docs/style_guide.cpp
@@ -27,9 +27,9 @@
// CodeChat Editor. For basic use, see the [user manual](../README.md).
//
// The CodeChat Editor divides code into code blocks and documentation (doc)
-// blocks. These blocks are separated by newlines; comments on the same line as
-// code are not interpreted doc blocks. Doc blocks must have 1 space after the
-// comment delimiter. For example, this paragraph is a doc block;
+// blocks. These blocks are separated by newlines; comments on the same line as
+// code are not interpreted doc blocks. Doc blocks must have at least one space after the
+// comment delimiter. For example, this paragraph is a doc block;
const char* CODE_BLOCK =
"this is a code block."; // Comments here are NOT part of a doc block.
@@ -63,7 +63,7 @@ const char* CODE_BLOCK =
// handle.
//
// **Phase 2 - writing.** As you write code, save links to helpful code
-// snippets, documentation, and ideas. Update the approach you sketched out as
+// snippets, documentation, and ideas. Update the approach you sketched out as
// your learn what works (and doesn't) through the development process. Explain
// any pieces of code that took significant development or debug time, or which
// contain difficult to understand code.
diff --git a/extensions/VSCode/package.json b/extensions/VSCode/package.json
index 215d8c4a..87a80c10 100644
--- a/extensions/VSCode/package.json
+++ b/extensions/VSCode/package.json
@@ -41,7 +41,7 @@
"type": "git",
"url": "https://github.com/bjones1/CodeChat_Editor"
},
- "version": "0.3.0",
+ "version": "0.3.1",
"activationEvents": [
"onCommand:extension.codeChatEditorActivate",
"onCommand:extension.codeChatEditorDeactivate",
@@ -121,23 +121,23 @@
"@napi-rs/cli": "^3.10.8",
"@tybys/wasm-util": "^0.10.4",
"@types/escape-html": "^1.0.4",
- "@types/node": "^26.6.4",
+ "@types/node": "^26.6.5",
"@types/vscode": "1.61.0",
- "@typescript-eslint/eslint-plugin": "^8.71.1",
- "@typescript-eslint/parser": "^8.71.1",
+ "@typescript-eslint/eslint-plugin": "^8.72.0",
+ "@typescript-eslint/parser": "^8.72.0",
"@typescript/native": "npm:typescript@^7.0.2",
"@vscode/vsce": "^4.0.0",
"chalk": "^6.0.1",
"esbuild": "^0.28.2",
- "eslint": "^10.12.0",
+ "eslint": "^10.13.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-node": "^11.1.0",
- "eslint-plugin-prettier": "^5.5.6",
+ "eslint-plugin-prettier": "^5.5.7",
"npm-run-all2": "^9.0.3",
- "prettier": "^3.9.9",
+ "prettier": "^3.9.10",
"typescript": "npm:@typescript/typescript6@^6.0.2",
- "typescript-eslint": "^8.71.1"
+ "typescript-eslint": "^8.72.0"
},
"optionalDependencies": {
"bufferutil": "^4.1.0"
diff --git a/extensions/VSCode/pnpm-lock.yaml b/extensions/VSCode/pnpm-lock.yaml
index 594ee514..5978ed84 100644
--- a/extensions/VSCode/pnpm-lock.yaml
+++ b/extensions/VSCode/pnpm-lock.yaml
@@ -20,10 +20,10 @@ importers:
version: 1.11.3
'@eslint/js':
specifier: ^10.0.1
- version: 10.0.1(eslint@10.12.0)
+ version: 10.0.1(eslint@10.13.0)
'@napi-rs/cli':
specifier: ^3.10.8
- version: 3.10.8(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@26.6.4)
+ version: 3.10.8(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@26.6.5)
'@tybys/wasm-util':
specifier: ^0.10.4
version: 0.10.4
@@ -31,17 +31,17 @@ importers:
specifier: ^1.0.4
version: 1.0.4
'@types/node':
- specifier: ^26.6.4
- version: 26.6.4
+ specifier: ^26.6.5
+ version: 26.6.5
'@types/vscode':
specifier: 1.61.0
version: 1.61.0
'@typescript-eslint/eslint-plugin':
- specifier: ^8.71.1
- version: 8.71.1(@typescript-eslint/parser@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0))(@typescript/typescript6@6.0.2)(eslint@10.12.0)
+ specifier: ^8.72.0
+ version: 8.72.0(@typescript-eslint/parser@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0))(@typescript/typescript6@6.0.2)(eslint@10.13.0)
'@typescript-eslint/parser':
- specifier: ^8.71.1
- version: 8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0)
+ specifier: ^8.72.0
+ version: 8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0)
'@typescript/native':
specifier: npm:typescript@^7.0.2
version: typescript@7.0.2
@@ -55,32 +55,32 @@ importers:
specifier: ^0.28.2
version: 0.28.2
eslint:
- specifier: ^10.12.0
- version: 10.12.0
+ specifier: ^10.13.0
+ version: 10.13.0
eslint-config-prettier:
specifier: ^10.1.8
- version: 10.1.8(eslint@10.12.0)
+ version: 10.1.8(eslint@10.13.0)
eslint-plugin-import:
specifier: ^2.32.0
- version: 2.32.0(@typescript-eslint/parser@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0))(eslint@10.12.0)
+ version: 2.32.0(@typescript-eslint/parser@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0))(eslint@10.13.0)
eslint-plugin-node:
specifier: ^11.1.0
- version: 11.1.0(eslint@10.12.0)
+ version: 11.1.0(eslint@10.13.0)
eslint-plugin-prettier:
- specifier: ^5.5.6
- version: 5.5.6(eslint-config-prettier@10.1.8(eslint@10.12.0))(eslint@10.12.0)(prettier@3.9.9)
+ specifier: ^5.5.7
+ version: 5.5.7(eslint-config-prettier@10.1.8(eslint@10.13.0))(eslint@10.13.0)(prettier@3.9.10)
npm-run-all2:
specifier: ^9.0.3
version: 9.0.3
prettier:
- specifier: ^3.9.9
- version: 3.9.9
+ specifier: ^3.9.10
+ version: 3.9.10
typescript:
specifier: npm:@typescript/typescript6@^6.0.2
version: '@typescript/typescript6@6.0.2'
typescript-eslint:
- specifier: ^8.71.1
- version: 8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0)
+ specifier: ^8.72.0
+ version: 8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0)
optionalDependencies:
bufferutil:
specifier: ^4.1.0
@@ -116,16 +116,16 @@ packages:
resolution: {integrity: sha512-9n2pWK61veAuN0V20t9lOuoV4CFMdyAZ1ygZzvBGk/pBBJRib/PjL9PLXa/aI2CcPpyHfqVsxxqLCYl6uZlfDw==}
engines: {node: '>=22.0.0'}
- '@azure/identity@4.13.3':
- resolution: {integrity: sha512-zGQPtqvXPgSA8yfV2CkIQ1qirqk0p9AIVpC5uEkdXQYcKl07QHvyaGYRnZOk0AsQUmxNb4wfkcwY5di8Z5xa9A==}
+ '@azure/identity@4.14.0':
+ resolution: {integrity: sha512-8utCLdTZn14CiAe0HW4JbtzUMsc0mdDwnjUvStFMrR4IxQP8S2eL9zVAdplpfORVJGYmidZElpeXRYjJQ4UyUw==}
engines: {node: '>=22.0.0'}
'@azure/logger@1.4.0':
resolution: {integrity: sha512-rbAE25KUfjU/s3XHUdJgceoCP5dEOpMx85J04kF+QMdta73XkuG9JGHHinch+XIoKpBdqljin+KqURpJriSzLA==}
engines: {node: '>=22.0.0'}
- '@azure/msal-browser@5.25.0':
- resolution: {integrity: sha512-7GyXzDUrdLG5zPDCYJbQdJqbl9a2U9SWUSC4lxr7cXjkBq6diTfWcJsi0UjdGWlqy+EevQg9dSC4g3s3BEp19g==}
+ '@azure/msal-browser@5.25.1':
+ resolution: {integrity: sha512-NDW2/bz2Pz71EJAlhSw+jg1kpRIWtrzj0gCzcHuG1u7iLvSOHpUc5lICkDA9x0bbeityxTneiunSBluvS+/y4w==}
engines: {node: '>=0.8.0'}
'@azure/msal-common@16.14.1':
@@ -146,6 +146,9 @@ packages:
'@cacheable/utils@2.5.0':
resolution: {integrity: sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==}
+ '@dual-bundle/import-meta-resolve@4.2.1':
+ resolution: {integrity: sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==}
+
'@emnapi/core@1.11.2':
resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==}
@@ -392,8 +395,8 @@ packages:
resolution: {integrity: sha512-0Ywt3S8tqM6TWUcsRlBFQ1x+X3/o8kktX0ZiKAnByYb9GFDknnOhse1PVtfiD2BXPI8ar+BWx9PV9R0lQIcygw==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
- '@inquirer/checkbox@5.2.6':
- resolution: {integrity: sha512-XkOardciF3tow2NUlY6/Qjk1QqYa+6WpmVLTpxXl97Uv4erczDEZ+xg1j6LqBf0ekcRSUiICQA4nQXHHkVKQ9w==}
+ '@inquirer/checkbox@5.2.7':
+ resolution: {integrity: sha512-/+QmGE/9bvE/EobaZDnzYe+T+QWoJrnmxwBQNtoi2OVSUR9d1znYsNyEoUrOZvFSxDYttrhzRIpXpaLkN+iIsg==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -401,8 +404,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/confirm@6.3.3':
- resolution: {integrity: sha512-ImOBgpl/sJTFp8naeSkNu4S0Sf8F8pxv3/CUx+LoBAdQvCVqFz6rSB2A/oZYsJge3+yphbEDxEkfaRNYq1OU0A==}
+ '@inquirer/confirm@6.3.4':
+ resolution: {integrity: sha512-ZwnaZZDa1zxsyVrN32s447ydMnKcw7e8WWNT+R+Qsu0zAn8Rs6yi9d1B80Kob1Ul2cfUi7t8JD2t4/K7uEZpqA==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -410,8 +413,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/core@12.0.4':
- resolution: {integrity: sha512-M5fB67y7jPObI45vhD82RskWwCSm0scOvenV/I6K8tIi2gfr9DXqQqJ+iw+Wi6SgapikdOiJibYzz1gW47fsDg==}
+ '@inquirer/core@12.0.5':
+ resolution: {integrity: sha512-c0ZSnO9oVk/IlEGoZK3u8b3IZQeu9I6jUkXJSQnQEVRXsjKvtAq3AGqFL2LgfJhlzMZne08pZHWRWasIcI3Rew==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -419,8 +422,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/editor@5.3.4':
- resolution: {integrity: sha512-imzxQDfw26NpcJAubMj/dE5RjU9Mp80IcT+FzsD6YoxXmL8L1QYnAFdCNv7ccGWh//73+0YP+1kfhwnExdl2VQ==}
+ '@inquirer/editor@5.3.5':
+ resolution: {integrity: sha512-6lU3/pPx5z3lHWejFW061MV4qZooBQYv/+EeGhNOPiP/i+hrYsHfxxBnnDGsAfg8YdLUd69YckoeOjtr3QuAUg==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -428,8 +431,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/expand@5.1.6':
- resolution: {integrity: sha512-JfiLKi7qGuHmcQO/DqR10UFEp054Y1FXZaszP5ZEfHhgHeuFoT5nNFQIXFhed/5/p07SbsV2tIJFwG/dg5+NmQ==}
+ '@inquirer/expand@5.1.7':
+ resolution: {integrity: sha512-eiJ31YFCaB6fLoJCqLIn8S6eqUyME6vwr8KcP5h8cxmA+bV9RRVPO+kywoxb7G63HduDXV6sN/SM7o2R+ahEcQ==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -450,8 +453,8 @@ packages:
resolution: {integrity: sha512-EAWgUTGQ/Umgga51dE3B2PUHbufuXarDfg86uVgoSgNHNNQnyFKcOrQLWVqYMghuSyHh8+2HUH0Js9cTC1WAdg==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
- '@inquirer/input@5.1.7':
- resolution: {integrity: sha512-Icrs9tuMBCxGI+lFYNO71VFOnqGO8IIrLIGqnTGh07X8jZ2la9QLAYxXcikHZQjjVhIZUtU2xyH89DVN/pPhVg==}
+ '@inquirer/input@5.1.8':
+ resolution: {integrity: sha512-psOSTgkL8y/OtgJJYbqJ0oISr2QBE0U1Ro/qQdA/EKjeJiwiUTQgQmq8o4ESNzVOWthIeh3YLD7BCUR9dKnR5g==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -459,8 +462,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/number@4.2.4':
- resolution: {integrity: sha512-C6g80U4U2+w2Vxa1CB0MN3a2WnQIfzLSUG0lduO3Au9GsirJsKATZkOwZPK+vZ1BlAlz+/Z5ParfQCS9E0svmg==}
+ '@inquirer/number@4.2.5':
+ resolution: {integrity: sha512-lEy7LWsdF5qHx5QjHZqabUqM9P7i5uTMCt8GRzqxmwXXKbapKJDA+ucVflVVPAyj4snD5DTAD4L+sct85XOlOQ==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -468,8 +471,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/password@5.2.3':
- resolution: {integrity: sha512-1Uo2qfI2K3wXbfunMpvwAzoZPIUf5iEETwDGqRVbswoorXGqYQJ/bfIJFeLj+pxjq+e2UFPNOmx+fsIl4Urigw==}
+ '@inquirer/password@5.2.4':
+ resolution: {integrity: sha512-T7ir3fflSSVCYEVqNyMBq+pIMsilQRtbZcD5gxn6Ewsin5TJWBxrGBdXveWtmeXVAeoInfhS+BhBUZaL4sFSDg==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -477,8 +480,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/prompts@8.7.3':
- resolution: {integrity: sha512-DHK20kO+H5k/XcJQpHQUpVQbMYHHhZchlGWhZVMUV4WIuRuBW5744hipOtriE1vMqPLgnIyXFMHWKkIbTepa4Q==}
+ '@inquirer/prompts@8.7.4':
+ resolution: {integrity: sha512-SAJt+QhY5rOJAIgZBw9TrHWfgrp6wsb9cqRCyqax+Hl6EiFfRXmD9EsIWkST8yq/dQ8H46Is2Sr/JG7Ls/LQ+Q==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -486,8 +489,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/rawlist@5.3.6':
- resolution: {integrity: sha512-2exExhQ5xTBqfN/rRlZkvM1Gm3b0eiLcWe2NqxWxe7qYmnU4M3j/n9TwzfPUb+Q7dAAz9TmBtDWWdF3jbp4ZLA==}
+ '@inquirer/rawlist@5.3.7':
+ resolution: {integrity: sha512-XveEe8v3BHj9ec00n+9i8FHjWhXjS6+jCTizYvXCqKUKtgkrq9xid8S5j3mYGW96qfxG3mwNoud3m3v8GDk9Pw==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -495,8 +498,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/search@4.3.4':
- resolution: {integrity: sha512-xsk0UeFP04jsbEeFMzghjUaH2aHXyt+WykSajQzq/8qyBNHsgGYHea/sFwv5T08kW7Sp+KDX8aF124zPUIs1Ww==}
+ '@inquirer/search@4.3.5':
+ resolution: {integrity: sha512-kIUecMugEN2YTvTmff+uYz0nAE0f9RFNDVZknfrh264vAQM9P8tnsXiUp7z8XbGuNIBEtFMJhvc9QquawpC46A==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -504,8 +507,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/select@5.2.6':
- resolution: {integrity: sha512-jFu7CFghlr0vpi/326a4wz6BGkJH9X3H/iIu+ahyV2Ja52ktqQE3fC9BYP47JKeSPQYGUv5xquE2Vp9PbepZ3g==}
+ '@inquirer/select@5.2.7':
+ resolution: {integrity: sha512-pRDWO4dTqSAvFQdO8yclKMtXNDm9K9HmvWB5UZmT3clw2OlOBNXveHzhyFmO/0d0yJMqlPcNITus1Jtpm6t8VQ==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -1078,69 +1081,69 @@ packages:
'@types/json5@0.0.29':
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
- '@types/node@26.6.4':
- resolution: {integrity: sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg==}
+ '@types/node@26.6.5':
+ resolution: {integrity: sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg==}
'@types/vscode@1.61.0':
resolution: {integrity: sha512-9k5Nwq45hkRwdfCFY+eKXeQQSbPoA114mF7U/4uJXRBJeGIO7MuJdhF1PnaDN+lllL9iKGQtd6FFXShBXMNaFg==}
- '@typescript-eslint/eslint-plugin@8.71.1':
- resolution: {integrity: sha512-nNlBf6HuotqMgZvSkWD5hNdemiBIxQ2NmrlgfcUIeZRs+BbakjscCQRq/Hb74EFVnFiSWWdL12JruqwBy3ERDw==}
+ '@typescript-eslint/eslint-plugin@8.72.0':
+ resolution: {integrity: sha512-aysKgysPX9XOv/dk95UkvUDe++Q3n3dJPG9CEWT3RUccxfkPJ+Qn6nhKPDhZXU6Ofo9kgwGWwKyQCnZIXTgngg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- '@typescript-eslint/parser': ^8.71.1
+ '@typescript-eslint/parser': ^8.72.0
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/parser@8.71.1':
- resolution: {integrity: sha512-oM/5sAqz/l1v/zYK1qTOPrn2+JIrBGC1V6uNYucRO5gh6eT9vWJ/RhPbGE59byvZEUQ4VP6XkLBaS5Jti5jpvw==}
+ '@typescript-eslint/parser@8.72.0':
+ resolution: {integrity: sha512-0O+x3ynciD6zuXb1CKSu165haQV6ey53DKhHHUnujP7U+XlltJG+/keT0Xhd1HXCze+IHf7Tvt39ovyepX/LNw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/project-service@8.71.1':
- resolution: {integrity: sha512-Oijc9RUsohHjncg6iGi7gCtjnEjYt9FWv9u6ygK8uKcHK5eLjLAkqERQ9BaytbpceOFdk6kifPCS/zAZE+A99g==}
+ '@typescript-eslint/project-service@8.72.0':
+ resolution: {integrity: sha512-bzHngBbmIgzj6TsTjVUCntGx6FFLgE7mPjpO0PzHycOaZHXqS43XnrkFIHRS16LLr/xO6FHSXG3GJFo/F4bSBQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/scope-manager@8.71.1':
- resolution: {integrity: sha512-1YUDZXdTnXLlob03SFHBipdyMBsDVm7zoSz0ytTJ39dk5J1TgsTK03VlR/dLXG/RLLUrPcYbD4rp23BmHxHP0w==}
+ '@typescript-eslint/scope-manager@8.72.0':
+ resolution: {integrity: sha512-JewPyJOYcXBSAS7PIgufscHOwZqhTGyBRiQJhhuDln66tMchxvvt7nJ90JKKIoOelmdHA3kAQbm9cEUHc4JSEA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/tsconfig-utils@8.71.1':
- resolution: {integrity: sha512-oTvkml5SxXhgg+WWKhod5qWQICDXtOB+/Hi9VdPKFAANJ2b7YOgMffgvzXuqog7R6A/JtrzRf128dsxummyWhg==}
+ '@typescript-eslint/tsconfig-utils@8.72.0':
+ resolution: {integrity: sha512-0vbxCf7xLVPSjcOdm6eQbgViH8EDJ8xnl1laLT9yoqSz2YmcSfSg5Vdl0JUvZf6rcuJ0ZivJIuMD4edTg8obaA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/type-utils@8.71.1':
- resolution: {integrity: sha512-kH3t3jZCYv2PZSBEWPKTariLrCaoF1CLoPitYFBJOhAGEwWnSRAtGEifrfrpBa4K9JlwWNZotgA5w3/wLSk/FQ==}
+ '@typescript-eslint/type-utils@8.72.0':
+ resolution: {integrity: sha512-gEXxmmbSTSOVTC37IYYvremOM31v+UN2W772bJxmVYPhAOPSUxtacyb7wkm1EhkYKU6KzFJsdU44DCV4to0LWw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/types@8.71.1':
- resolution: {integrity: sha512-ybYnPTUwg3VQQOFEiBPLs8Jxl1ry2vXdn1kVKguomBbvxpetdu3MKTNDqFNUrlG8ylP9MpZosmXMFYlGQHk1oQ==}
+ '@typescript-eslint/types@8.72.0':
+ resolution: {integrity: sha512-Adfoy59kxNEjT5E5fnyrp7GpHYuWs8ONbdohu4ez6IU46qDq8LltG90tLmkucZB2uhYL8mqBjtXqGd8yhfjXqQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/typescript-estree@8.71.1':
- resolution: {integrity: sha512-pSKSEK1JJpHs6KiKVtABCE4iJK5d7FLFzDXQNBQ2oRY7LEd3UwOt3TZemTgu9syTIf56dJWlTn/pdmrO9oKUXQ==}
+ '@typescript-eslint/typescript-estree@8.72.0':
+ resolution: {integrity: sha512-gwLfFgWcw15tN2KXDCJvxEOQjPn+P3j/n3eSqkZ5sX4VpW9Cosodr+tWwBZRvJT3TcKx6HnPRGZMCNAiP1Xhug==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/utils@8.71.1':
- resolution: {integrity: sha512-CBjT6gfAz3DW2j9Q4moFnqt208Aq+506sVGXfpRcyAeqn0EpayL0mjdWoCO2t3RC7GewNrFxdeehuEaPg6DMzQ==}
+ '@typescript-eslint/utils@8.72.0':
+ resolution: {integrity: sha512-0up3oArJZBYnzOaEXKo8DmI43QvMZyPoFEt9LWzRJcyl/RcJjfKKPebknN1VbTgXBf3M/KggUh5uUMdURLnseg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/visitor-keys@8.71.1':
- resolution: {integrity: sha512-0GxiUGqMU0qXJt58cBsiG3DP8lUmBaFo2vChtUX+CT7PnR3v/VGLLrzVP4K9r1lNiZXjHYefMBbPunplr09v6w==}
+ '@typescript-eslint/visitor-keys@8.72.0':
+ resolution: {integrity: sha512-E2mroviasaWecDzTGiGMtuEguuLVOeRggnrIn93CJuxYJzQBHbyymlT08aVQydbBal5538o+JkBKXh2sRuCyIA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript/typescript-aix-ppc64@7.0.2':
@@ -1345,8 +1348,8 @@ packages:
resolution: {integrity: sha512-kKvt3m4uwzqL0wlkPd09CmljPJGOZZ4D0fP65sqFSvPkMRKhNi+74MgIJ5QxE6SxqB4t4KyUFGg8+n5zjo6hew==}
engines: {node: '>=22'}
- argparse@2.0.1:
- resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+ argparse@3.0.3:
+ resolution: {integrity: sha512-Sn98ftV3KfsO8xfLPHG6oT8y+b0yvbrLgZjgPDXsharNY15tTAjqIsIju4S32nl6/iEUNNE4v+yL2Zhm8ooP3w==}
array-buffer-byte-length@1.0.2:
resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
@@ -1644,8 +1647,8 @@ packages:
peerDependencies:
eslint: '>=5.16.0'
- eslint-plugin-prettier@5.5.6:
- resolution: {integrity: sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==}
+ eslint-plugin-prettier@5.5.7:
+ resolution: {integrity: sha512-WjHyn3GrFd+ZR0rzsLiM4zJZ35PZMOE0SUIxzGmnWNG0126yNfJKJzObmmkYDwHJfGlkz3PSlzyG9ez/LJntwg==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
'@types/eslint': '>=8.0.0'
@@ -1678,8 +1681,8 @@ packages:
resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
- eslint@10.12.0:
- resolution: {integrity: sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA==}
+ eslint@10.13.0:
+ resolution: {integrity: sha512-UohDjD4zSzEwhf58M3O7vc/ycc8O1I7dNlhtD0WxE8Px8EHNJKtUwzBqrzeGhisRuv2ag4sAYswszCJRgKXYUg==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
hasBin: true
peerDependencies:
@@ -1849,8 +1852,8 @@ packages:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
- ignore@7.0.12:
- resolution: {integrity: sha512-/8UvqAPU9DGTI9k4mxtf49U37Isfwr8Uts96+SBHIkFxPJnHS0Ew4f00sM4Scd8V8EjM0jUNtVDdv1kPdt35lg==}
+ ignore@7.0.13:
+ resolution: {integrity: sha512-2E8Q6rh+1RRSq/YdaGsNfXsEZy9FVqv6BSSrkmZJP7BnaoPnRfKL+YaofPvP+5rzdk144qSm6i4+hvPgUcbV4Q==}
engines: {node: '>= 4'}
imurmurhash@0.1.4:
@@ -1918,6 +1921,10 @@ packages:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
+ is-in-ssh@1.0.0:
+ resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==}
+ engines: {node: '>=20'}
+
is-inside-container@1.0.0:
resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
engines: {node: '>=14.16'}
@@ -1989,8 +1996,8 @@ packages:
resolution: {integrity: sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==}
engines: {node: '>=4'}
- js-yaml@5.4.3:
- resolution: {integrity: sha512-yzzNtgczwgoVHNigCyKTXcQJU4Y8t/ikU4UxXW0BvrLfHTGWZhsNcOT2s/a7vn81/4KqVnNmHjaUEPlvWnNwVw==}
+ js-yaml@5.5.0:
+ resolution: {integrity: sha512-paNr4LFIFPjeeSmwNYdrb8FafLLG/Cr2Af3GjbsHJ5CxIjH4r0c2emlmRdAtpPAieP1gRVzk6buBbVP5gBMGAg==}
hasBin: true
json-parse-even-better-errors@6.0.0:
@@ -2059,8 +2066,8 @@ packages:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
- marked@18.1.0:
- resolution: {integrity: sha512-PamYXWWWg2nboG3oX5Ffzpy3EFZROqZJK9hSlMmUnekp7TxPs5DcT95vwd46m73/exfKCRVMtgXoPMI4FEJL6w==}
+ marked@18.1.1:
+ resolution: {integrity: sha512-zEGgrtUG8crk+TrMacJSVABU4DYDDkS8uqgU3ohrn/2fW9C2AEfUrO3m6IhpIpQpl7YcVpiXXC9cSMWDANT3dg==}
engines: {node: '>= 20'}
hasBin: true
@@ -2149,9 +2156,9 @@ packages:
resolution: {integrity: sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==}
engines: {node: '>=12.20.0'}
- open@10.2.0:
- resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==}
- engines: {node: '>=18'}
+ open@11.0.4:
+ resolution: {integrity: sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==}
+ engines: {node: '>=20'}
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
@@ -2199,6 +2206,10 @@ packages:
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
engines: {node: '>= 0.4'}
+ powershell-utils@0.2.1:
+ resolution: {integrity: sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==}
+ engines: {node: '>=20'}
+
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@@ -2207,8 +2218,8 @@ packages:
resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==}
engines: {node: '>=6.0.0'}
- prettier@3.9.9:
- resolution: {integrity: sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==}
+ prettier@3.9.10:
+ resolution: {integrity: sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==}
engines: {node: '>=14'}
hasBin: true
@@ -2223,8 +2234,8 @@ packages:
resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==}
engines: {node: '>=20'}
- qs@6.16.0:
- resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==}
+ qs@6.16.1:
+ resolution: {integrity: sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg==}
engines: {node: '>=0.6'}
read-package-json-fast@6.0.0:
@@ -2370,8 +2381,8 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
- synckit@0.11.13:
- resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==}
+ synckit@0.12.3:
+ resolution: {integrity: sha512-U+/AXT6LWsxnj2vz9HeUuPWBNgdWxjyIEjEPdbJARC9txmgPzo2VEdqnFo4hHaNyMPtycyVJrSaQYeQv/PoyyA==}
engines: {node: ^14.18.0 || >=16.0.0}
textextensions@6.11.0:
@@ -2424,8 +2435,8 @@ packages:
typed-rest-client@1.8.11:
resolution: {integrity: sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==}
- typescript-eslint@8.71.1:
- resolution: {integrity: sha512-oiNVPC3/NbV1FpaHU07l8O6ynqG9v4PAiW2WvTrAmZdR0f2HI/gE668ElMLCK7Xbsu5k2fK5C4fXNiq05j7/nQ==}
+ typescript-eslint@8.72.0:
+ resolution: {integrity: sha512-r26Fkc8As+mZvqp7SLsMmNFyYUup/6CjK7/eHBHmhOw6GH6n6YFxHVFrG0jN1c5CMpDzelLPxvgeCOzx7BjN+A==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
@@ -2494,9 +2505,9 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
- wsl-utils@0.1.0:
- resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==}
- engines: {node: '>=18'}
+ wsl-utils@1.0.1:
+ resolution: {integrity: sha512-c16PKLIRxBYpR3jSWsdS7SFp0COm5SOQdiFjY818dYFHJbMGuLBszpF07LBWmOS1uzrHa9VGdynsEgoeKy0oOQ==}
+ engines: {node: '>=20'}
xml2js@0.5.0:
resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==}
@@ -2572,7 +2583,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@azure/identity@4.13.3':
+ '@azure/identity@4.14.0':
dependencies:
'@azure/abort-controller': 2.2.0
'@azure/core-auth': 1.11.0
@@ -2582,9 +2593,9 @@ snapshots:
'@azure/core-tracing': 1.4.0
'@azure/core-util': 1.14.0
'@azure/logger': 1.4.0
- '@azure/msal-browser': 5.25.0
+ '@azure/msal-browser': 5.25.1
'@azure/msal-node': 6.0.1
- open: 10.2.0
+ open: 11.0.4
tslib: 2.8.1
transitivePeerDependencies:
- supports-color
@@ -2596,7 +2607,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@azure/msal-browser@5.25.0':
+ '@azure/msal-browser@5.25.1':
dependencies:
'@azure/msal-common': 16.14.2
@@ -2621,6 +2632,8 @@ snapshots:
hashery: 1.5.1
keyv: 5.6.0
+ '@dual-bundle/import-meta-resolve@4.2.1': {}
+
'@emnapi/core@1.11.2':
dependencies:
'@emnapi/wasi-threads': 1.2.2
@@ -2744,9 +2757,9 @@ snapshots:
'@esbuild/win32-x64@0.28.2':
optional: true
- '@eslint-community/eslint-utils@4.10.1(eslint@10.12.0)':
+ '@eslint-community/eslint-utils@4.10.1(eslint@10.13.0)':
dependencies:
- eslint: 10.12.0
+ eslint: 10.13.0
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {}
@@ -2767,9 +2780,9 @@ snapshots:
dependencies:
'@types/json-schema': 7.0.15
- '@eslint/js@10.0.1(eslint@10.12.0)':
+ '@eslint/js@10.0.1(eslint@10.13.0)':
optionalDependencies:
- eslint: 10.12.0
+ eslint: 10.13.0
'@eslint/object-schema@3.0.5': {}
@@ -2796,122 +2809,122 @@ snapshots:
'@inquirer/ansi@2.0.9': {}
- '@inquirer/checkbox@5.2.6(@types/node@26.6.4)':
+ '@inquirer/checkbox@5.2.7(@types/node@26.6.5)':
dependencies:
'@inquirer/ansi': 2.0.9
- '@inquirer/core': 12.0.4(@types/node@26.6.4)
+ '@inquirer/core': 12.0.5(@types/node@26.6.5)
'@inquirer/figures': 2.0.9
- '@inquirer/type': 4.2.0(@types/node@26.6.4)
+ '@inquirer/type': 4.2.0(@types/node@26.6.5)
optionalDependencies:
- '@types/node': 26.6.4
+ '@types/node': 26.6.5
- '@inquirer/confirm@6.3.3(@types/node@26.6.4)':
+ '@inquirer/confirm@6.3.4(@types/node@26.6.5)':
dependencies:
- '@inquirer/core': 12.0.4(@types/node@26.6.4)
- '@inquirer/type': 4.2.0(@types/node@26.6.4)
+ '@inquirer/core': 12.0.5(@types/node@26.6.5)
+ '@inquirer/type': 4.2.0(@types/node@26.6.5)
optionalDependencies:
- '@types/node': 26.6.4
+ '@types/node': 26.6.5
- '@inquirer/core@12.0.4(@types/node@26.6.4)':
+ '@inquirer/core@12.0.5(@types/node@26.6.5)':
dependencies:
'@inquirer/ansi': 2.0.9
'@inquirer/figures': 2.0.9
- '@inquirer/type': 4.2.0(@types/node@26.6.4)
+ '@inquirer/type': 4.2.0(@types/node@26.6.5)
cli-width: 4.1.0
fast-wrap-ansi: 0.2.2
mute-stream: 3.0.0
signal-exit: 4.1.0
optionalDependencies:
- '@types/node': 26.6.4
+ '@types/node': 26.6.5
- '@inquirer/editor@5.3.4(@types/node@26.6.4)':
+ '@inquirer/editor@5.3.5(@types/node@26.6.5)':
dependencies:
- '@inquirer/core': 12.0.4(@types/node@26.6.4)
- '@inquirer/external-editor': 3.0.6(@types/node@26.6.4)
- '@inquirer/type': 4.2.0(@types/node@26.6.4)
+ '@inquirer/core': 12.0.5(@types/node@26.6.5)
+ '@inquirer/external-editor': 3.0.6(@types/node@26.6.5)
+ '@inquirer/type': 4.2.0(@types/node@26.6.5)
optionalDependencies:
- '@types/node': 26.6.4
+ '@types/node': 26.6.5
- '@inquirer/expand@5.1.6(@types/node@26.6.4)':
+ '@inquirer/expand@5.1.7(@types/node@26.6.5)':
dependencies:
- '@inquirer/core': 12.0.4(@types/node@26.6.4)
- '@inquirer/type': 4.2.0(@types/node@26.6.4)
+ '@inquirer/core': 12.0.5(@types/node@26.6.5)
+ '@inquirer/type': 4.2.0(@types/node@26.6.5)
optionalDependencies:
- '@types/node': 26.6.4
+ '@types/node': 26.6.5
- '@inquirer/external-editor@3.0.6(@types/node@26.6.4)':
+ '@inquirer/external-editor@3.0.6(@types/node@26.6.5)':
dependencies:
chardet: 2.2.0
iconv-lite: 0.7.3
optionalDependencies:
- '@types/node': 26.6.4
+ '@types/node': 26.6.5
'@inquirer/figures@2.0.9': {}
- '@inquirer/input@5.1.7(@types/node@26.6.4)':
+ '@inquirer/input@5.1.8(@types/node@26.6.5)':
dependencies:
- '@inquirer/core': 12.0.4(@types/node@26.6.4)
- '@inquirer/type': 4.2.0(@types/node@26.6.4)
+ '@inquirer/core': 12.0.5(@types/node@26.6.5)
+ '@inquirer/type': 4.2.0(@types/node@26.6.5)
optionalDependencies:
- '@types/node': 26.6.4
+ '@types/node': 26.6.5
- '@inquirer/number@4.2.4(@types/node@26.6.4)':
+ '@inquirer/number@4.2.5(@types/node@26.6.5)':
dependencies:
- '@inquirer/core': 12.0.4(@types/node@26.6.4)
- '@inquirer/type': 4.2.0(@types/node@26.6.4)
+ '@inquirer/core': 12.0.5(@types/node@26.6.5)
+ '@inquirer/type': 4.2.0(@types/node@26.6.5)
optionalDependencies:
- '@types/node': 26.6.4
+ '@types/node': 26.6.5
- '@inquirer/password@5.2.3(@types/node@26.6.4)':
+ '@inquirer/password@5.2.4(@types/node@26.6.5)':
dependencies:
'@inquirer/ansi': 2.0.9
- '@inquirer/core': 12.0.4(@types/node@26.6.4)
- '@inquirer/type': 4.2.0(@types/node@26.6.4)
+ '@inquirer/core': 12.0.5(@types/node@26.6.5)
+ '@inquirer/type': 4.2.0(@types/node@26.6.5)
optionalDependencies:
- '@types/node': 26.6.4
-
- '@inquirer/prompts@8.7.3(@types/node@26.6.4)':
- dependencies:
- '@inquirer/checkbox': 5.2.6(@types/node@26.6.4)
- '@inquirer/confirm': 6.3.3(@types/node@26.6.4)
- '@inquirer/editor': 5.3.4(@types/node@26.6.4)
- '@inquirer/expand': 5.1.6(@types/node@26.6.4)
- '@inquirer/input': 5.1.7(@types/node@26.6.4)
- '@inquirer/number': 4.2.4(@types/node@26.6.4)
- '@inquirer/password': 5.2.3(@types/node@26.6.4)
- '@inquirer/rawlist': 5.3.6(@types/node@26.6.4)
- '@inquirer/search': 4.3.4(@types/node@26.6.4)
- '@inquirer/select': 5.2.6(@types/node@26.6.4)
+ '@types/node': 26.6.5
+
+ '@inquirer/prompts@8.7.4(@types/node@26.6.5)':
+ dependencies:
+ '@inquirer/checkbox': 5.2.7(@types/node@26.6.5)
+ '@inquirer/confirm': 6.3.4(@types/node@26.6.5)
+ '@inquirer/editor': 5.3.5(@types/node@26.6.5)
+ '@inquirer/expand': 5.1.7(@types/node@26.6.5)
+ '@inquirer/input': 5.1.8(@types/node@26.6.5)
+ '@inquirer/number': 4.2.5(@types/node@26.6.5)
+ '@inquirer/password': 5.2.4(@types/node@26.6.5)
+ '@inquirer/rawlist': 5.3.7(@types/node@26.6.5)
+ '@inquirer/search': 4.3.5(@types/node@26.6.5)
+ '@inquirer/select': 5.2.7(@types/node@26.6.5)
optionalDependencies:
- '@types/node': 26.6.4
+ '@types/node': 26.6.5
- '@inquirer/rawlist@5.3.6(@types/node@26.6.4)':
+ '@inquirer/rawlist@5.3.7(@types/node@26.6.5)':
dependencies:
- '@inquirer/core': 12.0.4(@types/node@26.6.4)
- '@inquirer/type': 4.2.0(@types/node@26.6.4)
+ '@inquirer/core': 12.0.5(@types/node@26.6.5)
+ '@inquirer/type': 4.2.0(@types/node@26.6.5)
optionalDependencies:
- '@types/node': 26.6.4
+ '@types/node': 26.6.5
- '@inquirer/search@4.3.4(@types/node@26.6.4)':
+ '@inquirer/search@4.3.5(@types/node@26.6.5)':
dependencies:
- '@inquirer/core': 12.0.4(@types/node@26.6.4)
+ '@inquirer/core': 12.0.5(@types/node@26.6.5)
'@inquirer/figures': 2.0.9
- '@inquirer/type': 4.2.0(@types/node@26.6.4)
+ '@inquirer/type': 4.2.0(@types/node@26.6.5)
optionalDependencies:
- '@types/node': 26.6.4
+ '@types/node': 26.6.5
- '@inquirer/select@5.2.6(@types/node@26.6.4)':
+ '@inquirer/select@5.2.7(@types/node@26.6.5)':
dependencies:
'@inquirer/ansi': 2.0.9
- '@inquirer/core': 12.0.4(@types/node@26.6.4)
+ '@inquirer/core': 12.0.5(@types/node@26.6.5)
'@inquirer/figures': 2.0.9
- '@inquirer/type': 4.2.0(@types/node@26.6.4)
+ '@inquirer/type': 4.2.0(@types/node@26.6.5)
optionalDependencies:
- '@types/node': 26.6.4
+ '@types/node': 26.6.5
- '@inquirer/type@4.2.0(@types/node@26.6.4)':
+ '@inquirer/type@4.2.0(@types/node@26.6.5)':
optionalDependencies:
- '@types/node': 26.6.4
+ '@types/node': 26.6.5
'@keyv/bigmap@1.3.1(keyv@5.6.0)':
dependencies:
@@ -2921,16 +2934,16 @@ snapshots:
'@keyv/serialize@1.1.1': {}
- '@napi-rs/cli@3.10.8(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@26.6.4)':
+ '@napi-rs/cli@3.10.8(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@26.6.5)':
dependencies:
- '@inquirer/prompts': 8.7.3(@types/node@26.6.4)
+ '@inquirer/prompts': 8.7.4(@types/node@26.6.5)
'@napi-rs/cross-toolchain': 1.0.3
'@napi-rs/wasm-tools': 1.1.0
'@octokit/rest': 22.0.1
clipanion: 4.0.0-rc.4(typanion@3.14.0)
colorette: 2.0.20
es-toolkit: 1.52.0
- js-yaml: 5.4.3
+ js-yaml: 5.5.0
obug: 3.0.0
semver: 7.8.5
typanion: 3.14.0
@@ -3341,78 +3354,78 @@ snapshots:
'@types/json5@0.0.29': {}
- '@types/node@26.6.4':
+ '@types/node@26.6.5':
dependencies:
undici-types: 8.9.0
'@types/vscode@1.61.0': {}
- '@typescript-eslint/eslint-plugin@8.71.1(@typescript-eslint/parser@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0))(@typescript/typescript6@6.0.2)(eslint@10.12.0)':
+ '@typescript-eslint/eslint-plugin@8.72.0(@typescript-eslint/parser@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0))(@typescript/typescript6@6.0.2)(eslint@10.13.0)':
dependencies:
'@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0)
- '@typescript-eslint/scope-manager': 8.71.1
- '@typescript-eslint/type-utils': 8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0)
- '@typescript-eslint/utils': 8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0)
- '@typescript-eslint/visitor-keys': 8.71.1
- eslint: 10.12.0
- ignore: 7.0.12
+ '@typescript-eslint/parser': 8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0)
+ '@typescript-eslint/scope-manager': 8.72.0
+ '@typescript-eslint/type-utils': 8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0)
+ '@typescript-eslint/utils': 8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0)
+ '@typescript-eslint/visitor-keys': 8.72.0
+ eslint: 10.13.0
+ ignore: 7.0.13
natural-compare: 1.4.0
ts-api-utils: 2.5.0(@typescript/typescript6@6.0.2)
typescript: '@typescript/typescript6@6.0.2'
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0)':
+ '@typescript-eslint/parser@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0)':
dependencies:
- '@typescript-eslint/scope-manager': 8.71.1
- '@typescript-eslint/types': 8.71.1
- '@typescript-eslint/typescript-estree': 8.71.1(@typescript/typescript6@6.0.2)
- '@typescript-eslint/visitor-keys': 8.71.1
+ '@typescript-eslint/scope-manager': 8.72.0
+ '@typescript-eslint/types': 8.72.0
+ '@typescript-eslint/typescript-estree': 8.72.0(@typescript/typescript6@6.0.2)
+ '@typescript-eslint/visitor-keys': 8.72.0
debug: 4.4.3
- eslint: 10.12.0
+ eslint: 10.13.0
typescript: '@typescript/typescript6@6.0.2'
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/project-service@8.71.1(@typescript/typescript6@6.0.2)':
+ '@typescript-eslint/project-service@8.72.0(@typescript/typescript6@6.0.2)':
dependencies:
- '@typescript-eslint/tsconfig-utils': 8.71.1(@typescript/typescript6@6.0.2)
- '@typescript-eslint/types': 8.71.1
+ '@typescript-eslint/tsconfig-utils': 8.72.0(@typescript/typescript6@6.0.2)
+ '@typescript-eslint/types': 8.72.0
debug: 4.4.3
typescript: '@typescript/typescript6@6.0.2'
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/scope-manager@8.71.1':
+ '@typescript-eslint/scope-manager@8.72.0':
dependencies:
- '@typescript-eslint/types': 8.71.1
- '@typescript-eslint/visitor-keys': 8.71.1
+ '@typescript-eslint/types': 8.72.0
+ '@typescript-eslint/visitor-keys': 8.72.0
- '@typescript-eslint/tsconfig-utils@8.71.1(@typescript/typescript6@6.0.2)':
+ '@typescript-eslint/tsconfig-utils@8.72.0(@typescript/typescript6@6.0.2)':
dependencies:
typescript: '@typescript/typescript6@6.0.2'
- '@typescript-eslint/type-utils@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0)':
+ '@typescript-eslint/type-utils@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0)':
dependencies:
- '@typescript-eslint/types': 8.71.1
- '@typescript-eslint/typescript-estree': 8.71.1(@typescript/typescript6@6.0.2)
- '@typescript-eslint/utils': 8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0)
+ '@typescript-eslint/types': 8.72.0
+ '@typescript-eslint/typescript-estree': 8.72.0(@typescript/typescript6@6.0.2)
+ '@typescript-eslint/utils': 8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0)
debug: 4.4.3
- eslint: 10.12.0
+ eslint: 10.13.0
ts-api-utils: 2.5.0(@typescript/typescript6@6.0.2)
typescript: '@typescript/typescript6@6.0.2'
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/types@8.71.1': {}
+ '@typescript-eslint/types@8.72.0': {}
- '@typescript-eslint/typescript-estree@8.71.1(@typescript/typescript6@6.0.2)':
+ '@typescript-eslint/typescript-estree@8.72.0(@typescript/typescript6@6.0.2)':
dependencies:
- '@typescript-eslint/project-service': 8.71.1(@typescript/typescript6@6.0.2)
- '@typescript-eslint/tsconfig-utils': 8.71.1(@typescript/typescript6@6.0.2)
- '@typescript-eslint/types': 8.71.1
- '@typescript-eslint/visitor-keys': 8.71.1
+ '@typescript-eslint/project-service': 8.72.0(@typescript/typescript6@6.0.2)
+ '@typescript-eslint/tsconfig-utils': 8.72.0(@typescript/typescript6@6.0.2)
+ '@typescript-eslint/types': 8.72.0
+ '@typescript-eslint/visitor-keys': 8.72.0
debug: 4.4.3
minimatch: 10.2.6
semver: 7.8.5
@@ -3422,20 +3435,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0)':
+ '@typescript-eslint/utils@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0)':
dependencies:
- '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0)
- '@typescript-eslint/scope-manager': 8.71.1
- '@typescript-eslint/types': 8.71.1
- '@typescript-eslint/typescript-estree': 8.71.1(@typescript/typescript6@6.0.2)
- eslint: 10.12.0
+ '@eslint-community/eslint-utils': 4.10.1(eslint@10.13.0)
+ '@typescript-eslint/scope-manager': 8.72.0
+ '@typescript-eslint/types': 8.72.0
+ '@typescript-eslint/typescript-estree': 8.72.0(@typescript/typescript6@6.0.2)
+ eslint: 10.13.0
typescript: '@typescript/typescript6@6.0.2'
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/visitor-keys@8.71.1':
+ '@typescript-eslint/visitor-keys@8.72.0':
dependencies:
- '@typescript-eslint/types': 8.71.1
+ '@typescript-eslint/types': 8.72.0
eslint-visitor-keys: 5.0.1
'@typescript/typescript-aix-ppc64@7.0.2':
@@ -3551,7 +3564,7 @@ snapshots:
'@vscode/vsce@4.0.0':
dependencies:
- '@azure/identity': 4.13.3
+ '@azure/identity': 4.14.0
'@napi-rs/keyring': 1.3.0
'@secretlint/core': 10.2.2
'@secretlint/secretlint-rule-no-dotenv': 10.2.2
@@ -3564,7 +3577,7 @@ snapshots:
commander: 12.1.0
hosted-git-info: 4.1.0
jsonc-parser: 3.3.1
- marked: 18.1.0
+ marked: 18.1.1
mime: 1.6.0
minimatch: 10.2.6
parse5: 8.0.1
@@ -3597,7 +3610,7 @@ snapshots:
ansi-styles@7.0.0: {}
- argparse@2.0.1: {}
+ argparse@3.0.3: {}
array-buffer-byte-length@1.0.2:
dependencies:
@@ -3941,9 +3954,9 @@ snapshots:
escape-string-regexp@4.0.0: {}
- eslint-config-prettier@10.1.8(eslint@10.12.0):
+ eslint-config-prettier@10.1.8(eslint@10.13.0):
dependencies:
- eslint: 10.12.0
+ eslint: 10.13.0
eslint-import-resolver-node@0.3.10:
dependencies:
@@ -3953,23 +3966,23 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.14.0(@typescript-eslint/parser@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0))(eslint-import-resolver-node@0.3.10)(eslint@10.12.0):
+ eslint-module-utils@2.14.0(@typescript-eslint/parser@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0))(eslint-import-resolver-node@0.3.10)(eslint@10.13.0):
dependencies:
debug: 3.2.7
optionalDependencies:
- '@typescript-eslint/parser': 8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0)
- eslint: 10.12.0
+ '@typescript-eslint/parser': 8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0)
+ eslint: 10.13.0
eslint-import-resolver-node: 0.3.10
transitivePeerDependencies:
- supports-color
- eslint-plugin-es@3.0.1(eslint@10.12.0):
+ eslint-plugin-es@3.0.1(eslint@10.13.0):
dependencies:
- eslint: 10.12.0
+ eslint: 10.13.0
eslint-utils: 2.1.0
regexpp: 3.2.0
- eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0))(eslint@10.12.0):
+ eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0))(eslint@10.13.0):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.2.0
@@ -3978,9 +3991,9 @@ snapshots:
array.prototype.flatmap: 1.3.3
debug: 3.2.7
doctrine: 2.1.0
- eslint: 10.12.0
+ eslint: 10.13.0
eslint-import-resolver-node: 0.3.10
- eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0))(eslint-import-resolver-node@0.3.10)(eslint@10.12.0)
+ eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0))(eslint-import-resolver-node@0.3.10)(eslint@10.13.0)
hasown: 2.0.4
is-core-module: 2.17.0
is-glob: 4.0.3
@@ -3992,30 +4005,30 @@ snapshots:
string.prototype.trimend: 1.0.10
tsconfig-paths: 3.15.0
optionalDependencies:
- '@typescript-eslint/parser': 8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0)
+ '@typescript-eslint/parser': 8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0)
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-node@11.1.0(eslint@10.12.0):
+ eslint-plugin-node@11.1.0(eslint@10.13.0):
dependencies:
- eslint: 10.12.0
- eslint-plugin-es: 3.0.1(eslint@10.12.0)
+ eslint: 10.13.0
+ eslint-plugin-es: 3.0.1(eslint@10.13.0)
eslint-utils: 2.1.0
ignore: 5.3.2
minimatch: 3.1.5
resolve: 1.22.13
semver: 6.3.1
- eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@10.12.0))(eslint@10.12.0)(prettier@3.9.9):
+ eslint-plugin-prettier@5.5.7(eslint-config-prettier@10.1.8(eslint@10.13.0))(eslint@10.13.0)(prettier@3.9.10):
dependencies:
- eslint: 10.12.0
- prettier: 3.9.9
+ eslint: 10.13.0
+ prettier: 3.9.10
prettier-linter-helpers: 1.0.1
- synckit: 0.11.13
+ synckit: 0.12.3
optionalDependencies:
- eslint-config-prettier: 10.1.8(eslint@10.12.0)
+ eslint-config-prettier: 10.1.8(eslint@10.13.0)
eslint-scope@9.1.2:
dependencies:
@@ -4034,9 +4047,9 @@ snapshots:
eslint-visitor-keys@5.0.1: {}
- eslint@10.12.0:
+ eslint@10.13.0:
dependencies:
- '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0)
+ '@eslint-community/eslint-utils': 4.10.1(eslint@10.13.0)
'@eslint-community/regexpp': 4.12.2
'@eslint/config-array': 0.23.5
'@eslint/config-helpers': 0.7.0
@@ -4237,7 +4250,7 @@ snapshots:
ignore@5.3.2: {}
- ignore@7.0.12: {}
+ ignore@7.0.13: {}
imurmurhash@0.1.4: {}
@@ -4311,6 +4324,8 @@ snapshots:
dependencies:
is-extglob: 2.1.1
+ is-in-ssh@1.0.0: {}
+
is-inside-container@1.0.0:
dependencies:
is-docker: 3.0.0
@@ -4379,9 +4394,9 @@ snapshots:
editions: 6.22.0
textextensions: 6.11.0
- js-yaml@5.4.3:
+ js-yaml@5.5.0:
dependencies:
- argparse: 2.0.1
+ argparse: 3.0.3
json-parse-even-better-errors@6.0.0: {}
@@ -4452,7 +4467,7 @@ snapshots:
dependencies:
yallist: 4.0.0
- marked@18.1.0: {}
+ marked@18.1.1: {}
math-intrinsics@1.1.0: {}
@@ -4543,12 +4558,14 @@ snapshots:
obug@3.0.0: {}
- open@10.2.0:
+ open@11.0.4:
dependencies:
default-browser: 5.5.1
define-lazy-prop: 3.0.0
+ is-in-ssh: 1.0.0
is-inside-container: 1.0.0
- wsl-utils: 0.1.0
+ powershell-utils: 0.2.1
+ wsl-utils: 1.0.1
optionator@0.9.4:
dependencies:
@@ -4592,13 +4609,15 @@ snapshots:
possible-typed-array-names@1.1.0: {}
+ powershell-utils@0.2.1: {}
+
prelude-ls@1.2.1: {}
prettier-linter-helpers@1.0.1:
dependencies:
fast-diff: 1.3.0
- prettier@3.9.9: {}
+ prettier@3.9.10: {}
proper-lockfile@4.1.2:
dependencies:
@@ -4612,7 +4631,7 @@ snapshots:
dependencies:
hookified: 2.2.0
- qs@6.16.0:
+ qs@6.16.1:
dependencies:
es-define-property: 1.0.1
side-channel: 1.1.1
@@ -4796,8 +4815,9 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
- synckit@0.11.13:
+ synckit@0.12.3:
dependencies:
+ '@dual-bundle/import-meta-resolve': 4.2.1
'@pkgr/core': 0.3.6
textextensions@6.11.0:
@@ -4864,17 +4884,17 @@ snapshots:
typed-rest-client@1.8.11:
dependencies:
- qs: 6.16.0
+ qs: 6.16.1
tunnel: 0.0.6
underscore: 1.13.8
- typescript-eslint@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0):
+ typescript-eslint@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0):
dependencies:
- '@typescript-eslint/eslint-plugin': 8.71.1(@typescript-eslint/parser@8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0))(@typescript/typescript6@6.0.2)(eslint@10.12.0)
- '@typescript-eslint/parser': 8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0)
- '@typescript-eslint/typescript-estree': 8.71.1(@typescript/typescript6@6.0.2)
- '@typescript-eslint/utils': 8.71.1(@typescript/typescript6@6.0.2)(eslint@10.12.0)
- eslint: 10.12.0
+ '@typescript-eslint/eslint-plugin': 8.72.0(@typescript-eslint/parser@8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0))(@typescript/typescript6@6.0.2)(eslint@10.13.0)
+ '@typescript-eslint/parser': 8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0)
+ '@typescript-eslint/typescript-estree': 8.72.0(@typescript/typescript6@6.0.2)
+ '@typescript-eslint/utils': 8.72.0(@typescript/typescript6@6.0.2)(eslint@10.13.0)
+ eslint: 10.13.0
typescript: '@typescript/typescript6@6.0.2'
transitivePeerDependencies:
- supports-color
@@ -4976,9 +4996,10 @@ snapshots:
word-wrap@1.2.5: {}
- wsl-utils@0.1.0:
+ wsl-utils@1.0.1:
dependencies:
is-wsl: 3.1.1
+ powershell-utils: 0.2.1
xml2js@0.5.0:
dependencies:
diff --git a/server/Cargo.toml b/server/Cargo.toml
index 92ec62ec..ed712c77 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -62,7 +62,7 @@ imara-diff = { version = "0.2", features = [] }
indoc = "2.0.5"
log.workspace = true
log4rs = "1.3"
-lru = "0.18.5"
+lru = "0.19"
markup5ever_rcdom = "0.39"
mime = "0.3.17"
mime_guess = "2.0.5"
@@ -105,7 +105,7 @@ chrono = "0.4"
futures = "0.3.31"
predicates = "3.1.2"
pretty_assertions = "1.4.1"
-thirtyfour = "0.37"
+thirtyfour = "0.38"
tokio-tungstenite = "0.30"
# Lints
diff --git a/server/src/ide/vscode/tests.rs b/server/src/ide/vscode/tests.rs
index a7063a1c..728d05dd 100644
--- a/server/src/ide/vscode/tests.rs
+++ b/server/src/ide/vscode/tests.rs
@@ -39,8 +39,7 @@ use pretty_assertions::assert_eq;
use tokio::{
io::{AsyncRead, AsyncWrite},
net::TcpStream,
- select,
- time::sleep,
+ time::{sleep, timeout},
};
use tokio_tungstenite::{
MaybeTlsStream, WebSocketStream, connect_async,
@@ -70,6 +69,7 @@ use test_utils::{
// Globals
// -------
+//
// Run a single webserver for all tests.
static WEBSERVER_HANDLE: LazyLock>> = LazyLock::new(|| {
thread::spawn(|| {
@@ -107,23 +107,29 @@ async fn send_message(
/// Read a message from a websocket.
async fn read_message(
+ // The websocket to read from.
ws_stream: &mut WebSocketStream,
+ // The message read.
) -> EditorMessage {
- let now = SystemTime::now();
- let msg_txt = loop {
- let msg = select! {
- data = ws_stream.next() => data.unwrap().unwrap(),
- () = sleep(Duration::from_secs(6).checked_sub(now.elapsed().unwrap()).unwrap()) => panic!("Timeout waiting for message")
- };
- match msg {
- Message::Close(_) => panic!("Unexpected close message."),
- Message::Ping(_) => ws_stream.send(Message::Pong(vec![].into())).await.unwrap(),
- Message::Pong(_) => panic!("Unexpected pong message."),
- Message::Text(txt) => break txt,
- Message::Binary(_) => panic!("Unexpected binary message."),
- Message::Frame(_) => panic!("Unexpected frame message."),
+ let msg_txt = timeout(Duration::from_secs(6), async {
+ loop {
+ let msg = ws_stream
+ .next()
+ .await
+ .expect("The websocket ended without a close message.")
+ .unwrap_or_else(|err| panic!("Unable to read from the websocket: {err}"));
+ match msg {
+ Message::Close(_) => panic!("Unexpected close message."),
+ Message::Ping(data) => ws_stream.send(Message::Pong(data)).await.unwrap(),
+ Message::Pong(_) => panic!("Unexpected pong message."),
+ Message::Text(txt) => break txt,
+ Message::Binary(_) => panic!("Unexpected binary message."),
+ Message::Frame(_) => panic!("Unexpected frame message."),
+ }
}
- };
+ })
+ .await
+ .expect("Timeout waiting for message");
serde_json::from_str(&msg_txt)
.unwrap_or_else(|_| panic!("Unable to convert '{msg_txt}' to JSON."))
}
@@ -225,8 +231,8 @@ async fn prep_test_core(
(temp_dir, test_dir, ws_ide, ws_client)
}
-/// This calls `prep_test_core` with the current function name. It must be a macro,
-/// so that it's called with the test function's name; calling it inside
+/// This calls `prep_test_core` with the current function name. It must be a
+/// macro, so that it's called with the test function's name; calling it inside
/// `prep_test_core` would give the wrong name.
macro_rules! prep_test {
($connection_id: ident) => {{
@@ -1410,7 +1416,7 @@ async fn test_vscode_ide_websocket5() {
ws_client.close(None).await.unwrap();
loop {
match ws_ide.next().await.unwrap().unwrap() {
- Message::Ping(_) => ws_ide.send(Message::Pong(vec![].into())).await.unwrap(),
+ Message::Ping(data) => ws_ide.send(Message::Pong(data)).await.unwrap(),
Message::Close(_) => break,
_ => panic!("Unexpected message."),
}
@@ -1431,7 +1437,7 @@ async fn test_vscode_ide_websocket9() {
ws_ide.close(None).await.unwrap();
loop {
match ws_client.next().await.unwrap().unwrap() {
- Message::Ping(_) => ws_client.send(Message::Pong(vec![].into())).await.unwrap(),
+ Message::Ping(data) => ws_client.send(Message::Pong(data)).await.unwrap(),
Message::Close(_) => break,
_ => panic!("Unexpected message."),
}
@@ -1575,8 +1581,8 @@ async fn test_vscode_ide_websocket10() {
//
// The consumer's initial scan of the project may reach the TOC on either
// side of this write; either way, only one commit defines `foo`, outdating
- // the current file once. A scan before the write finds no ids in the TOC.
- // A scan after it commits the new TOC, which makes the watcher's report a
+ // the current file once. A scan before the write finds no ids in the TOC. A
+ // scan after it commits the new TOC, which makes the watcher's report a
// no-op: the TOC's metadata then matches the cache's, so the consumer skips
// it.
fs::write(test_dir.join("toc.md"), "Foo
\n").unwrap();
diff --git a/server/src/processing.rs b/server/src/processing.rs
index bf23dbcc..50ac0564 100644
--- a/server/src/processing.rs
+++ b/server/src/processing.rs
@@ -54,7 +54,7 @@ use dprint_plugin_markdown::{
format_text,
};
use htmd::{
- HtmlToMarkdown,
+ Conversion, HtmlToMarkdown,
options::{BrStyle, LinkStyle, TranslationMode},
};
use html5ever::{
@@ -486,7 +486,8 @@ pub fn codechat_for_web_to_source(
return Err(CodechatForWebToSourceError::DocBlocksNotAllowed);
}
// Translate the HTML document to Markdown.
- let converter = HtmlToMarkdownWrapped::new();
+ let html_to_markdown = new_html_to_markdown();
+ let converter = HtmlToMarkdownWrapped::new(&html_to_markdown);
let tree = html_to_dom(&code_mirror.doc, None)?;
dehydrating_walk_node(&tree);
return converter
@@ -581,9 +582,27 @@ fn code_mirror_to_code_doc_blocks(code_mirror: &CodeMirror) -> Vec
code_doc_block_arr
}
-/// This converts HTML to Markdown then word wraps the result.
-struct HtmlToMarkdownWrapped {
- html_to_markdown: HtmlToMarkdown,
+/// Build the HTML to Markdown converter which an [`HtmlToMarkdownWrapped`]
+/// borrows. A `Conversion` holds a reference to its converter, so the converter
+/// must outlive the wrapper rather than live inside it.
+fn new_html_to_markdown() -> HtmlToMarkdown {
+ // Most of the options don't need to be specified here, since the line
+ // wrapper will override them.
+ HtmlToMarkdown::builder()
+ .options(htmd::options::Options {
+ link_style: LinkStyle::Inlined,
+ translation_mode: TranslationMode::Faithful,
+ // Note that this is ignored in Faithful mode.
+ br_style: BrStyle::Backslash,
+ ..Default::default()
+ })
+ .build()
+}
+
+/// This converts a document, supplied as one or more DOM trees, from HTML to
+/// Markdown then word wraps the result.
+struct HtmlToMarkdownWrapped<'a> {
+ html_to_markdown_conversion: Conversion<'a>,
word_wrap_config: Configuration,
}
@@ -597,20 +616,13 @@ pub enum HtmlToMarkdownWrappedError {
LineWidthOverflow(#[from] std::num::TryFromIntError),
}
-impl HtmlToMarkdownWrapped {
- fn new() -> Self {
+impl<'a> HtmlToMarkdownWrapped<'a> {
+ fn new(
+ // The converter, typically from `new_html_to_markdown`.
+ html_to_markdown: &'a HtmlToMarkdown,
+ ) -> Self {
HtmlToMarkdownWrapped {
- // Most of the options don't need to be specified here, since the
- // line wrapper will override them.
- html_to_markdown: HtmlToMarkdown::builder()
- .options(htmd::options::Options {
- link_style: LinkStyle::Inlined,
- translation_mode: TranslationMode::Faithful,
- // Note that this is ignored in Faithful mode.
- br_style: BrStyle::Backslash,
- ..Default::default()
- })
- .build(),
+ html_to_markdown_conversion: html_to_markdown.start_conversion(),
// TODO: numbered list formatting should be improved in the dprint
// library.
word_wrap_config: ConfigurationBuilder::new()
@@ -632,7 +644,7 @@ impl HtmlToMarkdownWrapped {
/// Convert one item in a stream of HTML to markdown. The HTML must be start
/// at the root, not continue a previous incomplete section of the DOM.
fn next(&self, tree: &Rc) -> Result {
- let converted = self.html_to_markdown.tree_to_markdown(tree);
+ let converted = self.html_to_markdown_conversion.tree_to_markdown(tree);
Ok(
format_text(&converted, &self.word_wrap_config, |_, _, _| Ok(None))?
// A return value of `None` means the text was unchanged or
@@ -643,16 +655,28 @@ impl HtmlToMarkdownWrapped {
)
}
- fn last(&self) -> Result {
- let converted = self.html_to_markdown.finalize_conversion();
+ /// End the document, returning the Markdown placed at its end (such as
+ /// link reference definitions). This consumes the wrapper, since finishing
+ /// a `Conversion` consumes it.
+ fn last(
+ self,
+ // The word-wrapped Markdown which ends the document.
+ ) -> Result {
+ let converted = self.html_to_markdown_conversion.finish();
Ok(
format_text(&converted, &self.word_wrap_config, |_, _, _| Ok(None))?
.unwrap_or(converted),
)
}
- /// Convert HTML to markdown.
- fn convert(&self, tree: &Rc) -> Result {
+ /// Convert a document consisting of a single DOM tree from HTML to
+ /// markdown.
+ fn convert(
+ self,
+ // The DOM tree holding the entire document.
+ tree: &Rc,
+ // The document as word-wrapped Markdown.
+ ) -> Result {
let mut converted = self.next(tree)?;
converted.push_str(&self.last()?);
Ok(converted)
@@ -670,7 +694,8 @@ pub fn doc_block_html_to_markdown(
// containing only one doc block.
dom_location: Option<&(Vec, usize)>,
) -> Result, HtmlToMarkdownWrappedError> {
- let mut converter = HtmlToMarkdownWrapped::new();
+ let html_to_markdown = new_html_to_markdown();
+ let mut converter = HtmlToMarkdownWrapped::new(&html_to_markdown);
let mut last_doc_block_index = None;
// Only perform marker insertions to a length 1 vec.
assert!(dom_location.is_none() || code_doc_block_vec.len() == 1);
diff --git a/server/src/processing/cache-spec.md b/server/src/processing/cache-spec.md
index a0dc014a..f9be25c3 100644
--- a/server/src/processing/cache-spec.md
+++ b/server/src/processing/cache-spec.md
@@ -17,15 +17,14 @@ do not.
The cache reflects data read directly from disk/IDE; content edited in the
Client does not update the cache until it's written to disk/IDE, at which point
-the cache must re-process the file. Since the Client is designed around
-an autosave principle which updates disk/IDE regularly, there's little gap
-between the two.
+the cache must re-process the file. Since the Client is designed around an
+autosave principle which updates disk/IDE regularly, there's little gap between
+the two.
The cache hydrates data to the Client; the dehydration routes are responsible
for removing all cache hydration artifacts.
-Cross references
-----------------
+## Cross references
A `` is a cross reference to a `Target` or a gather
element. The `ref` attribute specifies the destination as an id; the cache then
@@ -36,9 +35,9 @@ is only present in the Client.
Details:
-* This element allows no attribute except for `ref`. Since an `` is
- never a `Target`, an `id` attribute on it -- including `id="*"` -- would
- mislead, so it replaces the contents with an error message.
+* This element allows no attribute except for `ref`. Since an `` is never
+ a `Target`, an `id` attribute on it -- including `id="*"` -- would mislead, so
+ it replaces the contents with an error message.
* The inner HTML is always taken before cache hydration, to prevent circular
dependencies (see also ):
@@ -63,8 +62,7 @@ Details:
* Cross references to fragments are not allowed; this would create circular
dependencies (see ).
-Fragments and gather elements
------------------------------
+## Fragments and gather elements
A gather element such as `Bazzy
things
` is a `Target` with the `data-gather` attribute. It becomes a list
@@ -93,18 +91,18 @@ Details:
contents will be replaced by an error message.
* Fragments store an HTML rendering of the code and doc blocks they contain,
excluding the content produced by hydrating the `` tags, to avoid
- duplication and circular dependencies. Cross-references in this rendering
- are hydrated in the fragment's file, so their links lead from there; a
- gather element re-hydrates each which hydrated to a link so that it leads
- from the gather element's file instead. An error message reads the same
- anywhere, so it's kept as is. See layer 4 under
- : this exclusion is what keeps a gather
- element and the fragments it lists from outdating each other forever. The HTML
- rendering of a fragment reproduces the layout of the source it came from: each
- doc block includes its indent, each line of a code block or the first line of
- a doc block is preceded by that line's number, and the two are aligned -- a
- doc block and a line of code indented equally in the source begin in the same
- column, with the line numbers in a gutter of their own to the left of both.
+ duplication and circular dependencies. Cross-references in this rendering are
+ hydrated in the fragment's file, so their links lead from there; a gather
+ element re-hydrates each which hydrated to a link so that it leads from the
+ gather element's file instead. An error message reads the same anywhere, so
+ it's kept as is. See layer 4 under : this
+ exclusion is what keeps a gather element and the fragments it lists from
+ outdating each other forever. The HTML rendering of a fragment reproduces the
+ layout of the source it came from: each doc block includes its indent, each
+ line of a code block or the first line of a doc block is preceded by that
+ line's number, and the two are aligned -- a doc block and a line of code
+ indented equally in the source begin in the same column, with the line numbers
+ in a gutter of their own to the left of both.
* The backlinks a `` hydrates to are derived from the gather elements
which list it: for each such element, its containing file, its `id`, and its
inner HTML (the link text). A fragment's rendered output therefore depends on
@@ -199,9 +197,10 @@ lower-numbered layers:
fragment it lists would outdate one another forever.
5. Gather-list hydration depends on layers 1 and 3. It also re-hydrates each
`` within a fragment's content (layer 2) which hydrated to a link, so
- that it links from the gathering file. That needs no dependency of its own: everything about a target which
- this reads is embedded in the fragment's content, so any change to it also
- changes that content, which outdates the gathering file.
+ that it links from the gathering file. That needs no dependency of its own:
+ everything about a target which this reads is embedded in the fragment's
+ content, so any change to it also changes that content, which outdates the
+ gathering file.
Only layers 1 and 3 are cached, and layer 3 depends only on layer 1 through
layer 2. So a change travels at most two steps (C → B → A), which matches the
@@ -228,8 +227,8 @@ differences is:
A gather element's `gather_ids` are absent from this list because no dependent
reads them: a cross-reference to a gather element renders only its inner HTML.
-The fragments a gather element lists do depend on them, through the reverse
-edge described below.
+The fragments a gather element lists do depend on them, through the reverse edge
+described below.
This also makes indirection (gather A includes fragment B, whose content
cross-references target C) work without extra machinery: a change to C outdates
@@ -394,11 +393,10 @@ timestamp tick could leave this metadata unchanged (Git's "racy clean" problem).
* If the file exists and doesn't match its cached stamp or if the file's status
is `Outdated`:
- * If the file is the TOC and doesn't match its cached stamp, send
- `TocChanged` to all connections once the processing below is done, even if
- it ignores the file: each connection's sidebar renders the TOC from the
- text it last loaded, which it must therefore re-load (see `Connections`
- below).
+ * If the file is the TOC and doesn't match its cached stamp, send `TocChanged`
+ to all connections once the processing below is done, even if it ignores the
+ file: each connection's sidebar renders the TOC from the text it last
+ loaded, which it must therefore re-load (see `Connections` below).
* If the file is the current file for any of the cache's connections, ignore
it. This is handled by the standard IDE to Client translation mechanism.
@@ -476,7 +474,27 @@ Overall shutdown sequence:
4. The consumer thread exits when either its consumer rx channel closes or the
weak pointer fails to upgrade.
-TODO:
+## Links in hydrated content
+
+Some content, such as math, cross-references, fragments, and gather lists (see
+ and ), sits
+inside an element marked `contenteditable=false`. TinyMCE's link plugin offers
+no context menu items for non-editable content, so a right click (mouse) or long
+press (touch) on one of these links is ignored. Therefore, when the user
+performs either of these actions, the Client must show the TinyMCE context menu,
+whose only item, "Open link", follows the link as a Ctrl+click does. A right
+click or long press on non-editable content beside its links offers no "Open
+link". With a non-editable element selected, the keyboard's context menu
+shortcut (the Menu key, or Shift+F10) shows the same menu, which follows the
+element's first link, as Alt+Enter does. The Client also follows these links in
+the other ways it follows any link (see `Link navigation` in
+[implementation.md](../../../docs/implementation.md)):
+
+* Ctrl+click (Cmd+click on macOS) follows the clicked link.
+* Alt+Enter, with the non-editable element selected, follows its first link.
+
+TODO
+----
* `SaveOnScan` and search functionality are not implemented yet. This is future
work.
diff --git a/server/src/processing/tests.rs b/server/src/processing/tests.rs
index 552094c0..35996d2f 100644
--- a/server/src/processing/tests.rs
+++ b/server/src/processing/tests.rs
@@ -53,7 +53,8 @@ use crate::{
cache::{Cache, FileStatus},
code_doc_block_vec_to_source, code_mirror_to_code_doc_blocks, codechat_for_web_to_source,
dehydrating_walk_node, diff_code_mirror_doc_blocks, diff_str, doc_block_html_to_markdown,
- html_to_dom, hydrate_html, is_css_identifier, markdown_to_html, source_to_codechat_for_web,
+ html_to_dom, hydrate_html, is_css_identifier, markdown_to_html, new_html_to_markdown,
+ source_to_codechat_for_web,
},
};
use test_utils::{cast, prep_test_dir, test_utils::stringit};
@@ -2292,9 +2293,10 @@ fn dehydrate_html(html: &str) -> io::Result> {
#[test]
fn test_dehydrate_html_1() {
- let converter = HtmlToMarkdownWrapped::new();
+ // Each document needs its own conversion, since `convert` consumes it.
+ let html_to_markdown = new_html_to_markdown();
assert_eq!(
- converter
+ HtmlToMarkdownWrapped::new(&html_to_markdown)
.convert(
&dehydrate_html(indoc!(
"
@@ -2317,7 +2319,7 @@ fn test_dehydrate_html_1() {
);
assert_eq!(
- converter
+ HtmlToMarkdownWrapped::new(&html_to_markdown)
.convert(
&dehydrate_html(indoc!(
"
@@ -2342,7 +2344,7 @@ fn test_dehydrate_html_1() {
);
assert_eq!(
- converter
+ HtmlToMarkdownWrapped::new(&html_to_markdown)
.convert(
&dehydrate_html(indoc!(
r#"
@@ -2367,7 +2369,7 @@ fn test_dehydrate_html_1() {
);
assert_eq!(
- converter
+ HtmlToMarkdownWrapped::new(&html_to_markdown)
.convert(
&dehydrate_html(indoc!(
"
@@ -2383,7 +2385,7 @@ fn test_dehydrate_html_1() {
);
assert_eq!(
- converter
+ HtmlToMarkdownWrapped::new(&html_to_markdown)
.convert(
&dehydrate_html(indoc!(
r#"
@@ -2428,7 +2430,7 @@ fn test_dehydrate_html_1() {
// `
` by `dehydrating_walk_node`, preserving it as a
// non-breaking space.
assert_eq!(
- converter
+ HtmlToMarkdownWrapped::new(&html_to_markdown)
.convert(
&dehydrate_html(indoc!(
"
diff --git a/server/src/webserver.rs b/server/src/webserver.rs
index 37f87728..e90c79b9 100644
--- a/server/src/webserver.rs
+++ b/server/src/webserver.rs
@@ -52,7 +52,7 @@ use actix_web::{
};
use actix_web_httpauth::{extractors::basic::BasicAuth, middleware::HttpAuthentication};
-use actix_ws::AggregatedMessage;
+use actix_ws::{AggregatedMessage, CloseCode, CloseReason};
use bytes::Bytes;
use dunce::simplified;
use futures_util::StreamExt;
@@ -79,7 +79,7 @@ use tokio::{
oneshot,
},
task::JoinHandle,
- time::sleep,
+ time::{Instant, MissedTickBehavior, interval_at, sleep},
};
use ts_rs::TS;
use url::Url;
@@ -492,9 +492,18 @@ pub const REPLY_TIMEOUT_MS: Duration = if cfg!(test) {
Duration::from_secs(15)
};
-/// The time to wait for a pong from the websocket in response to a ping sent by
-/// this server.
-const WEBSOCKET_PING_DELAY: Duration = Duration::from_secs(2);
+/// How often this server pings each websocket, which is also how long it waits
+/// for the answering pong before closing the websocket. The unit tests in
+/// `ide/vscode/tests.rs` read one websocket of a pair while leaving the other
+/// unread, and tungstenite answers a ping only while its websocket is read.
+/// Waiting on the cache's watcher can leave a websocket unread for longer than
+/// twice the normal delay, so the unit tests use a delay which no wait
+/// approaches.
+const WEBSOCKET_PING_DELAY: Duration = if cfg!(test) {
+ Duration::from_secs(10)
+} else {
+ Duration::from_secs(2)
+};
/// A few message IDs reserved for use during startup or for sending errors.
pub const RESERVED_MESSAGE_ID: f64 = 0.0;
@@ -1340,28 +1349,30 @@ pub fn client_websocket(
// otherwise, closing represents a network interruption (such as the
// computer going to sleep).
let mut is_closing = false;
+ // The status code and description which the Server's Close frame sends
+ // the websocket's client, if any.
+ let mut close_reason: Option = None;
// True if a ping was sent, but a matching pong wasn't yet received.
let mut sent_ping = false;
+ // Ping on a fixed schedule, rather than after a quiet period, so that
+ // a steady stream of outgoing messages can't put off the ping, and
+ // with it the detection of a broken websocket, indefinitely. A send
+ // which stalls the loop past a tick mustn't produce a burst of
+ // catch-up ticks, which would close the websocket before its pong
+ // could arrive.
+ let mut ping_interval =
+ interval_at(Instant::now() + WEBSOCKET_PING_DELAY, WEBSOCKET_PING_DELAY);
+ ping_interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
loop {
select! {
- // Send pings on a regular basis.
- () = sleep(WEBSOCKET_PING_DELAY) => {
- if sent_ping {
- // If we haven't received the answering pong, the
- // websocket must be broken.
- break;
- }
- // Send a ping to check that the websocket is still open.
- // For example, putting a PC to sleep then waking it breaks
- // the websocket, but the server doesn't detect this without
- // sending a ping (which then fails).
- if let Err(err) = session.ping(&Bytes::new()).await {
- error!("Unable to send ping: {err}");
- break;
- }
- sent_ping = true;
- }
+ // Poll the branches in the order written, rather than at
+ // random. Reading the websocket comes first, so that a tick
+ // never judges the websocket broken while the answering pong
+ // waits unread, as it may after a send stalls the loop. The
+ // tick comes before outgoing messages, so that a steady stream
+ // of those can't put off the ping.
+ biased;
// Process a message received from the websocket.
Some(msg_wrapped) = aggregated_msg_stream.next() => {
@@ -1450,12 +1461,39 @@ pub fn client_websocket(
}
}
}
+ // A protocol error must
+ // [fail the connection](https://www.rfc-editor.org/rfc/rfc6455#section-7.1.7),
+ // sending a Close frame with an
+ // [appropriate status code](https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1).
+ // The stream may also report the same error on every
+ // poll, which, since this branch is polled first,
+ // would starve the others.
Err(err) => {
error!("websocket receive error {err:?}");
+ close_reason = Some(CloseCode::Protocol.into());
+ break;
}
}
}
+ // Send pings on a regular basis.
+ _ = ping_interval.tick() => {
+ if sent_ping {
+ // If we haven't received the answering pong, the
+ // websocket must be broken.
+ break;
+ }
+ // Send a ping to check that the websocket is still open.
+ // For example, putting a PC to sleep then waking it breaks
+ // the websocket, but the server doesn't detect this without
+ // sending a ping (which then fails).
+ if let Err(err) = session.ping(&Bytes::new()).await {
+ error!("Unable to send ping: {err}");
+ break;
+ }
+ sent_ping = true;
+ }
+
// Forward a message from the processing task to the websocket.
Some(m) = to_websocket_rx.recv() => {
// Pre-process this message.
@@ -1509,7 +1547,7 @@ pub fn client_websocket(
}
// Shut down the session, to stop any incoming messages.
- if let Err(err) = session.close(None).await {
+ if let Err(err) = session.close(close_reason).await {
error!("Unable to close session: {err}");
}
diff --git a/server/tests/fixtures/overall/overall_5/test_alt_enter_non_editable_link/other.md b/server/tests/fixtures/overall/overall_5/test_alt_enter_non_editable_link/other.md
new file mode 100644
index 00000000..3b1386f5
--- /dev/null
+++ b/server/tests/fixtures/overall/overall_5/test_alt_enter_non_editable_link/other.md
@@ -0,0 +1,3 @@
+The contents of this file don't matter -- tests will supply the content,
+instead of loading it from disk. However, it does need to exist for
+`canonicalize` to find the correct path to this file.
diff --git a/server/tests/fixtures/overall/overall_5/test_alt_enter_non_editable_link/test.md b/server/tests/fixtures/overall/overall_5/test_alt_enter_non_editable_link/test.md
new file mode 100644
index 00000000..3b1386f5
--- /dev/null
+++ b/server/tests/fixtures/overall/overall_5/test_alt_enter_non_editable_link/test.md
@@ -0,0 +1,3 @@
+The contents of this file don't matter -- tests will supply the content,
+instead of loading it from disk. However, it does need to exist for
+`canonicalize` to find the correct path to this file.
diff --git a/server/tests/fixtures/overall/overall_5/test_ctrl_click_non_editable_link/other.md b/server/tests/fixtures/overall/overall_5/test_ctrl_click_non_editable_link/other.md
new file mode 100644
index 00000000..3b1386f5
--- /dev/null
+++ b/server/tests/fixtures/overall/overall_5/test_ctrl_click_non_editable_link/other.md
@@ -0,0 +1,3 @@
+The contents of this file don't matter -- tests will supply the content,
+instead of loading it from disk. However, it does need to exist for
+`canonicalize` to find the correct path to this file.
diff --git a/server/tests/fixtures/overall/overall_5/test_ctrl_click_non_editable_link/test.md b/server/tests/fixtures/overall/overall_5/test_ctrl_click_non_editable_link/test.md
new file mode 100644
index 00000000..3b1386f5
--- /dev/null
+++ b/server/tests/fixtures/overall/overall_5/test_ctrl_click_non_editable_link/test.md
@@ -0,0 +1,3 @@
+The contents of this file don't matter -- tests will supply the content,
+instead of loading it from disk. However, it does need to exist for
+`canonicalize` to find the correct path to this file.
diff --git a/server/tests/fixtures/overall/overall_5/test_open_non_editable_link/other.md b/server/tests/fixtures/overall/overall_5/test_open_non_editable_link/other.md
new file mode 100644
index 00000000..3b1386f5
--- /dev/null
+++ b/server/tests/fixtures/overall/overall_5/test_open_non_editable_link/other.md
@@ -0,0 +1,3 @@
+The contents of this file don't matter -- tests will supply the content,
+instead of loading it from disk. However, it does need to exist for
+`canonicalize` to find the correct path to this file.
diff --git a/server/tests/fixtures/overall/overall_5/test_open_non_editable_link/test.md b/server/tests/fixtures/overall/overall_5/test_open_non_editable_link/test.md
new file mode 100644
index 00000000..3b1386f5
--- /dev/null
+++ b/server/tests/fixtures/overall/overall_5/test_open_non_editable_link/test.md
@@ -0,0 +1,3 @@
+The contents of this file don't matter -- tests will supply the content,
+instead of loading it from disk. However, it does need to exist for
+`canonicalize` to find the correct path to this file.
diff --git a/server/tests/fixtures/overall/overall_5/test_open_non_editable_link_beside/test.md b/server/tests/fixtures/overall/overall_5/test_open_non_editable_link_beside/test.md
new file mode 100644
index 00000000..3b1386f5
--- /dev/null
+++ b/server/tests/fixtures/overall/overall_5/test_open_non_editable_link_beside/test.md
@@ -0,0 +1,3 @@
+The contents of this file don't matter -- tests will supply the content,
+instead of loading it from disk. However, it does need to exist for
+`canonicalize` to find the correct path to this file.
diff --git a/server/tests/fixtures/overall/overall_5/test_open_non_editable_link_keyboard/other.md b/server/tests/fixtures/overall/overall_5/test_open_non_editable_link_keyboard/other.md
new file mode 100644
index 00000000..3b1386f5
--- /dev/null
+++ b/server/tests/fixtures/overall/overall_5/test_open_non_editable_link_keyboard/other.md
@@ -0,0 +1,3 @@
+The contents of this file don't matter -- tests will supply the content,
+instead of loading it from disk. However, it does need to exist for
+`canonicalize` to find the correct path to this file.
diff --git a/server/tests/fixtures/overall/overall_5/test_open_non_editable_link_keyboard/test.md b/server/tests/fixtures/overall/overall_5/test_open_non_editable_link_keyboard/test.md
new file mode 100644
index 00000000..3b1386f5
--- /dev/null
+++ b/server/tests/fixtures/overall/overall_5/test_open_non_editable_link_keyboard/test.md
@@ -0,0 +1,3 @@
+The contents of this file don't matter -- tests will supply the content,
+instead of loading it from disk. However, it does need to exist for
+`canonicalize` to find the correct path to this file.
diff --git a/server/tests/overall/overall_4.rs b/server/tests/overall/overall_4.rs
index 685c7fae..7f83a061 100644
--- a/server/tests/overall/overall_4.rs
+++ b/server/tests/overall/overall_4.rs
@@ -109,7 +109,7 @@ async fn test_xss_core(
let orig_text =
"Before
after."
.to_string();
- let server_id = perform_loadfile(
+ perform_loadfile(
&codechat_server,
&test_dir,
"test.md",
@@ -228,35 +228,6 @@ async fn test_xss_core(
"Source written back to the IDE still contains the XSS payload: {inserted}"
);
codechat_server.send_result(client_id, None).await.unwrap();
- client_id += MESSAGE_ID_INCREMENT;
-
- // Editing a doc block prompts the Server to send the Client a re-translated
- // version of the document; the Client's acknowledgement comes back here as
- // a `Result(Ok)` carrying the Server's ID.
- assert_eq!(
- codechat_server.get_message_timeout(TIMEOUT).await.unwrap(),
- EditorMessage {
- id: server_id,
- message: EditorMessageContents::Result(Ok(ResultOkTypes::Void))
- }
- );
-
- // The re-translation settles the cursor, producing a final cursor-only
- // update from the Client.
- assert_eq!(
- codechat_server.get_message_timeout(TIMEOUT).await.unwrap(),
- EditorMessage {
- id: client_id,
- message: EditorMessageContents::Update(UpdateMessageContents {
- file_path: path_str.clone(),
- cursor_position: Some(CursorPosition::Line(1)),
- scroll_position: None,
- is_re_translation: false,
- contents: None,
- })
- }
- );
- codechat_server.send_result(client_id, None).await.unwrap();
//client_id += MESSAGE_ID_INCREMENT;
assert_no_more_messages(&codechat_server).await;
diff --git a/server/tests/overall/overall_5.rs b/server/tests/overall/overall_5.rs
index e4b9a9cd..646ec37f 100644
--- a/server/tests/overall/overall_5.rs
+++ b/server/tests/overall/overall_5.rs
@@ -26,16 +26,24 @@
// -------
//
// ### Standard library
-use std::{fmt::Write, path::PathBuf, time::Duration};
+use std::{
+ fmt::Write,
+ fs,
+ path::{Path, PathBuf},
+ time::{Duration, Instant},
+};
// ### Third-party
use dunce::canonicalize;
+use indoc::indoc;
use pretty_assertions::assert_eq;
+use serde_json::json;
use thirtyfour::{
- By, Key, WebDriver,
+ By, Key, WebDriver, WebElement,
error::WebDriverError,
prelude::{ElementQueryable, ElementWaitable},
};
+use tokio::time::sleep;
// ### Local
use crate::common::{
@@ -828,3 +836,1285 @@ async fn test_named_anchor_round_trip_core(
Ok(())
}
+
+// ### Links
+//
+// #### In non-editable content
+//
+// A right click on a link in non-editable content shows a context menu whose
+// only item, "Open link", follows that link, while a right click beside the
+// link offers no "Open link". The keyboard opens the same menu on a selected
+// non-editable element, following its first link. See `addLinkMenu` in
+// [tinymce-config.mts](../../../client/src/tinymce-config.mts) and `Links in
+// hydrated content` in [cache-spec.md](../../src/processing/cache-spec.md).
+make_test!(
+ test_open_non_editable_link,
+ test_open_non_editable_link_core
+);
+
+async fn test_open_non_editable_link_core(
+ // The Server, through which the test plays the IDE.
+ codechat_server: CodeChatEditorServerLog,
+ // The browser showing the Client.
+ driver: WebDriver,
+ // The directory holding `test.md` and the `other.md` it links to.
+ test_dir: PathBuf,
+ // WebDriver's errors; failed checks panic instead.
+) -> Result<(), WebDriverError> {
+ open_markdown_non_editable_link(codechat_server, driver, test_dir, MenuTrigger::Pointer).await
+}
+
+make_test!(
+ test_open_non_editable_link_keyboard,
+ test_open_non_editable_link_keyboard_core
+);
+
+async fn test_open_non_editable_link_keyboard_core(
+ // The Server, through which the test plays the IDE.
+ codechat_server: CodeChatEditorServerLog,
+ // The browser showing the Client.
+ driver: WebDriver,
+ // The directory holding `test.md` and the `other.md` it links to.
+ test_dir: PathBuf,
+ // WebDriver's errors; failed checks panic instead.
+) -> Result<(), WebDriverError> {
+ open_markdown_non_editable_link(codechat_server, driver, test_dir, MenuTrigger::Keyboard).await
+}
+
+make_test!(
+ test_open_non_editable_link_beside,
+ test_open_non_editable_link_beside_core
+);
+
+async fn test_open_non_editable_link_beside_core(
+ // The Server, through which the test plays the IDE.
+ codechat_server: CodeChatEditorServerLog,
+ // The browser showing the Client.
+ driver: WebDriver,
+ // The directory holding `test.md`.
+ test_dir: PathBuf,
+ // WebDriver's errors; failed checks panic instead.
+) -> Result<(), WebDriverError> {
+ load_markdown_non_editable_link(&codechat_server, &driver, &test_dir).await;
+
+ // Right-click the text preceding the link. Pressing a non-editable element
+ // selects it, leaving TinyMCE in the state in which the keyboard opens the
+ // menu, so this checks that the Client doesn't mistake this right click for
+ // the keyboard.
+ let non_editable = query_markdown_non_editable(&driver).await?;
+ let rect = non_editable.rect().await?;
+ driver
+ .action_chain()
+ .move_to_element_with_offset(
+ &non_editable,
+ (-rect.width / 2.0 + 8.0) as i64,
+ (-rect.height / 2.0 + 8.0) as i64,
+ )
+ .context_click()
+ .perform()
+ .await?;
+ // Give a menu time to appear, were the Client to show one.
+ sleep(SETTLE_TIME).await;
+ for menu_item in driver
+ .query(By::Css(".tox-menu [role='menuitem']"))
+ .nowait()
+ .all_from_selector()
+ .await?
+ {
+ assert_ne!(menu_item.text().await?, "Open link");
+ }
+
+ Ok(())
+}
+
+// In a source file, unlike a Markdown document, a doc block becomes a TinyMCE
+// editor only once it's focused (see `DocBlockPlugin` in
+// [CodeMirror-integration.mts](../../../client/src/CodeMirror-integration.mts)),
+// and the cache, not the test, supplies the non-editable content. This test
+// right-clicks a link in a doc block which isn't yet an editor.
+make_test!(
+ test_open_non_editable_link_py_unfocused,
+ test_open_non_editable_link_py_unfocused_core,
+ ignore = "a right click makes a doc block a TinyMCE editor only after the context menu event, so TinyMCE never shows its menu"
+);
+
+async fn test_open_non_editable_link_py_unfocused_core(
+ codechat_server: CodeChatEditorServerLog,
+ driver: WebDriver,
+ test_dir: PathBuf,
+) -> Result<(), WebDriverError> {
+ let link_css = "fragment#test-fragment a";
+ let mut client_id = load_link_project(&codechat_server, &driver, &test_dir, link_css).await?;
+ open_link_from_context_menu(
+ &driver,
+ link_css,
+ MenuTrigger::Pointer,
+ NON_EDITABLE_LINK_MENU,
+ )
+ .await?;
+ assert_requests_file(&codechat_server, &mut client_id, &test_dir.join("other.py")).await;
+
+ Ok(())
+}
+
+// Each of the remaining tests clicks into the doc block holding a link, then
+// follows that link: a link in math, a cross-reference, a fragment's backlinks,
+// or a gather list, leading either to `other.py` or elsewhere in `test.py`.
+make_test!(
+ test_open_non_editable_link_py_math,
+ test_open_non_editable_link_py_math_core,
+ ignore =
+ "MathJax's own context menu handles a right click on math, so TinyMCE never shows its menu"
+);
+
+async fn test_open_non_editable_link_py_math_core(
+ codechat_server: CodeChatEditorServerLog,
+ driver: WebDriver,
+ test_dir: PathBuf,
+) -> Result<(), WebDriverError> {
+ follow_link_project_link(
+ codechat_server,
+ driver,
+ test_dir,
+ "mjx-container a",
+ LinkDestination::OtherFile,
+ )
+ .await
+}
+
+make_test!(
+ test_open_non_editable_link_py_xref,
+ test_open_non_editable_link_py_xref_core
+);
+
+async fn test_open_non_editable_link_py_xref_core(
+ codechat_server: CodeChatEditorServerLog,
+ driver: WebDriver,
+ test_dir: PathBuf,
+) -> Result<(), WebDriverError> {
+ follow_link_project_link(
+ codechat_server,
+ driver,
+ test_dir,
+ "xref[ref='other-target'] a",
+ LinkDestination::OtherFile,
+ )
+ .await
+}
+
+make_test!(
+ test_open_non_editable_link_py_xref_same_file,
+ test_open_non_editable_link_py_xref_same_file_core,
+ ignore = "the Client doesn't follow a link to an id elsewhere in the current file"
+);
+
+async fn test_open_non_editable_link_py_xref_same_file_core(
+ codechat_server: CodeChatEditorServerLog,
+ driver: WebDriver,
+ test_dir: PathBuf,
+) -> Result<(), WebDriverError> {
+ follow_link_project_link(
+ codechat_server,
+ driver,
+ test_dir,
+ "xref[ref='test-target'] a",
+ LinkDestination::ThisFile("test-target"),
+ )
+ .await
+}
+
+make_test!(
+ test_open_non_editable_link_py_fragment,
+ test_open_non_editable_link_py_fragment_core
+);
+
+async fn test_open_non_editable_link_py_fragment_core(
+ codechat_server: CodeChatEditorServerLog,
+ driver: WebDriver,
+ test_dir: PathBuf,
+) -> Result<(), WebDriverError> {
+ follow_link_project_link(
+ codechat_server,
+ driver,
+ test_dir,
+ "fragment#test-fragment a",
+ LinkDestination::OtherFile,
+ )
+ .await
+}
+
+make_test!(
+ test_open_non_editable_link_py_fragment_same_file,
+ test_open_non_editable_link_py_fragment_same_file_core,
+ ignore = "the Client doesn't follow a link to an id elsewhere in the current file"
+);
+
+async fn test_open_non_editable_link_py_fragment_same_file_core(
+ codechat_server: CodeChatEditorServerLog,
+ driver: WebDriver,
+ test_dir: PathBuf,
+) -> Result<(), WebDriverError> {
+ follow_link_project_link(
+ codechat_server,
+ driver,
+ test_dir,
+ "fragment#test-near-fragment a",
+ LinkDestination::ThisFile("test-far-gather"),
+ )
+ .await
+}
+
+make_test!(
+ test_open_non_editable_link_py_gather,
+ test_open_non_editable_link_py_gather_core
+);
+
+async fn test_open_non_editable_link_py_gather_core(
+ codechat_server: CodeChatEditorServerLog,
+ driver: WebDriver,
+ test_dir: PathBuf,
+) -> Result<(), WebDriverError> {
+ follow_link_project_link(
+ codechat_server,
+ driver,
+ test_dir,
+ ".cc-gather-items a[href$='#other-fragment']",
+ LinkDestination::OtherFile,
+ )
+ .await
+}
+
+make_test!(
+ test_open_non_editable_link_py_gather_same_file,
+ test_open_non_editable_link_py_gather_same_file_core,
+ ignore = "the Client doesn't follow a link to an id elsewhere in the current file"
+);
+
+async fn test_open_non_editable_link_py_gather_same_file_core(
+ codechat_server: CodeChatEditorServerLog,
+ driver: WebDriver,
+ test_dir: PathBuf,
+) -> Result<(), WebDriverError> {
+ follow_link_project_link(
+ codechat_server,
+ driver,
+ test_dir,
+ ".cc-gather-items a[href$='#test-far-fragment']",
+ LinkDestination::ThisFile("test-far-fragment"),
+ )
+ .await
+}
+
+// #### In a doc block not holding TinyMCE
+//
+// Links in a doc block which isn't yet a TinyMCE editor act as they do in the
+// one which is: a plain click moves TinyMCE into the doc block without
+// following the link, while a Ctrl+click (Cmd+click on macOS) follows it. See
+// `Link navigation` in [implementation.md](../../../docs/implementation.md).
+make_test!(
+ test_click_link_py_unfocused,
+ test_click_link_py_unfocused_core
+);
+
+async fn test_click_link_py_unfocused_core(
+ codechat_server: CodeChatEditorServerLog,
+ driver: WebDriver,
+ test_dir: PathBuf,
+) -> Result<(), WebDriverError> {
+ let mut client_id =
+ click_unfocused_link(&codechat_server, &driver, &test_dir, PLAIN_LINK_CSS, &[]).await?;
+ let link =
+ assert_not_followed(&codechat_server, &driver, &mut client_id, PLAIN_LINK_CSS).await?;
+ // The cursor lands where the click did, in the link.
+ assert_cursor_in(&driver, &link).await
+}
+
+make_test!(
+ test_ctrl_click_link_py_unfocused,
+ test_ctrl_click_link_py_unfocused_core
+);
+
+async fn test_ctrl_click_link_py_unfocused_core(
+ codechat_server: CodeChatEditorServerLog,
+ driver: WebDriver,
+ test_dir: PathBuf,
+) -> Result<(), WebDriverError> {
+ let mut client_id = click_unfocused_link(
+ &codechat_server,
+ &driver,
+ &test_dir,
+ PLAIN_LINK_CSS,
+ &[FOLLOW_KEY],
+ )
+ .await?;
+ assert_requests_file(&codechat_server, &mut client_id, &test_dir.join("other.py")).await;
+ Ok(())
+}
+
+// As in TinyMCE, a click holding both Ctrl and Alt, which AltGr reports on
+// Windows, doesn't follow the link.
+make_test!(
+ test_ctrl_alt_click_link_py_unfocused,
+ test_ctrl_alt_click_link_py_unfocused_core
+);
+
+async fn test_ctrl_alt_click_link_py_unfocused_core(
+ codechat_server: CodeChatEditorServerLog,
+ driver: WebDriver,
+ test_dir: PathBuf,
+) -> Result<(), WebDriverError> {
+ let mut client_id = click_unfocused_link(
+ &codechat_server,
+ &driver,
+ &test_dir,
+ PLAIN_LINK_CSS,
+ &[Key::Control, Key::Alt],
+ )
+ .await?;
+ assert_not_followed(&codechat_server, &driver, &mut client_id, PLAIN_LINK_CSS).await?;
+ Ok(())
+}
+
+// Focusing a link from the keyboard, as the Tab key does, likewise moves
+// TinyMCE into the doc block without following the link, placing the cursor at
+// the link's start.
+make_test!(
+ test_focus_link_py_unfocused,
+ test_focus_link_py_unfocused_core
+);
+
+async fn test_focus_link_py_unfocused_core(
+ codechat_server: CodeChatEditorServerLog,
+ driver: WebDriver,
+ test_dir: PathBuf,
+) -> Result<(), WebDriverError> {
+ let mut client_id =
+ load_link_project(&codechat_server, &driver, &test_dir, PLAIN_LINK_CSS).await?;
+ let link = query_unfocused_link(&driver, PLAIN_LINK_CSS).await?;
+ driver
+ .execute("arguments[0].focus();", vec![link.to_json()?])
+ .await?;
+ let link =
+ assert_not_followed(&codechat_server, &driver, &mut client_id, PLAIN_LINK_CSS).await?;
+ assert_cursor_in(&driver, &link).await
+}
+
+// The same, for a link in non-editable content. TinyMCE keeps the cursor out of
+// that content, placing it beside the content instead, so these tests don't
+// check where the cursor lands.
+make_test!(
+ test_click_non_editable_link_py_unfocused,
+ test_click_non_editable_link_py_unfocused_core
+);
+
+async fn test_click_non_editable_link_py_unfocused_core(
+ codechat_server: CodeChatEditorServerLog,
+ driver: WebDriver,
+ test_dir: PathBuf,
+) -> Result<(), WebDriverError> {
+ let link_css = "fragment#test-fragment a";
+ let mut client_id =
+ click_unfocused_link(&codechat_server, &driver, &test_dir, link_css, &[]).await?;
+ assert_not_followed(&codechat_server, &driver, &mut client_id, link_css).await?;
+ Ok(())
+}
+
+make_test!(
+ test_ctrl_click_non_editable_link_py_unfocused,
+ test_ctrl_click_non_editable_link_py_unfocused_core
+);
+
+async fn test_ctrl_click_non_editable_link_py_unfocused_core(
+ codechat_server: CodeChatEditorServerLog,
+ driver: WebDriver,
+ test_dir: PathBuf,
+) -> Result<(), WebDriverError> {
+ let link_css = "fragment#test-fragment a";
+ let mut client_id = click_unfocused_link(
+ &codechat_server,
+ &driver,
+ &test_dir,
+ link_css,
+ &[FOLLOW_KEY],
+ )
+ .await?;
+ assert_requests_file(&codechat_server, &mut client_id, &test_dir.join("other.py")).await;
+ Ok(())
+}
+
+// #### In the doc block holding TinyMCE
+//
+// There, the Client takes Ctrl+click (Cmd+click on macOS) and Alt+Enter from
+// TinyMCE's link plugin, following links as it does everywhere else. Alt+Enter
+// follows the link holding the cursor, or the first link in a selected
+// non-editable element. See `takeLinkGestures` in
+// [tinymce-config.mts](../../../client/src/tinymce-config.mts) and `Link
+// navigation` in [implementation.md](../../../docs/implementation.md).
+make_test!(test_ctrl_click_link_py, test_ctrl_click_link_py_core);
+
+async fn test_ctrl_click_link_py_core(
+ codechat_server: CodeChatEditorServerLog,
+ driver: WebDriver,
+ test_dir: PathBuf,
+) -> Result<(), WebDriverError> {
+ let mut client_id =
+ load_link_project(&codechat_server, &driver, &test_dir, PLAIN_LINK_CSS).await?;
+ let link_css = click_into_link_doc_block(&driver, PLAIN_LINK_CSS).await?;
+ let link = driver
+ .query(By::Css(link_css))
+ .desc("the link to follow")
+ .first()
+ .await?;
+ click_holding(&driver, &link, &[FOLLOW_KEY]).await?;
+ assert_requests_file(&codechat_server, &mut client_id, &test_dir.join("other.py")).await;
+ Ok(())
+}
+
+// A plain click on a link which opens in a new tab places the cursor, as on any
+// other link. The Server's sanitizer strips a link's `target` (see
+// `AMMONIA_OPTIONS` in [processing.rs](../../src/processing.rs)), so this test
+// gives the link the target which TinyMCE's link dialog sets for a new window.
+make_test!(test_click_new_tab_link_py, test_click_new_tab_link_py_core);
+
+async fn test_click_new_tab_link_py_core(
+ // The Server, through which the test plays the IDE.
+ codechat_server: CodeChatEditorServerLog,
+ // The browser showing the Client.
+ driver: WebDriver,
+ // The directory in which to create the project.
+ test_dir: PathBuf,
+ // WebDriver's errors; failed checks panic instead.
+) -> Result<(), WebDriverError> {
+ let mut client_id =
+ load_link_project(&codechat_server, &driver, &test_dir, PLAIN_LINK_CSS).await?;
+ let link_css = click_into_link_doc_block(&driver, PLAIN_LINK_CSS).await?;
+ let link = driver
+ .query(By::Css(link_css))
+ .desc("the link to click")
+ .first()
+ .await?;
+ driver
+ .execute("arguments[0].target = '_blank';", vec![link.to_json()?])
+ .await?;
+ click_holding(&driver, &link, &[]).await?;
+ let link =
+ assert_not_followed(&codechat_server, &driver, &mut client_id, PLAIN_LINK_CSS).await?;
+ assert_cursor_in(&driver, &link).await
+}
+
+make_test!(
+ test_ctrl_click_non_editable_link,
+ test_ctrl_click_non_editable_link_core
+);
+
+async fn test_ctrl_click_non_editable_link_core(
+ codechat_server: CodeChatEditorServerLog,
+ driver: WebDriver,
+ test_dir: PathBuf,
+) -> Result<(), WebDriverError> {
+ load_markdown_non_editable_link(&codechat_server, &driver, &test_dir).await;
+ let link = driver
+ .query(By::Css(format!("{DOC_BLOCK_CSS} a")))
+ .desc("the link to follow")
+ .first()
+ .await?;
+ click_holding(&driver, &link, &[FOLLOW_KEY]).await?;
+ let mut client_id = INITIAL_CLIENT_MESSAGE_ID;
+ assert_requests_file(&codechat_server, &mut client_id, &test_dir.join("other.md")).await;
+ Ok(())
+}
+
+make_test!(test_alt_enter_link_py, test_alt_enter_link_py_core);
+
+async fn test_alt_enter_link_py_core(
+ codechat_server: CodeChatEditorServerLog,
+ driver: WebDriver,
+ test_dir: PathBuf,
+) -> Result<(), WebDriverError> {
+ // A plain click on the link places the cursor in it.
+ let mut client_id =
+ click_unfocused_link(&codechat_server, &driver, &test_dir, PLAIN_LINK_CSS, &[]).await?;
+ let link =
+ assert_not_followed(&codechat_server, &driver, &mut client_id, PLAIN_LINK_CSS).await?;
+ assert_cursor_in(&driver, &link).await?;
+ press_alt_enter(&driver).await?;
+ assert_requests_file(&codechat_server, &mut client_id, &test_dir.join("other.py")).await;
+ Ok(())
+}
+
+make_test!(
+ test_alt_enter_non_editable_link,
+ test_alt_enter_non_editable_link_core
+);
+
+async fn test_alt_enter_non_editable_link_core(
+ codechat_server: CodeChatEditorServerLog,
+ driver: WebDriver,
+ test_dir: PathBuf,
+) -> Result<(), WebDriverError> {
+ load_markdown_non_editable_link(&codechat_server, &driver, &test_dir).await;
+ // Click the text preceding the link; the press selects the non-editable
+ // element enclosing it.
+ let non_editable = query_markdown_non_editable(&driver).await?;
+ click_element_top_left(&driver, &non_editable).await?;
+ press_alt_enter(&driver).await?;
+ let mut client_id = INITIAL_CLIENT_MESSAGE_ID;
+ assert_requests_file(&codechat_server, &mut client_id, &test_dir.join("other.md")).await;
+ Ok(())
+}
+
+// The Client's context menu replaces the link plugin's. For an editable link,
+// it keeps the plugin's items for editing the link, while its "Open link"
+// follows the link as a Ctrl+click does. See `addLinkMenu` in
+// [tinymce-config.mts](../../../client/src/tinymce-config.mts).
+make_test!(test_open_link_py, test_open_link_py_core);
+
+async fn test_open_link_py_core(
+ // The Server, through which the test plays the IDE.
+ codechat_server: CodeChatEditorServerLog,
+ // The browser showing the Client.
+ driver: WebDriver,
+ // The directory in which to create the project.
+ test_dir: PathBuf,
+ // WebDriver's errors; failed checks panic instead.
+) -> Result<(), WebDriverError> {
+ let mut client_id =
+ load_link_project(&codechat_server, &driver, &test_dir, PLAIN_LINK_CSS).await?;
+ let link_css = click_into_link_doc_block(&driver, PLAIN_LINK_CSS).await?;
+ open_link_from_context_menu(&driver, &link_css, MenuTrigger::Pointer, EDITABLE_LINK_MENU)
+ .await?;
+ assert_requests_file(&codechat_server, &mut client_id, &test_dir.join("other.py")).await;
+ Ok(())
+}
+
+// `followLink` looks for a link's id throughout the Client, not just in the doc
+// block holding TinyMCE, so "Open link" follows a link to an id in another doc
+// block. CodeMirror renders this one, since it's near the link; see the TODO in
+// `followLink` for a target which CodeMirror hasn't rendered.
+make_test!(
+ test_open_link_py_near_target,
+ test_open_link_py_near_target_core
+);
+
+async fn test_open_link_py_near_target_core(
+ // The Server, through which the test plays the IDE.
+ codechat_server: CodeChatEditorServerLog,
+ // The browser showing the Client.
+ driver: WebDriver,
+ // The directory in which to create the project.
+ test_dir: PathBuf,
+ // WebDriver's errors; failed checks panic instead.
+) -> Result<(), WebDriverError> {
+ let link_css = "a[href='#test-near-target']";
+ let mut client_id = load_link_project(&codechat_server, &driver, &test_dir, link_css).await?;
+ let link_css = click_into_link_doc_block(&driver, link_css).await?;
+ // Measure only once TinyMCE has moved into the doc block, so that any
+ // scrolling the move causes can't pass for following the link.
+ driver
+ .query(By::Css(&link_css))
+ .desc("the link in the doc block holding TinyMCE")
+ .first()
+ .await?;
+ let unscrolled_top = scroll_top(&driver).await?;
+ open_link_from_context_menu(&driver, &link_css, MenuTrigger::Pointer, EDITABLE_LINK_MENU)
+ .await?;
+ // Following a link within the current file mustn't ask the IDE to open a
+ // file. Waiting for that also gives any scrolling time to finish.
+ assert_eq!(
+ next_unhandled_message(&codechat_server, &mut client_id, SETTLE_TIME).await,
+ None
+ );
+ // The target may have been in view to begin with, but `scrollIntoView`
+ // then scrolls it to the top, since filler follows it.
+ assert!(
+ scroll_top(&driver).await? > unscrolled_top,
+ "Following the link didn't scroll."
+ );
+ assert!(
+ is_in_view(&driver, "test-near-target").await,
+ "Following the link didn't scroll #test-near-target into view."
+ );
+ Ok(())
+}
+
+// #### Support for these tests
+//
+// How a test opens TinyMCE's context menu for a link.
+enum MenuTrigger {
+ // A right click on the link.
+ Pointer,
+ // A click beside the link, which selects the non-editable element
+ // enclosing it, then the keyboard's context menu shortcut.
+ Keyboard,
+}
+
+// The non-editable content which the Markdown tests above place in `test.md`,
+// as a stand-in for the content the cache hydrates. Text precedes the link, so
+// that a click at the content's top left lands beside the link.
+const MARKDOWN_NON_EDITABLE_LINK: &str =
+ "\n";
+
+// Where following a link should lead.
+enum LinkDestination {
+ // The Client asks the IDE to open `other.py`.
+ OtherFile,
+ // The Client scrolls the element with this id, in the current file, into
+ // view.
+ ThisFile(&'static str),
+}
+
+// How long the Client must stay silent before the tests above consider the
+// Server's re-translations of the current file finished.
+const SETTLE_TIME: Duration = Duration::from_secs(1);
+
+// How often the tests above re-check the Client while waiting for it.
+const POLL_INTERVAL: Duration = Duration::from_millis(100);
+
+// The other file in the project `write_link_project` creates, holding a target
+// of each kind which `test.py` links to.
+const LINK_PROJECT_OTHER_PY: &str = indoc!(
+ r#"
+ # Other target
+ a = 1
+ # Other gather
+ b = 2
+ # A fragment gathered by `test.py`.
+ c = 3
+ "#
+);
+
+// CSS selecting the plain link in the `test.py` which `write_link_project`
+// creates.
+const PLAIN_LINK_CSS: &str = "a[href='other.py']";
+
+// The labels of the items on TinyMCE's context menu for a link in non-editable
+// content: only the Client's "Open link".
+const NON_EDITABLE_LINK_MENU: &[&str] = &["Open link"];
+
+// The same, for an editable link: the link plugin's items for editing the link,
+// then the Client's "Open link".
+const EDITABLE_LINK_MENU: &[&str] = &["Link…", "Remove link", "Open link"];
+
+// The key which, held during a click on a link, follows that link.
+const FOLLOW_KEY: Key = if cfg!(target_os = "macos") {
+ Key::Command
+} else {
+ Key::Control
+};
+
+// Create a project in `test_dir` whose `test.py` contains a plain link, a link
+// to an id, and a link of each kind which the cache hydrates. Every link
+// sits near the start of `test.py`, within the first screen. So does the target
+// `test-near-target`, close enough that CodeMirror always renders it; every
+// other target in `test.py` itself follows 100 lines of filler, so that
+// following a link to one of them must scroll.
+fn write_link_project(
+ // The directory to create the project in.
+ test_dir: &Path,
+) {
+ let mut test_py = indoc!(
+ r#"
+ # Links to `other.py`: the cross-reference
+ # and the math $\href{other.py}{\text{Other}}$.
+ a = 1
+ # A fragment gathered by `other.py`.
+ b = 2
+ # A fragment gathered at the
+ # end of this file.
+ c = 3
+ # Gathered
+ d = 4
+ # A link to the end of this file: the cross-reference
+ # .
+ e = 5
+ # A plain link to [the file other.py](other.py).
+ f = 6
+ # A link to [the target below](#test-near-target).
+ g = 7
+ # Near target
+ h = 8
+ "#
+ )
+ .to_string();
+ for i in 0..100 {
+ let _ = writeln!(test_py, "filler_{i} = {i}");
+ }
+ test_py.push_str(indoc!(
+ r#"
+ # Test target
+ i = 9
+ # A fragment gathered at the
+ # start of this file.
+ j = 10
+ # Far gather
+ k = 11
+ "#
+ ));
+ // A `toc.md` makes this directory a project, so that the cache resolves ids
+ // across its files.
+ fs::write(test_dir.join("toc.md"), "Contents\n").unwrap();
+ fs::write(test_dir.join("other.py"), LINK_PROJECT_OTHER_PY).unwrap();
+ fs::write(test_dir.join("test.py"), test_py).unwrap();
+}
+
+// Show `test.md`, holding `MARKDOWN_NON_EDITABLE_LINK`, in the Client.
+async fn load_markdown_non_editable_link(
+ // The Server, through which the test plays the IDE.
+ codechat_server: &CodeChatEditorServerLog,
+ // The browser showing the Client; this selects the Client's iframe.
+ driver: &WebDriver,
+ // The directory holding `test.md`.
+ test_dir: &Path,
+) {
+ perform_loadfile(
+ codechat_server,
+ test_dir,
+ "test.md",
+ Some((MARKDOWN_NON_EDITABLE_LINK.to_string(), 0.0)),
+ false,
+ 6.0,
+ )
+ .await;
+
+ // Target the iframe containing the Client.
+ select_codechat_iframe(driver).await;
+}
+
+// Find the non-editable element which `load_markdown_non_editable_link` shows.
+async fn query_markdown_non_editable(
+ // The browser, with the Client's iframe selected.
+ driver: &WebDriver,
+ // The non-editable element.
+) -> Result {
+ driver
+ .query(By::Css(format!(
+ "{DOC_BLOCK_CSS} [contenteditable='false']"
+ )))
+ .desc("the non-editable element")
+ .first()
+ .await
+}
+
+// In a Markdown document, open the context menu for the link in
+// `MARKDOWN_NON_EDITABLE_LINK`, then follow the link.
+async fn open_markdown_non_editable_link(
+ // The Server, through which the test plays the IDE.
+ codechat_server: CodeChatEditorServerLog,
+ // The browser showing the Client.
+ driver: WebDriver,
+ // The directory holding `test.md` and the `other.md` it links to.
+ test_dir: PathBuf,
+ // How to open the context menu.
+ trigger: MenuTrigger,
+ // WebDriver's errors; failed checks panic instead.
+) -> Result<(), WebDriverError> {
+ load_markdown_non_editable_link(&codechat_server, &driver, &test_dir).await;
+ open_link_from_context_menu(
+ &driver,
+ &format!("{DOC_BLOCK_CSS} a"),
+ trigger,
+ NON_EDITABLE_LINK_MENU,
+ )
+ .await?;
+ let mut client_id = INITIAL_CLIENT_MESSAGE_ID;
+ assert_requests_file(&codechat_server, &mut client_id, &test_dir.join("other.md")).await;
+
+ Ok(())
+}
+
+// Create the project `write_link_project` describes, then show its `test.py`
+// in the Client.
+async fn load_link_project(
+ // The Server, through which the test plays the IDE.
+ codechat_server: &CodeChatEditorServerLog,
+ // The browser showing the Client; this selects the Client's iframe.
+ driver: &WebDriver,
+ // The directory in which to create the project.
+ test_dir: &Path,
+ // CSS selecting the link the test will follow.
+ link_css: &str,
+ // The id of the next message the Client sends.
+) -> Result {
+ write_link_project(test_dir);
+ let test_py = canonicalize(test_dir.join("test.py")).unwrap();
+ codechat_server
+ .send_message_current_file(test_py.to_str().unwrap().to_string())
+ .await
+ .unwrap();
+
+ // The Server translates `test.py` at once, while a walk of the project
+ // brings `other.py` into the cache in the background; the content hydrated
+ // from `other.py` then arrives in re-translations of `test.py` (see `Cache
+ // population and maintenance` in
+ // [cache-spec.md](../../src/processing/cache-spec.md)). These interleave
+ // unpredictably with the Server's requests to load `test.py` and the TOC,
+ // which `perform_loadfile` expects in a fixed order, so answer requests
+ // as they come instead.
+ //
+ // The Client's page can't finish loading until the requests for `test.py`
+ // and the TOC are answered, and WebDriver waits for it to finish before
+ // entering its iframe; answer those first.
+ let mut unrequested = vec![test_py, canonicalize(test_dir.join("toc.md")).unwrap()];
+ while !unrequested.is_empty() {
+ let msg = codechat_server
+ .get_message_timeout(TIMEOUT)
+ .await
+ .unwrap_or_else(|| panic!("The Server never requested {unrequested:?}."));
+ match &msg.message {
+ EditorMessageContents::LoadFile(path, _) => {
+ codechat_server
+ .send_result_loadfile(msg.id, None)
+ .await
+ .unwrap();
+ unrequested.retain(|unrequested_path| unrequested_path != path);
+ }
+ EditorMessageContents::Result(Ok(ResultOkTypes::Void)) => {}
+ _ => panic!("Unexpected message: {msg:#?}"),
+ }
+ }
+
+ // Target the iframe containing the Client.
+ select_codechat_iframe(driver).await;
+
+ // Wait for the link, then for the re-translations to end, so that none
+ // replaces the link while it's in use.
+ let mut client_id = INITIAL_CLIENT_MESSAGE_ID;
+ let deadline = Instant::now() + TIMEOUT;
+ while !driver.query(By::Css(link_css)).nowait().exists().await? {
+ assert!(
+ Instant::now() < deadline,
+ "The link {link_css} never appeared."
+ );
+ assert_eq!(
+ next_unhandled_message(codechat_server, &mut client_id, POLL_INTERVAL).await,
+ None
+ );
+ }
+ assert_eq!(
+ next_unhandled_message(codechat_server, &mut client_id, SETTLE_TIME).await,
+ None
+ );
+ Ok(client_id)
+}
+
+// Open `test.py` from the project `write_link_project` creates, click into the
+// doc block holding the link `link_css` selects, follow that link, then check
+// that it leads to `destination`.
+async fn follow_link_project_link(
+ // The Server, through which the test plays the IDE.
+ codechat_server: CodeChatEditorServerLog,
+ // The browser showing the Client.
+ driver: WebDriver,
+ // The directory in which to create the project.
+ test_dir: PathBuf,
+ // CSS selecting the link to follow.
+ link_css: &str,
+ // Where the link should lead.
+ destination: LinkDestination,
+) -> Result<(), WebDriverError> {
+ let mut client_id = load_link_project(&codechat_server, &driver, &test_dir, link_css).await?;
+ let link_css = &click_into_link_doc_block(&driver, link_css).await?;
+
+ match destination {
+ LinkDestination::OtherFile => {
+ open_link_from_context_menu(
+ &driver,
+ link_css,
+ MenuTrigger::Pointer,
+ NON_EDITABLE_LINK_MENU,
+ )
+ .await?;
+ assert_requests_file(&codechat_server, &mut client_id, &test_dir.join("other.py"))
+ .await;
+ }
+ LinkDestination::ThisFile(id) => {
+ assert!(
+ !is_in_view(&driver, id).await,
+ "#{id} is in view before following the link to it."
+ );
+ open_link_from_context_menu(
+ &driver,
+ link_css,
+ MenuTrigger::Pointer,
+ NON_EDITABLE_LINK_MENU,
+ )
+ .await?;
+ let deadline = Instant::now() + TIMEOUT;
+ while !is_in_view(&driver, id).await {
+ assert!(
+ Instant::now() < deadline,
+ "Following the link never scrolled #{id} into view."
+ );
+ sleep(POLL_INTERVAL).await;
+ }
+ // Following a link within the current file mustn't ask the IDE to
+ // open a file.
+ assert_eq!(
+ next_unhandled_message(&codechat_server, &mut client_id, SETTLE_TIME).await,
+ None
+ );
+ }
+ }
+
+ Ok(())
+}
+
+// Open TinyMCE's context menu for the link `link_css` selects, check that the
+// menu shows `menu_labels`, then choose "Open link".
+async fn open_link_from_context_menu(
+ // The browser, with the Client's iframe selected.
+ driver: &WebDriver,
+ // CSS selecting the link to follow.
+ link_css: &str,
+ // How to open the context menu.
+ trigger: MenuTrigger,
+ // The labels of the menu's items, in order, which must include "Open
+ // link".
+ menu_labels: &[&str],
+ // WebDriver's errors; failed checks panic instead.
+) -> Result<(), WebDriverError> {
+ let link = driver
+ .query(By::Css(link_css))
+ .desc("the link to follow")
+ .first()
+ .await?;
+ match trigger {
+ MenuTrigger::Pointer => {
+ driver
+ .action_chain()
+ .context_click_element(&link)
+ .perform()
+ .await?;
+ }
+ MenuTrigger::Keyboard => {
+ // Click the text preceding the link in the non-editable element
+ // enclosing it; the press selects that element.
+ let non_editable = link
+ .find(By::XPath("ancestor::*[@contenteditable='false'][1]"))
+ .await?;
+ click_element_top_left(driver, &non_editable).await?;
+ // macOS has no keyboard shortcut which opens a context menu, so
+ // there, send the `contextmenu` event which such a shortcut
+ // produces elsewhere. TinyMCE treats this event as the keyboard's
+ // when its `button` isn't 2 (the right button), opening the menu
+ // on the selection.
+ if cfg!(target_os = "macos") {
+ driver
+ .execute(
+ r#"tinymce.get(0).getBody().dispatchEvent(
+ new MouseEvent("contextmenu", { bubbles: true, cancelable: true }),
+ );"#,
+ vec![],
+ )
+ .await?;
+ } else {
+ // Press Shift+F10. Chrome doesn't open a context menu for
+ // WebDriver's key actions, but does for a `rawKeyDown` sent
+ // through the DevTools protocol's
+ // [Input.dispatchKeyEvent](https://chromedevtools.github.io/devtools-protocol/tot/Input/#method-dispatchKeyEvent).
+ let cdp = driver.cdp();
+ for event_type in ["rawKeyDown", "keyUp"] {
+ cdp.send_raw(
+ "Input.dispatchKeyEvent",
+ json!({
+ "type": event_type,
+ // Shift.
+ "modifiers": 8,
+ // F10.
+ "windowsVirtualKeyCode": 121,
+ "key": "F10",
+ "code": "F10",
+ }),
+ )
+ .await?;
+ }
+ }
+ }
+ }
+ let menu_items = driver
+ .query(By::Css(".tox-menu [role='menuitem']"))
+ .desc("the items of TinyMCE's context menu")
+ .all_from_selector_required()
+ .await?;
+ // An item's text includes its keyboard shortcut, if any; its label
+ // doesn't.
+ let mut labels = Vec::new();
+ for menu_item in &menu_items {
+ labels.push(
+ menu_item
+ .find(By::Css(".tox-collection__item-label"))
+ .await?
+ .text()
+ .await?,
+ );
+ }
+ assert_eq!(labels, menu_labels);
+ let open_link = labels
+ .iter()
+ .position(|label| label == "Open link")
+ .unwrap();
+ menu_items[open_link].click().await?;
+ Ok(())
+}
+
+// Find the link `link_css` selects in a doc block which doesn't hold TinyMCE.
+// Once `load_link_project` returns, nothing has focused a doc block yet, so
+// none holds TinyMCE.
+async fn query_unfocused_link(
+ // The browser, with the Client's iframe selected.
+ driver: &WebDriver,
+ // CSS selecting the link.
+ link_css: &str,
+ // The link.
+) -> Result {
+ driver
+ .query(By::Css(format!(
+ ".CodeChat-CodeMirror .CodeChat-doc-contents:not(#TinyMCE-inst) {link_css}"
+ )))
+ .desc("the link, in a doc block not holding TinyMCE")
+ .first()
+ .await
+}
+
+// Open `test.py` from the project `write_link_project` creates, then click the
+// link `link_css` selects, in a doc block which doesn't hold TinyMCE.
+async fn click_unfocused_link(
+ // The Server, through which the test plays the IDE.
+ codechat_server: &CodeChatEditorServerLog,
+ // The browser showing the Client.
+ driver: &WebDriver,
+ // The directory in which to create the project.
+ test_dir: &Path,
+ // CSS selecting the link to click.
+ link_css: &str,
+ // The keys to hold during the click.
+ modifiers: &[Key],
+ // The id of the next message the Client sends.
+) -> Result {
+ let client_id = load_link_project(codechat_server, driver, test_dir, link_css).await?;
+ let link = query_unfocused_link(driver, link_css).await?;
+ click_holding(driver, &link, modifiers).await?;
+ Ok(client_id)
+}
+
+// Click into the doc block holding the link `link_css` selects, moving TinyMCE
+// there, without following the link.
+async fn click_into_link_doc_block(
+ // The browser, with the Client's iframe selected.
+ driver: &WebDriver,
+ // CSS selecting the link.
+ link_css: &str,
+ // CSS selecting the link in the doc block, which now holds TinyMCE.
+) -> Result {
+ // Clicking a doc block's top left corner lands on text which precedes
+ // every link in these doc blocks, so the click focuses the doc block
+ // without following anything. Focus moves the TinyMCE editor into the doc
+ // block, replacing its contents.
+ let doc_block = driver
+ .query(By::Css(format!(
+ ".CodeChat-CodeMirror .CodeChat-doc-contents:has({link_css})"
+ )))
+ .desc("the doc block holding the link to follow")
+ .first()
+ .await?;
+ click_element_top_left(driver, &doc_block).await?;
+ Ok(format!(
+ "#TinyMCE-inst:not(.CodeChat-doc-hidden) {link_css}"
+ ))
+}
+
+// Click `element` while holding `modifiers`.
+async fn click_holding(
+ // The browser, with the Client's iframe selected.
+ driver: &WebDriver,
+ // The element to click.
+ element: &WebElement,
+ // The keys to hold during the click.
+ modifiers: &[Key],
+) -> Result<(), WebDriverError> {
+ let mut actions = driver.action_chain();
+ for modifier in modifiers {
+ actions = actions.key_down(modifier.clone());
+ }
+ actions = actions.click_element(element);
+ for modifier in modifiers.iter().rev() {
+ actions = actions.key_up(modifier.clone());
+ }
+ actions.perform().await
+}
+
+// Press Alt+Enter in the element which has the focus.
+async fn press_alt_enter(
+ // The browser, with the Client's iframe selected.
+ driver: &WebDriver,
+) -> Result<(), WebDriverError> {
+ driver
+ .action_chain()
+ .key_down(Key::Alt)
+ .key_down(Key::Enter)
+ .key_up(Key::Enter)
+ .key_up(Key::Alt)
+ .perform()
+ .await
+}
+
+// Check that clicking or focusing the link `link_css` selects moved TinyMCE
+// into the doc block holding that link without following the link.
+async fn assert_not_followed(
+ // The Server, through which the test plays the IDE.
+ codechat_server: &CodeChatEditorServerLog,
+ // The browser, with the Client's iframe selected.
+ driver: &WebDriver,
+ // The id of the next message the Client sends; updated as messages
+ // arrive.
+ client_id: &mut f64,
+ // CSS selecting the link.
+ link_css: &str,
+ // The link, now in the doc block holding TinyMCE.
+) -> Result {
+ assert_eq!(
+ next_unhandled_message(codechat_server, client_id, SETTLE_TIME).await,
+ None
+ );
+ let link = driver
+ .query(By::Css(format!(
+ "#TinyMCE-inst:not(.CodeChat-doc-hidden) {link_css}"
+ )))
+ .desc("the link, in the doc block holding TinyMCE")
+ .first()
+ .await?;
+ let has_focus: bool = driver
+ .execute(
+ "return document.activeElement.id === 'TinyMCE-inst';",
+ Vec::new(),
+ )
+ .await?
+ .convert()?;
+ assert!(has_focus, "TinyMCE doesn't have the focus.");
+ Ok(link)
+}
+
+// Check that the cursor is in `link`.
+async fn assert_cursor_in(
+ // The browser, with the Client's iframe selected.
+ driver: &WebDriver,
+ // The link which must hold the cursor.
+ link: &WebElement,
+) -> Result<(), WebDriverError> {
+ let has_cursor: bool = driver
+ .execute(
+ "return arguments[0].contains(document.getSelection().anchorNode);",
+ vec![link.to_json()?],
+ )
+ .await?
+ .convert()?;
+ assert!(has_cursor, "The cursor isn't in the link.");
+ Ok(())
+}
+
+// Check that the Client's next request asks the IDE to open `file`.
+async fn assert_requests_file(
+ // The Server, through which the test plays the IDE.
+ codechat_server: &CodeChatEditorServerLog,
+ // The id of the next message the Client sends; updated as messages
+ // arrive.
+ client_id: &mut f64,
+ // The file the Client should request.
+ file: &Path,
+) {
+ let path = canonicalize(file).unwrap();
+ let request = next_unhandled_message(codechat_server, client_id, TIMEOUT).await;
+ assert_eq!(
+ request,
+ Some(EditorMessage {
+ id: *client_id,
+ message: EditorMessageContents::CurrentFile(
+ path.to_str().unwrap().to_string(),
+ Some(true)
+ ),
+ })
+ );
+}
+
+// Return the next message the IDE receives, handling those which need no
+// attention from the tests above:
+//
+// * A successful result, such as the Client's acknowledgement of a
+// re-translation from the Server.
+// * A request to load a file, answered as an IDE without that file open would,
+// so that the Server reads it from disk.
+// * The Client's report of a new cursor or scroll position, which this
+// acknowledges.
+async fn next_unhandled_message(
+ // The Server, through which the test plays the IDE.
+ codechat_server: &CodeChatEditorServerLog,
+ // The id of the next message the Client sends; updated as messages
+ // arrive.
+ client_id: &mut f64,
+ // How long to wait for each message.
+ timeout: Duration,
+ // The message, or `None` if none arrives within `timeout`.
+) -> Option {
+ while let Some(msg) = codechat_server.get_message_timeout(timeout).await {
+ match &msg.message {
+ EditorMessageContents::Result(Ok(ResultOkTypes::Void)) => {}
+ EditorMessageContents::LoadFile(..) => {
+ codechat_server
+ .send_result_loadfile(msg.id, None)
+ .await
+ .unwrap();
+ }
+ EditorMessageContents::Update(UpdateMessageContents { contents: None, .. }) => {
+ assert_eq!(msg.id, *client_id);
+ codechat_server.send_result(msg.id, None).await.unwrap();
+ *client_id += MESSAGE_ID_INCREMENT;
+ }
+ _ => return Some(msg),
+ }
+ }
+ None
+}
+
+// Report whether any of the element with the given id is visible in the
+// CodeMirror editor. CodeMirror renders only what's near its viewport, so an
+// element far from it isn't in the DOM.
+async fn is_in_view(
+ // The browser, with the Client's iframe selected.
+ driver: &WebDriver,
+ // The element's id.
+ id: &str,
+ // True if any of the element is visible.
+) -> bool {
+ driver
+ .execute(
+ &format!(
+ "const element = document.getElementById('{id}');
+ if (element === null) return false;
+ const rect = element.getBoundingClientRect();
+ const scroller = document
+ .querySelector('.CodeChat-CodeMirror .cm-scroller')
+ .getBoundingClientRect();
+ return rect.bottom > scroller.top && rect.top < scroller.bottom;"
+ ),
+ Vec::new(),
+ )
+ .await
+ .unwrap()
+ .convert()
+ .unwrap()
+}
+
+// Return how far, in pixels, the CodeMirror editor has scrolled from its top.
+async fn scroll_top(
+ // The browser, with the Client's iframe selected.
+ driver: &WebDriver,
+ // The scroll offset.
+) -> Result {
+ driver
+ .execute(
+ "return document.querySelector('.CodeChat-CodeMirror .cm-scroller').scrollTop;",
+ Vec::new(),
+ )
+ .await?
+ .convert()
+}
diff --git a/toc.md b/toc.md
index 364ff3d2..cc1343c0 100644
--- a/toc.md
+++ b/toc.md
@@ -105,8 +105,8 @@ Implementation
* Builder
* [builder/Cargo.toml](builder/Cargo.toml)
* [builder/src/main.rs](builder/src/main.rs)
- * [server/bt](server/bt) - shortcut to run the build tool
- * [server/bt.ps1](server/bt.ps1) - PowerShell shortcut to run the build tool
+ * [bt](bt) - shortcut to run the build tool
+ * [bt.ps1](bt.ps1) - PowerShell shortcut to run the build tool
* Continuous integration
* [check.yml](.github/workflows/check.yml)
* [release.yml](.github/workflows/release.yml)
@@ -138,6 +138,7 @@ Implementation
* [Cargo.toml](Cargo.toml) - workspace manifest
* [config.toml](.cargo/config.toml) - for Rust code coverage
* [dist-workspace.toml](dist-workspace.toml) - cargo-dist configuration
+ * [.ignore](.ignore) - show hidden directories to the CodeChat Editor
Misc
====