mirror of
https://github.com/Quad4-Software/MeshChatX.git
synced 2026-08-18 09:49:09 -04:00
feat: improve propagation node management with clipboard support and localization adjustments
This commit is contained in:
parent
1648955cab
commit
230bed861a
11 changed files with 38 additions and 10 deletions
BIN
meshchatx.rsm
BIN
meshchatx.rsm
Binary file not shown.
|
|
@ -923,6 +923,7 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.manualHashDraft = parsed;
|
this.manualHashDraft = parsed;
|
||||||
|
await this.usePropagationNode(parsed);
|
||||||
},
|
},
|
||||||
async copyPreferredHash() {
|
async copyPreferredHash() {
|
||||||
const hash = this.config.lxmf_preferred_propagation_node_destination_hash;
|
const hash = this.config.lxmf_preferred_propagation_node_destination_hash;
|
||||||
|
|
|
||||||
|
|
@ -4337,6 +4337,7 @@ export default {
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.config.lxmf_preferred_propagation_node_destination_hash = parsed;
|
this.config.lxmf_preferred_propagation_node_destination_hash = parsed;
|
||||||
|
this.savePreferredPropagationNodeHash(true);
|
||||||
},
|
},
|
||||||
async pastePreferredPropagationNodeHash() {
|
async pastePreferredPropagationNodeHash() {
|
||||||
const result = await readTextFromClipboard();
|
const result = await readTextFromClipboard();
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,12 @@ class Utils {
|
||||||
if (raw == null) {
|
if (raw == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const compact = String(raw).replace(/[^0-9a-fA-F]/g, "");
|
let text = String(raw).trim();
|
||||||
|
if (!text) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
text = text.replace(/^(lxmf|lxma|lxm|rns):\/\//i, "");
|
||||||
|
const compact = text.replace(/[^0-9a-fA-F]/g, "");
|
||||||
if (compact.length < 32) {
|
if (compact.length < 32) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2493,7 +2493,7 @@
|
||||||
"preferred_cleared": "Voorkeurs-propagatieknooppunt gewist",
|
"preferred_cleared": "Voorkeurs-propagatieknooppunt gewist",
|
||||||
"invalid_hash": "Voer een bestemmings-hash van 32 tekens in",
|
"invalid_hash": "Voer een bestemmings-hash van 32 tekens in",
|
||||||
"manual_placeholder": "Plak bestemmings-hash, <hash> of lxmf://...",
|
"manual_placeholder": "Plak bestemmings-hash, <hash> of lxmf://...",
|
||||||
"manual_hint": "Haakjes, spaties en lxmf://-links zijn in orde. Een hash uitzetten schakelt automatische selectie uit.",
|
"manual_hint": "Haakjes, spaties en lxmf://-links zijn in orde. Een hash instellen schakelt automatische selectie uit.",
|
||||||
"set_preferred": "Instellen",
|
"set_preferred": "Instellen",
|
||||||
"paste_hash": "Plakken",
|
"paste_hash": "Plakken",
|
||||||
"clear_preferred": "Wissen",
|
"clear_preferred": "Wissen",
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ record_hit() {
|
||||||
# Do not use a bare /(^|/)android(/|$)/ pattern: LXST ships
|
# Do not use a bare /(^|/)android(/|$)/ pattern: LXST ships
|
||||||
# LXST/Platforms/android for mobile hosts. That path must remain allowed in
|
# LXST/Platforms/android for mobile hosts. That path must remain allowed in
|
||||||
# APKs and must not false-fail Docker scans of site-packages.
|
# APKs and must not false-fail Docker scans of site-packages.
|
||||||
COMMON_DENY_RE='(^|/)\.git(/|$)|(^|/)node_modules(/|$)|(^|/)\.pnpm-store(/|$)|(^|/)\.venv(/|$)|(^|/)vendor/offline(/|$)|(^|/)vendor/lxmfy/tests(/|$)|(^|/)vendor/lxmfy/docs(/|$)|(^|/)vendor/lxmfy/docker(/|$)|(^|/)vendor/rns_filesync/tests(/|$)|(^|/)vendor/rns_filesync/docs(/|$)|(^|/)vendor/rns_filesync/docker(/|$)|(^|/)vendor/rns_filesync/packaging(/|$)|(^|/)vendor/rns_filesync/sideband(/|$)|(^|/)vendor/rns_filesync/man(/|$)|(^|/)vendor/rns_filesync/scripts(/|$)|(^|/)\.github(/|$)|(^|/)docs/agents(/|$)|(^|/)screenshots(/|$)|(^|/)\.pytest_cache(/|$)|(^|/)mutants(/|$)|(^|/)coverage(/|$)'
|
COMMON_DENY_RE='(^|/)\.git(/|$)|(^|/)node_modules(/|$)|(^|/)\.pnpm-store(/|$)|(^|/)\.venv(/|$)|(^|/)vendor/offline(/|$)|(^|/)vendor/lxmfy/tests(/|$)|(^|/)vendor/lxmfy/docs(/|$)|(^|/)vendor/lxmfy/docker(/|$)|(^|/)vendor/rns_filesync/tests(/|$)|(^|/)vendor/rns_filesync/docs(/|$)|(^|/)vendor/rns_filesync/docker(/|$)|(^|/)vendor/rns_filesync/packaging(/|$)|(^|/)vendor/rns_filesync/sideband(/|$)|(^|/)vendor/rns_filesync/man(/|$)|(^|/)vendor/rns_filesync/scripts(/|$)|(^|/)\.github(/|$)|(^|/)\.agents(/|$)|(^|/)screenshots(/|$)|(^|/)\.pytest_cache(/|$)|(^|/)mutants(/|$)|(^|/)coverage(/|$)'
|
||||||
|
|
||||||
BYTECODE_DENY_RE='(^|/)__pycache__(/|$)'
|
BYTECODE_DENY_RE='(^|/)__pycache__(/|$)'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* Copy docs/ tree into meshchatx/src/frontend/public/meshchatx-docs/ for in-app serving.
|
* Copy docs/ tree into meshchatx/src/frontend/public/meshchatx-docs/ for in-app serving.
|
||||||
* Source of truth: docs/ at repo root.
|
* Source of truth: docs/ at repo root.
|
||||||
* Skips docs/agents/ (contributor/agent guidance, not end-user docs).
|
* Agent guidance lives under .agents/ at repo root, not under docs/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
|
@ -12,7 +12,7 @@ const srcDir = path.join(root, "docs");
|
||||||
const destDir = path.join(root, "meshchatx", "src", "frontend", "public", "meshchatx-docs");
|
const destDir = path.join(root, "meshchatx", "src", "frontend", "public", "meshchatx-docs");
|
||||||
|
|
||||||
const COPY_EXTENSIONS = new Set([".md", ".txt", ".json"]);
|
const COPY_EXTENSIONS = new Set([".md", ".txt", ".json"]);
|
||||||
const SKIP_TOP_LEVEL_DIRS = new Set(["agents"]);
|
const SKIP_TOP_LEVEL_DIRS = new Set();
|
||||||
|
|
||||||
function walkSync(dir, callback, relBase = "") {
|
function walkSync(dir, callback, relBase = "") {
|
||||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
"""Helpers for the backend module ownership contract.
|
"""Helpers for the backend module ownership contract.
|
||||||
|
|
||||||
Parses the "Backend" table in docs/agents/module-ownership.md into a plain
|
Parses the "Backend" table in .agents/module-ownership.md into a plain
|
||||||
row structure, and resolves the backtick-quoted spans in that table to
|
row structure, and resolves the backtick-quoted spans in that table to
|
||||||
on-disk paths under meshchatx/src/backend/ or
|
on-disk paths under meshchatx/src/backend/ or
|
||||||
meshchatx/src/frontend/components/. Prose cells without backtick spans
|
meshchatx/src/frontend/components/. Prose cells without backtick spans
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# SPDX-License-Identifier: 0BSD
|
# SPDX-License-Identifier: 0BSD
|
||||||
|
|
||||||
"""Contract test: docs/agents/module-ownership.md Backend table vs files on disk.
|
"""Contract test: .agents/module-ownership.md Backend table vs files on disk.
|
||||||
|
|
||||||
Parses the Backend table directly from the doc so the checked-in fixture
|
Parses the Backend table directly from the doc so the checked-in fixture
|
||||||
drifts loudly (via a failing diff) whenever a row is added, removed, or
|
drifts loudly (via a failing diff) whenever a row is added, removed, or
|
||||||
|
|
@ -28,7 +28,7 @@ from tests.backend.module_ownership_contract_helpers import (
|
||||||
)
|
)
|
||||||
|
|
||||||
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||||
_DOC_PATH = _REPO_ROOT / "docs" / "agents" / "module-ownership.md"
|
_DOC_PATH = _REPO_ROOT / ".agents" / "module-ownership.md"
|
||||||
_FIXTURE = (
|
_FIXTURE = (
|
||||||
Path(__file__).resolve().parent / "fixtures" / "backend_module_ownership.json"
|
Path(__file__).resolve().parent / "fixtures" / "backend_module_ownership.json"
|
||||||
)
|
)
|
||||||
|
|
@ -45,7 +45,7 @@ def test_backend_ownership_table_matches_fixture():
|
||||||
expected = load_ownership_fixture(_FIXTURE)
|
expected = load_ownership_fixture(_FIXTURE)
|
||||||
assert parsed == expected, (
|
assert parsed == expected, (
|
||||||
"Backend module ownership table drifted from the checked-in fixture. "
|
"Backend module ownership table drifted from the checked-in fixture. "
|
||||||
"If the docs/agents/module-ownership.md Backend table changed on purpose, run: "
|
"If the .agents/module-ownership.md Backend table changed on purpose, run: "
|
||||||
"UPDATE_BACKEND_MODULE_OWNERSHIP=1 uv run pytest tests/backend/test_module_ownership_contract.py -k "
|
"UPDATE_BACKEND_MODULE_OWNERSHIP=1 uv run pytest tests/backend/test_module_ownership_contract.py -k "
|
||||||
"backend_ownership_table_matches_fixture"
|
"backend_ownership_table_matches_fixture"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,9 @@ vi.mock("../../meshchatx/src/frontend/js/Utils", () => ({
|
||||||
formatTimeAgo: (d) => "1h ago",
|
formatTimeAgo: (d) => "1h ago",
|
||||||
formatDestinationHash: (h) => (h && h.length >= 8 ? h.slice(0, 8) + "…" : h),
|
formatDestinationHash: (h) => (h && h.length >= 8 ? h.slice(0, 8) + "…" : h),
|
||||||
parseDestinationHash: (h) => {
|
parseDestinationHash: (h) => {
|
||||||
const compact = String(h || "").replace(/[^0-9a-fA-F]/g, "");
|
let text = String(h || "").trim();
|
||||||
|
text = text.replace(/^(lxmf|lxma|lxm|rns):\/\//i, "");
|
||||||
|
const compact = text.replace(/[^0-9a-fA-F]/g, "");
|
||||||
return compact.length >= 32 ? compact.slice(0, 32).toLowerCase() : null;
|
return compact.length >= 32 ? compact.slice(0, 32).toLowerCase() : null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ describe("PropagationNodesPage", () => {
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
|
vi.unstubAllGlobals();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("finds local propagation node from list", () => {
|
it("finds local propagation node from list", () => {
|
||||||
|
|
@ -253,6 +254,24 @@ describe("PropagationNodesPage", () => {
|
||||||
expect(ctx.manualHashDraft).toBe("a39610c89d18bb48c73e429582423c24");
|
expect(ctx.manualHashDraft).toBe("a39610c89d18bb48c73e429582423c24");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("pastePreferredHash sets the preferred node from clipboard text", async () => {
|
||||||
|
const ctx = {
|
||||||
|
manualHashDraft: "",
|
||||||
|
usePropagationNode: vi.fn().mockResolvedValue(true),
|
||||||
|
$t: (k) => k,
|
||||||
|
};
|
||||||
|
vi.stubGlobal("navigator", {
|
||||||
|
clipboard: {
|
||||||
|
readText: vi.fn().mockResolvedValue("<A39610C89D18BB48C73E429582423C24>"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
Object.defineProperty(window, "isSecureContext", { value: true, configurable: true });
|
||||||
|
await PropagationNodesPage.methods.pastePreferredHash.call(ctx);
|
||||||
|
expect(ctx.manualHashDraft).toBe("a39610c89d18bb48c73e429582423c24");
|
||||||
|
expect(ctx.usePropagationNode).toHaveBeenCalledWith("a39610c89d18bb48c73e429582423c24");
|
||||||
|
vi.unstubAllGlobals();
|
||||||
|
});
|
||||||
|
|
||||||
it("clears preferred node and draft", async () => {
|
it("clears preferred node and draft", async () => {
|
||||||
const ctx = {
|
const ctx = {
|
||||||
manualHashDraft: "a39610c89d18bb48c73e429582423c24",
|
manualHashDraft: "a39610c89d18bb48c73e429582423c24",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue