Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
# Keep the supply chain current: GitHub Actions and Cargo dependencies.
#
# Copied from Project-Colony-Resources templates/dependabot.yml. The one
# difference is the cargo directory: the crate lives in src-tauri/.
# Copied from Project-Colony-Resources templates/dependabot.yml.
#
# Dependabot opening a PR is not the rule being followed; merging it is. See
# design/dependencies.md: everything is on its latest release, always.
#
# The libraries in dist/vendor/ (highlight.js, KaTeX, Mermaid, the Colony
# The libraries in src/ui/vendor/ (highlight.js, KaTeX, Mermaid, the Colony
# themes) are not packages Dependabot can see. Check them by hand on the
# weekly pass; docs/third-party-notices.md lists their versions.
version: 2
Expand All @@ -29,7 +28,7 @@ updates:
patterns: ["*"]

- package-ecosystem: cargo
directory: /src-tauri
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
Expand Down
46 changes: 30 additions & 16 deletions .github/scripts/check-colony-themes.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,54 @@
#!/usr/bin/env python3
"""Check that dist/ only uses Colony colours that every theme defines.
"""Check that src/ui/ only uses Colony colours that every theme defines.

dist/vendor/colony/colony-themes.css sets the --colony-* variables once per
src/ui/vendor/colony/colony-themes.css sets the --colony-* variables once per
theme, and themes.json lists the themes the picker offers. A variable one
theme lacks leaves that colour unset in that theme, so this fails on it, and
on a picker entry with no CSS block or a CSS block with no picker entry.
colony-accents.css sets the accent colours once for every theme, so a
variable it defines counts as defined everywhere, and every accent the
picker offers must be one of them.
"""

import json
import re
import sys
from pathlib import Path

dist = Path(__file__).resolve().parents[2] / "dist"
vendor = dist / "vendor" / "colony"
ui = Path(__file__).resolve().parents[2] / "src" / "ui"
vendor = ui / "vendor" / "colony"


def variables(css_file):
"""{selector: set of --colony-* names}, comments out first: the file
header quotes a selector."""
css = re.sub(r"/\*.*?\*/", "", (vendor / css_file).read_text(encoding="utf-8"), flags=re.S)
return {
selector.strip(): set(re.findall(r"(--colony-[a-z0-9-]+):", body))
for selector, body in re.findall(r"([^{}]+)\{([^}]*)\}", css)
}


# Comments out first: the file header quotes a selector.
css = re.sub(r"/\*.*?\*/", "", (vendor / "colony-themes.css").read_text(encoding="utf-8"), flags=re.S)
themes = {
slug: set(re.findall(r"(--colony-[a-z0-9-]+):", body))
for slug, body in re.findall(r'\[data-colony-theme="([^"]+)"\][^{]*\{([^}]*)\}', css)
}
listed = {
variant["slug"]
for family in json.loads((vendor / "themes.json").read_text(encoding="utf-8"))["families"]
for variant in family["variants"]
re.search(r'\[data-colony-theme="([^"]+)"\]', selector).group(1): defined
for selector, defined in variables("colony-themes.css").items()
if "[data-colony-theme=" in selector
}
accents = variables("colony-accents.css").get(":root", set())
theme_list = json.loads((vendor / "themes.json").read_text(encoding="utf-8"))
listed = {variant["slug"] for family in theme_list["families"] for variant in family["variants"]}

errors = []
if not themes or set(themes) != listed:
errors.append(f"themes.json and colony-themes.css disagree: {sorted(set(themes) ^ listed)}")
for path in sorted(dist.glob("*.*")):
for var in sorted(set(re.findall(r"--colony-[a-z0-9-]+", path.read_text(encoding="utf-8")))):
for accent in theme_list["accents"]:
if accent["cssVar"] not in accents:
errors.append(f"themes.json: accent {accent['key']} has no {accent['cssVar']} in colony-accents.css")
for path in sorted(ui.glob("*.*")):
for var in sorted(set(re.findall(r"--colony-[a-z0-9-]+", path.read_text(encoding="utf-8"))) - accents):
missing = [slug for slug, defined in themes.items() if var not in defined]
if missing:
errors.append(f"{path.name}: {var} is missing from {', '.join(missing)}")

print("\n".join(errors) or f"OK: all {len(themes)} themes define every --colony-* variable in dist/")
print("\n".join(errors) or f"OK: all {len(themes)} themes define every --colony-* variable in src/ui/")
sys.exit(1 if errors else 0)
8 changes: 4 additions & 4 deletions .github/scripts/check-document-filter.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Check that render() in dist/app.js still drops every element that could act
"""Check that render() in src/ui/app.js still drops every element that could act
on MemoryStick's own page.

comrak keeps a document's raw HTML as written, and the inert <template> pass
Expand All @@ -15,20 +15,20 @@
# Ordinary raw HTML a document is allowed to keep, as on GitHub.
KEPT = {"kbd", "details", "summary", "sub", "sup", "svg", "img", "a"}

app = (Path(__file__).resolve().parents[2] / "dist" / "app.js").read_text(encoding="utf-8")
app = (Path(__file__).resolve().parents[2] / "src" / "ui" / "app.js").read_text(encoding="utf-8")

errors = []
match = re.search(r"^const DROPPED_ELEMENTS = '([^']*)';$", app, flags=re.M)
if not match:
errors.append("dist/app.js: no `const DROPPED_ELEMENTS = '...';` line")
errors.append("src/ui/app.js: no `const DROPPED_ELEMENTS = '...';` line")
else:
dropped = {name.strip() for name in match.group(1).split(",")}
if missing := REQUIRED - dropped:
errors.append(f"DROPPED_ELEMENTS no longer drops: {', '.join(sorted(missing))}")
if wrongly := KEPT & dropped:
errors.append(f"DROPPED_ELEMENTS drops ordinary HTML: {', '.join(sorted(wrongly))}")
if ".querySelectorAll(DROPPED_ELEMENTS).forEach((el) => el.remove());" not in app:
errors.append("dist/app.js: render() no longer removes DROPPED_ELEMENTS from the document")
errors.append("src/ui/app.js: render() no longer removes DROPPED_ELEMENTS from the document")

print("\n".join(errors) or f"OK: render() drops all {len(REQUIRED)} elements")
sys.exit(1 if errors else 0)
26 changes: 10 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ concurrency:
env:
CARGO_TERM_COLOR: always

defaults:
run:
working-directory: src-tauri

jobs:
test:
name: test ${{ matrix.os }}
Expand All @@ -48,7 +44,6 @@ jobs:
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
key: ci-${{ matrix.os }}
workspaces: src-tauri -> target

# Same list as the release workflow.
- name: Install Linux Tauri dependencies
Expand Down Expand Up @@ -101,7 +96,7 @@ jobs:
id: msrv
run: |
version=$(sed -n 's/^rust-version = "\(.*\)"$/\1/p' Cargo.toml)
[ -n "$version" ] || { echo "::error::could not read rust-version from src-tauri/Cargo.toml"; exit 1; }
[ -n "$version" ] || { echo "::error::could not read rust-version from Cargo.toml"; exit 1; }
echo "version=$version" >> "$GITHUB_OUTPUT"

- uses: dtolnay/rust-toolchain@e2a55d2ffb04f378e9626c28d38b36d230d1e12f # master, 2026-10-09
Expand All @@ -111,7 +106,6 @@ jobs:
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
key: msrv-${{ steps.msrv.outputs.version }}
workspaces: src-tauri -> target

- name: Install Linux Tauri dependencies
run: |
Expand All @@ -127,11 +121,11 @@ jobs:
defaults:
run:
shell: bash
working-directory: .
env:
# The Project-Colony-Resources commit colony-ui 0.1.5 was published
# from. Bump it together with dist/vendor/colony/README.md.
COLONY_RESOURCES_REV: edc7495de15e75553b9203e85d58e4452be67b3a
# A Project-Colony-Resources commit whose generated/ matches the one
# colony-ui 0.1.6 was published from. Bump it together with
# src/ui/vendor/colony/README.md.
COLONY_RESOURCES_REV: 7247b814fd9afbb17e3c4f44315ec9333c3a31b0

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -141,21 +135,21 @@ jobs:
- name: Vendored files match Project-Colony-Resources
run: |
base="https://raw.githubusercontent.com/Project-Colony/Project-Colony-Resources/$COLONY_RESOURCES_REV/generated"
curl -fsSL --retry 3 "$base/css/colony-themes.css" | cmp - dist/vendor/colony/colony-themes.css
curl -fsSL --retry 3 "$base/themes.json" | cmp - dist/vendor/colony/themes.json
curl -fsSL --retry 3 "$base/css/colony-themes.css" | cmp - src/ui/vendor/colony/colony-themes.css
curl -fsSL --retry 3 "$base/css/colony-accents.css" | cmp - src/ui/vendor/colony/colony-accents.css
curl -fsSL --retry 3 "$base/themes.json" | cmp - src/ui/vendor/colony/themes.json

- name: Every theme defines the colours dist/ uses
- name: Every theme defines the colours src/ui/ uses
run: python3 .github/scripts/check-colony-themes.py

# render() in dist/app.js is the only place a document's scripts, frames
# render() in src/ui/app.js is the only place a document's scripts, frames
# and style sheets are removed (comrak keeps raw HTML as written).
document-filter:
name: Document filter
runs-on: ubuntu-latest
defaults:
run:
shell: bash
working-directory: .

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,15 @@ jobs:
# --locked: release-please bumps Cargo.lock with Cargo.toml, so the tag
# builds exactly the dependency versions it records.
- name: Build
run: cargo build --release --locked --target ${{ matrix.target }} --manifest-path src-tauri/Cargo.toml
run: cargo build --release --locked --target ${{ matrix.target }}

- name: Stage binary (Unix)
if: runner.os != 'Windows'
run: cp src-tauri/target/${{ matrix.target }}/release/memorystick ${{ matrix.asset }}
run: cp target/${{ matrix.target }}/release/memorystick ${{ matrix.asset }}

- name: Stage binary (Windows)
if: runner.os == 'Windows'
run: copy src-tauri\target\${{ matrix.target }}\release\memorystick.exe ${{ matrix.asset }}
run: copy target\${{ matrix.target }}\release\memorystick.exe ${{ matrix.asset }}

# A manifest can PARSE perfectly and still leave the program listed in
# Colony with no Download button, because none of its assets match a
Expand Down Expand Up @@ -192,7 +192,7 @@ jobs:

# SignPath only signs an exe whose version resource names the project
# and carries the release version. tauri-build writes both, from
# productName in tauri.conf.json and the version in src-tauri/Cargo.toml.
# productName in tauri.conf.json and the version in Cargo.toml.
- name: Check the Windows version resource
if: runner.os == 'Windows'
shell: pwsh
Expand Down
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Rust / Cargo
src-tauri/target/
src-tauri/gen/schemas/
/target/
# Written by tauri-build on every build
/gen/schemas/
/permissions/
**/*.rs.bk

# Node (should a bundler ever be added)
Expand Down
File renamed without changes.
File renamed without changes.
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,16 @@ Linux (for example `libwebkit2gtk-4.1-0` on Debian and Ubuntu,
```bash
git clone https://github.com/Project-Colony/MemoryStick
cd MemoryStick
cargo build --release --locked --manifest-path src-tauri/Cargo.toml
cargo build --release --locked
```

Requires Rust 1.90 or newer and, on Linux, the WebKitGTK 4.1 development
package (`libwebkit2gtk-4.1-dev` on Debian and Ubuntu). The executable is
`src-tauri/target/release/memorystick`. No Tauri CLI or Node.js is needed:
`src-tauri/` is the Rust application, which reads a file and renders its
Markdown to HTML, and `dist/` is the interface, plain HTML, CSS and
JavaScript embedded in the executable at build time.
`target/release/memorystick`. No Tauri CLI or Node.js is needed: `src/` is
the Rust application, which reads a file and renders its Markdown to HTML,
and `src/ui/` is the interface, plain HTML, CSS and JavaScript embedded in
the executable at build time. `assets/icons/` holds the program's icons and
`config/capabilities/` the commands the interface may call.

## Documentation

Expand Down Expand Up @@ -203,10 +204,10 @@ GPL-3.0-or-later. You may redistribute and modify MemoryStick under the terms
of version 3 of the GNU General Public License, or (at your option) any later
version. The full text is in [LICENSE](LICENSE).

The libraries in `dist/vendor/` keep their own licences: BSD-3-Clause for
The libraries in `src/ui/vendor/` keep their own licences: BSD-3-Clause for
highlight.js, MIT for KaTeX and Mermaid, OFL-1.1 for the KaTeX fonts,
GPL-3.0-or-later for the Colony themes, and those of the packages Mermaid's
build contains. The fonts in `dist/fonts/` are under OFL-1.1. The Rust
build contains. The fonts in `src/ui/fonts/` are under OFL-1.1. The Rust
libraries compiled into the executable keep theirs too, mostly MIT,
Apache-2.0, BSD and ISC, and Preferences > About > Third-party licences
shows their texts. See
Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@ reach users without the Project-Colony organisation's signature.

Out of scope: bugs in the operating system's web view (WebView2, WebKit,
WebKitGTK), which belong to its vendor, and in the libraries under
`dist/vendor/`, which belong to their projects. Do tell us if a MemoryStick
`src/ui/vendor/`, which belong to their projects. Do tell us if a MemoryStick
release ships a vulnerable version of one of them.
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes.
File renamed without changes.
File renamed without changes
24 changes: 14 additions & 10 deletions src-tauri/build.rs → build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,20 @@ fn main() {
write_rust_licenses();

// Declaring the app's commands turns on Tauri's permission check for
// them, so they answer only the pages that capabilities/ allows: the
// app's own page, never a page from anywhere else.
tauri_build::try_build(tauri_build::Attributes::new().app_manifest(
tauri_build::AppManifest::new().commands(&[
"open_document",
"load_file",
"save_preferences",
"third_party_licenses",
]),
))
// them, so they answer only the pages that config/capabilities/ allows:
// the app's own page, never a page from anywhere else. tauri-build
// watches only its default capabilities/ folder, so this one is named.
println!("cargo:rerun-if-changed=config/capabilities");
tauri_build::try_build(
tauri_build::Attributes::new()
.capabilities_path_pattern("config/capabilities/**/*")
.app_manifest(tauri_build::AppManifest::new().commands(&[
"open_document",
"load_file",
"save_preferences",
"third_party_licenses",
])),
)
.expect("tauri-build failed");
}

Expand Down
2 changes: 1 addition & 1 deletion colony.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"$schema": "https://raw.githubusercontent.com/Project-Colony/Project-Colony-Resources/main/generated/colony.schema.json",
"name": "MemoryStick",
"category": "office",
"icon": "src-tauri/icons/icon.png",
"icon": "assets/icons/icon.png",
"signed": true
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"$schema": "../../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "What the MemoryStick window may call, and only from the app's own pages",
"windows": ["main"],
Expand Down
33 changes: 0 additions & 33 deletions dist/vendor/colony/README.md

This file was deleted.

Loading
Loading