@inkandswitch/patchwork 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/dist/client.d.ts +2 -1
- package/dist/index.js +4 -4
- package/dist/repo.d.ts +1 -1
- package/dist/repo.js +5 -4
- package/dist/router.d.ts +2 -2
- package/dist/router.js +14 -6
- package/dist/site-kit/html.d.ts +1 -1
- package/dist/site-kit/html.js +2 -1
- package/dist/site-kit/manifest.d.ts +1 -1
- package/dist/site-kit/manifest.js +2 -1
- package/dist/site-kit/options.d.ts +16 -3
- package/dist/site-kit/options.js +1 -1
- package/dist/types.d.ts +6 -5
- package/dist/vite/config-plugin.d.ts +2 -1
- package/dist/vite/config-plugin.js +8 -4
- package/package.json +4 -4
- package/src/client.d.ts +2 -1
- package/src/index.ts +6 -9
- package/src/repo.ts +8 -4
- package/src/router.ts +17 -6
- package/src/site-kit/html.ts +2 -2
- package/src/site-kit/manifest.ts +2 -2
- package/src/site-kit/options.ts +17 -3
- package/src/types.ts +6 -5
- package/src/vite/config-plugin.ts +10 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @inkandswitch/patchwork
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 98be594: Collapse `siteName`, `title`, and `setup({name})` into a single `title`.
|
|
8
|
+
|
|
9
|
+
A site's name was three options across two files: `siteName` and `title` in the vite config, `name` at `setup`. They fed the same handful of strings and could disagree with each other.
|
|
10
|
+
|
|
11
|
+
`title` is now the only one. It names the html `<title>`, `apple-mobile-web-app-title`, and the manifest's `name`/`short_name` as before, and is also emitted as the `__SITE_TITLE__` define, which supplies the brand word the router appends to the document title as `"<doc> | <title>"`. It defaults to `"Patchwork"`.
|
|
12
|
+
|
|
13
|
+
- `siteName` and the `__SITE_NAME__` define are removed. If you used `siteName` only for display, rename it to `title`; if you relied on it to namespace storage, see `storagePrefix`.
|
|
14
|
+
- `setup({name})` is now `setup({title})`, and is only needed to override the build-time value.
|
|
15
|
+
|
|
16
|
+
- 98be594: Namespace IndexedDB and peer ids with a new build-time `storagePrefix` option.
|
|
17
|
+
|
|
18
|
+
The tab and the shared automerge worker are separate bundles that must open the same databases. Both now read the name from one place, `@inkandswitch/patchwork-bootloader/storage`, resolved from the `__STORAGE_PREFIX__` define the vite plugin emits unconditionally.
|
|
19
|
+
|
|
20
|
+
Previously each side resolved `__SITE_NAME__` itself with a different fallback — `"patchwork.inkandswitch.com"` in the worker, `"patchwork"` in the tab — so a site that never set `siteName` had its tab and worker on two different keyhive databases, and one that passed `setup({name})` split them the same way, since a runtime option never reaches the worker.
|
|
21
|
+
|
|
22
|
+
- `storagePrefix` defaults to `"patchwork"` and is settable only in the build config. Sites sharing an origin must use distinct prefixes. It is deliberately not derived from any display name: changing it points a site at empty storage, so a rebrand must not be able to change it by accident.
|
|
23
|
+
- Sites that relied on `siteName` to namespace their storage must now set `storagePrefix` explicitly to that same value to keep their existing databases.
|
|
24
|
+
- `createRepo` in `@inkandswitch/patchwork` no longer takes a site name argument.
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- Updated dependencies [98be594]
|
|
29
|
+
- @inkandswitch/patchwork-bootloader@0.6.0
|
|
30
|
+
|
|
3
31
|
## 0.4.0
|
|
4
32
|
|
|
5
33
|
### Minor Changes
|
package/dist/client.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -53,8 +53,8 @@ export function setup(options = {}) {
|
|
|
53
53
|
}
|
|
54
54
|
export default setup;
|
|
55
55
|
async function doSetup(options) {
|
|
56
|
-
const
|
|
57
|
-
(typeof
|
|
56
|
+
const siteTitle = options.title ??
|
|
57
|
+
(typeof __SITE_TITLE__ !== "undefined" ? __SITE_TITLE__ : "Patchwork");
|
|
58
58
|
const moduleSources = resolveDefaultModules(options);
|
|
59
59
|
const routing = options.routing ?? "hash";
|
|
60
60
|
log("booting", options);
|
|
@@ -85,7 +85,7 @@ async function doSetup(options) {
|
|
|
85
85
|
}
|
|
86
86
|
});
|
|
87
87
|
let workerAdapter = new MessageChannelNetworkAdapter(workerPort);
|
|
88
|
-
({ repo, hive, signerIdentity } = await createRepo(
|
|
88
|
+
({ repo, hive, signerIdentity } = await createRepo(workerAdapter));
|
|
89
89
|
// The worker was recreated with cold state: wire the repo onto the fresh
|
|
90
90
|
// port and drop the adapter stranded on the dead one.
|
|
91
91
|
const bootHive = hive;
|
|
@@ -154,7 +154,7 @@ async function doSetup(options) {
|
|
|
154
154
|
rootElement,
|
|
155
155
|
repo,
|
|
156
156
|
accountDocHandle,
|
|
157
|
-
|
|
157
|
+
siteTitle,
|
|
158
158
|
});
|
|
159
159
|
}
|
|
160
160
|
installReveal(rootElement, router, toolsLoaded);
|
package/dist/repo.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type AutomergeRepoKeyhive } from "@automerge/automerge-repo-keyhive";
|
|
|
3
3
|
import setupServiceWorker from "@inkandswitch/patchwork-bootloader";
|
|
4
4
|
import type { SignerIdentity } from "./types.js";
|
|
5
5
|
export declare function initWasm(): Promise<void>;
|
|
6
|
-
export declare function createRepo(
|
|
6
|
+
export declare function createRepo(workerAdapter: MessageChannelNetworkAdapter): Promise<{
|
|
7
7
|
repo: Repo;
|
|
8
8
|
hive?: AutomergeRepoKeyhive;
|
|
9
9
|
signerIdentity?: SignerIdentity;
|
package/dist/repo.js
CHANGED
|
@@ -5,6 +5,7 @@ import { initKeyhiveWasm, initializeAutomergeRepoKeyhiveWithRepo, } from "@autom
|
|
|
5
5
|
// @ts-ignore — initSync is a wasm-bindgen runtime helper not in the .d.ts
|
|
6
6
|
import { initSync as initSubductionSync } from "@automerge/automerge-subduction/slim";
|
|
7
7
|
import { MemorySigner } from "@automerge/automerge-subduction/slim";
|
|
8
|
+
import { keyhiveStorageName, storagePrefix, } from "@inkandswitch/patchwork-bootloader/storage";
|
|
8
9
|
import debug from "debug";
|
|
9
10
|
const log = debug("patchwork:setup:repo");
|
|
10
11
|
const syncServer = typeof __SYNC_SERVER__ !== "undefined"
|
|
@@ -27,14 +28,14 @@ export function initWasm() {
|
|
|
27
28
|
}
|
|
28
29
|
return wasmReady;
|
|
29
30
|
}
|
|
30
|
-
export async function createRepo(
|
|
31
|
+
export async function createRepo(workerAdapter) {
|
|
31
32
|
if (syncServer.keyhive) {
|
|
32
33
|
log("setting up keyhive");
|
|
33
34
|
initKeyhiveWasm();
|
|
34
35
|
const { hive, repo } = await initializeAutomergeRepoKeyhiveWithRepo({
|
|
35
36
|
createRepo: (repoConfig) => new Repo(repoConfig),
|
|
36
|
-
storage: new IndexedDBWorkerStorageAdapter(
|
|
37
|
-
peerIdSuffix:
|
|
37
|
+
storage: new IndexedDBWorkerStorageAdapter(keyhiveStorageName),
|
|
38
|
+
peerIdSuffix: storagePrefix + Math.random().toString(36).slice(2),
|
|
38
39
|
networkAdapter: workerAdapter,
|
|
39
40
|
automaticArchiveIngestion: true,
|
|
40
41
|
cachingMode: "periodic",
|
|
@@ -61,7 +62,7 @@ export async function createRepo(siteName, workerAdapter) {
|
|
|
61
62
|
return peerId.includes("automerge-worker");
|
|
62
63
|
},
|
|
63
64
|
enableRemoteHeadsGossiping: true,
|
|
64
|
-
peerId: `${
|
|
65
|
+
peerId: `${storagePrefix}-tab-${crypto.randomUUID()}`,
|
|
65
66
|
});
|
|
66
67
|
const signerIdentity = {
|
|
67
68
|
peerId: signer.peerId().toString(),
|
package/dist/router.d.ts
CHANGED
|
@@ -9,10 +9,10 @@ export interface RouterParams {
|
|
|
9
9
|
rootElement: HTMLElement;
|
|
10
10
|
repo: Repo;
|
|
11
11
|
accountDocHandle: DocHandle<AccountDoc>;
|
|
12
|
-
|
|
12
|
+
siteTitle: string;
|
|
13
13
|
}
|
|
14
14
|
export interface Router {
|
|
15
15
|
/** Apply `location.hash` to the view. */
|
|
16
16
|
route(): Promise<void>;
|
|
17
17
|
}
|
|
18
|
-
export declare function createRouter({ rootElement, repo, accountDocHandle,
|
|
18
|
+
export declare function createRouter({ rootElement, repo, accountDocHandle, siteTitle, }: RouterParams): Router;
|
package/dist/router.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isValidAutomergeUrl, isValidDocumentId, stringifyAutomergeUrl, } from "@automerge/vanillajs/slim";
|
|
1
|
+
import { isValidAutomergeUrl, isValidDocumentId, parseAutomergeUrl, stringifyAutomergeUrl, } from "@automerge/vanillajs/slim";
|
|
2
2
|
import { openDocument } from "@inkandswitch/patchwork-elements";
|
|
3
3
|
import { getRegistry, } from "@inkandswitch/patchwork-plugins";
|
|
4
4
|
// Legacy big-patchwork hash shape: `<slug>--<documentId>[?…]`. The slug can
|
|
@@ -6,12 +6,12 @@ import { getRegistry, } from "@inkandswitch/patchwork-plugins";
|
|
|
6
6
|
// anchor on the `--` before the base58 document id rather than a strict slug
|
|
7
7
|
// charset.
|
|
8
8
|
const BIG_PATCHWORK_HASH_REGEX = /^(?<title>[^=&?/#]*)--(?<docId>[1-9A-HJ-NP-Za-km-z]+)/;
|
|
9
|
-
// The `doc=`
|
|
9
|
+
// The `doc=` and `draft=` values are automerge URLs, kept literal rather than
|
|
10
10
|
// percent-encoded so links stay readable.
|
|
11
|
-
const RAW_HASH_KEYS = new Set(["doc"]);
|
|
11
|
+
const RAW_HASH_KEYS = new Set(["doc", "draft"]);
|
|
12
12
|
// A stable order means re-serializing the same logical params is
|
|
13
13
|
// byte-identical, avoiding spurious `hashchange` round-trips.
|
|
14
|
-
const HASH_KEY_ORDER = ["doc", "tool", "type", "title", "frame"];
|
|
14
|
+
const HASH_KEY_ORDER = ["doc", "tool", "type", "title", "frame", "draft"];
|
|
15
15
|
function serializeHashParams(params) {
|
|
16
16
|
const keys = [...HASH_KEY_ORDER, ...params.keys()];
|
|
17
17
|
const parts = [];
|
|
@@ -47,7 +47,7 @@ function registeredFrameToolId() {
|
|
|
47
47
|
.filter((tool) => !!tool.tags?.includes("frame-tool") && !tool.unlisted)
|
|
48
48
|
.at(0)?.id;
|
|
49
49
|
}
|
|
50
|
-
export function createRouter({ rootElement, repo, accountDocHandle,
|
|
50
|
+
export function createRouter({ rootElement, repo, accountDocHandle, siteTitle, }) {
|
|
51
51
|
const route = async () => {
|
|
52
52
|
// The first call seeds the root view's tool/doc so it can mount; later
|
|
53
53
|
// calls reconcile the mounted view with the hash.
|
|
@@ -107,6 +107,14 @@ export function createRouter({ rootElement, repo, accountDocHandle, siteName, })
|
|
|
107
107
|
// `doc` is the full automerge URL, so heads live inside it and the separate
|
|
108
108
|
// `heads=` param is gone.
|
|
109
109
|
params.delete("heads");
|
|
110
|
+
// `draft` is doc-scoped (owned by the drafts plugin, opaque here):
|
|
111
|
+
// navigating to a different document invalidates the selection, while
|
|
112
|
+
// same-doc navigation (tool switches, heads changes) keeps it.
|
|
113
|
+
const prevDoc = docParamToUrl(params.get("doc"));
|
|
114
|
+
if (prevDoc &&
|
|
115
|
+
parseAutomergeUrl(prevDoc).documentId !== parseAutomergeUrl(url).documentId) {
|
|
116
|
+
params.delete("draft");
|
|
117
|
+
}
|
|
110
118
|
params.set("doc", url);
|
|
111
119
|
for (const [key, value] of [
|
|
112
120
|
["tool", toolId],
|
|
@@ -130,7 +138,7 @@ export function createRouter({ rootElement, repo, accountDocHandle, siteName, })
|
|
|
130
138
|
return;
|
|
131
139
|
const docTitle = datatype.module.getTitle(doc);
|
|
132
140
|
if (docTitle)
|
|
133
|
-
document.title = `${docTitle} | ${
|
|
141
|
+
document.title = `${docTitle} | ${siteTitle}`;
|
|
134
142
|
}
|
|
135
143
|
catch (e) {
|
|
136
144
|
console.error("Failed to update document title", e);
|
package/dist/site-kit/html.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type PatchworkSiteOptions } from "./options.js";
|
|
2
2
|
export declare function escapeHtml(value: string): string;
|
|
3
3
|
/** Builds the generated index.html as a plain string — no bundler involved. */
|
|
4
4
|
export declare function buildHtml(options: PatchworkSiteOptions): string;
|
package/dist/site-kit/html.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DEFAULT_TITLE } from "./options.js";
|
|
1
2
|
import { resolveSyncServers, PRELOAD_WASM_ASSETS } from "./sync-servers.js";
|
|
2
3
|
import { ICON_SPECS } from "./icons.js";
|
|
3
4
|
const HTML_ESCAPES = {
|
|
@@ -12,7 +13,7 @@ export function escapeHtml(value) {
|
|
|
12
13
|
}
|
|
13
14
|
/** Builds the generated index.html as a plain string — no bundler involved. */
|
|
14
15
|
export function buildHtml(options) {
|
|
15
|
-
const title = options.title ??
|
|
16
|
+
const title = options.title ?? DEFAULT_TITLE;
|
|
16
17
|
const lang = (options.html && options.html.lang) || "en";
|
|
17
18
|
const entry = options.entry ?? "/src/main.ts";
|
|
18
19
|
const syncServers = resolveSyncServers(options);
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type PatchworkSiteOptions } from "./options.js";
|
|
2
2
|
/** Builds the generated manifest.webmanifest object — no bundler involved. */
|
|
3
3
|
export declare function buildManifest(options: PatchworkSiteOptions): Record<string, unknown>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { DEFAULT_TITLE } from "./options.js";
|
|
1
2
|
import { ICON_SPECS } from "./icons.js";
|
|
2
3
|
/** Builds the generated manifest.webmanifest object — no bundler involved. */
|
|
3
4
|
export function buildManifest(options) {
|
|
4
|
-
const title = options.title ??
|
|
5
|
+
const title = options.title ?? DEFAULT_TITLE;
|
|
5
6
|
const icons = !options.icons
|
|
6
7
|
? []
|
|
7
8
|
: ICON_SPECS.filter((spec) => spec.fileName === "apple-touch-icon.png" || spec.manifestPurpose).map((spec) => ({
|
|
@@ -37,10 +37,23 @@ export type PatchworkSyncServersOptions = {
|
|
|
37
37
|
/** wss:// URL for the legacy automerge-repo sync-server channel (connected on demand via connectClassicSync). Default: wss://sync3.automerge.org. Pass false to skip its preconnect hint. */
|
|
38
38
|
classic?: string | false;
|
|
39
39
|
} & PatchworkPrimarySyncServerOptions;
|
|
40
|
+
export declare const DEFAULT_TITLE = "Patchwork";
|
|
40
41
|
export interface PatchworkSiteOptions {
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Namespace for this site's IndexedDB databases and peer ids
|
|
44
|
+
* (-> __STORAGE_PREFIX__ define). Defaults to `"patchwork"`. Sites sharing
|
|
45
|
+
* an origin MUST use distinct prefixes.
|
|
46
|
+
*
|
|
47
|
+
* The tab and the shared automerge worker are separate bundles that have to
|
|
48
|
+
* open the same databases, so this is settable only here, where both of them
|
|
49
|
+
* receive it. Changing it on an existing site points it at empty storage.
|
|
50
|
+
*/
|
|
51
|
+
storagePrefix?: string;
|
|
52
|
+
/**
|
|
53
|
+
* This site's name: `<title>`, apple-mobile-web-app-title, manifest name,
|
|
54
|
+
* and — via the __SITE_TITLE__ define — the brand word the router appends to
|
|
55
|
+
* the document title as `"<doc> | <title>"`. Defaults to `"Patchwork"`.
|
|
56
|
+
*/
|
|
44
57
|
title?: string;
|
|
45
58
|
/** manifest short_name (defaults to title) */
|
|
46
59
|
shortName?: string;
|
package/dist/site-kit/options.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export const DEFAULT_TITLE = "Patchwork";
|
package/dist/types.d.ts
CHANGED
|
@@ -47,13 +47,14 @@ export interface PatchworkOptions {
|
|
|
47
47
|
accountKey?: string;
|
|
48
48
|
createAccount?: AccountCreator;
|
|
49
49
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* for you), and used to namespace this site's storage and peer ids.
|
|
50
|
+
* This site's name, appended to the document title as `"<doc> | <title>"`
|
|
51
|
+
* when a document is open (the separator is provided for you).
|
|
53
52
|
*
|
|
54
|
-
* Defaults to the build-time `
|
|
53
|
+
* Defaults to the build-time `__SITE_TITLE__` define — the vite plugin's
|
|
54
|
+
* `title` option, which also names the html `<title>` and the manifest —
|
|
55
|
+
* then `"Patchwork"`.
|
|
55
56
|
*/
|
|
56
|
-
|
|
57
|
+
title?: string;
|
|
57
58
|
/** DOM id of the `<patchwork-view>` hosting the root tool. Defaults to "root". */
|
|
58
59
|
rootElementId?: string;
|
|
59
60
|
/**
|
|
@@ -2,7 +2,8 @@ import type { Plugin } from "vite";
|
|
|
2
2
|
import wasm from "vite-plugin-wasm";
|
|
3
3
|
import type { PatchworkVitePluginOptions } from "./patchwork-plugin.js";
|
|
4
4
|
/**
|
|
5
|
-
* Owns envPrefix, define (
|
|
5
|
+
* Owns envPrefix, define (__SITE_TITLE__/__STORAGE_PREFIX__/sync-server
|
|
6
|
+
* configuration),
|
|
6
7
|
* server/preview CORS defaults, worker format + the wasm plugin, and build
|
|
7
8
|
* defaults (firefox150 target, unminified, sourcemapped) — everything a site
|
|
8
9
|
* used to hand-write in its own vite.config.ts. Each is switched off
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import wasm from "vite-plugin-wasm";
|
|
2
|
+
import { DEFAULT_STORAGE_PREFIX } from "@inkandswitch/patchwork-bootloader/storage";
|
|
3
|
+
import { DEFAULT_TITLE } from "../site-kit/options.js";
|
|
2
4
|
import { DEFAULT_SYNC_SERVERS, resolvePrimarySyncServer, } from "../site-kit/sync-servers.js";
|
|
3
5
|
const CORS_HEADERS = { "Access-Control-Allow-Origin": "*" };
|
|
4
6
|
/**
|
|
5
|
-
* Owns envPrefix, define (
|
|
7
|
+
* Owns envPrefix, define (__SITE_TITLE__/__STORAGE_PREFIX__/sync-server
|
|
8
|
+
* configuration),
|
|
6
9
|
* server/preview CORS defaults, worker format + the wasm plugin, and build
|
|
7
10
|
* defaults (firefox150 target, unminified, sourcemapped) — everything a site
|
|
8
11
|
* used to hand-write in its own vite.config.ts. Each is switched off
|
|
@@ -16,13 +19,14 @@ export function configPlugin(options = {}) {
|
|
|
16
19
|
const classicSyncServer = options.syncServers && typeof options.syncServers.classic === "string"
|
|
17
20
|
? options.syncServers.classic
|
|
18
21
|
: DEFAULT_SYNC_SERVERS.classic;
|
|
22
|
+
// __STORAGE_PREFIX__ is defined unconditionally: the tab and the shared
|
|
23
|
+
// automerge worker resolve it separately, and only a define reaches both.
|
|
19
24
|
const define = {
|
|
20
25
|
__SYNC_SERVER__: JSON.stringify(primarySyncServer),
|
|
21
26
|
__CLASSIC_SYNC_SERVER__: JSON.stringify(classicSyncServer),
|
|
27
|
+
__SITE_TITLE__: JSON.stringify(options.title ?? DEFAULT_TITLE),
|
|
28
|
+
__STORAGE_PREFIX__: JSON.stringify(options.storagePrefix ?? DEFAULT_STORAGE_PREFIX),
|
|
22
29
|
};
|
|
23
|
-
if (options.siteName) {
|
|
24
|
-
define.__SITE_NAME__ = JSON.stringify(options.siteName);
|
|
25
|
-
}
|
|
26
30
|
return {
|
|
27
31
|
envPrefix: ["VITE_", "PATCHWORK_"],
|
|
28
32
|
define,
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"url": "git+https://github.com/inkandswitch/patchwork-system.git",
|
|
6
6
|
"directory": "core/patchwork"
|
|
7
7
|
},
|
|
8
|
-
"version": "0.
|
|
8
|
+
"version": "0.5.0",
|
|
9
9
|
"author": "Ink & Switch",
|
|
10
10
|
"type": "module",
|
|
11
11
|
"license": "MIT",
|
|
@@ -42,11 +42,11 @@
|
|
|
42
42
|
"debug": "^4.4.3",
|
|
43
43
|
"sharp": "^0.35.3",
|
|
44
44
|
"vite-plugin-wasm": "^3.6.0",
|
|
45
|
-
"@inkandswitch/patchwork-plugins": "^1.2.0",
|
|
46
|
-
"@inkandswitch/patchwork-providers": "^0.5.0",
|
|
47
45
|
"@inkandswitch/patchwork-elements": "^6.0.0",
|
|
46
|
+
"@inkandswitch/patchwork-plugins": "^1.2.0",
|
|
47
|
+
"@inkandswitch/patchwork-bootloader": "^0.6.0",
|
|
48
48
|
"@inkandswitch/patchwork-filesystem": "^0.2.5",
|
|
49
|
-
"@inkandswitch/patchwork-
|
|
49
|
+
"@inkandswitch/patchwork-providers": "^0.5.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"esbuild": "^0.23.1",
|
package/src/client.d.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -64,7 +64,7 @@ import { createDefaultAccount } from "./createAccount.js";
|
|
|
64
64
|
|
|
65
65
|
const log = debug("patchwork:setup");
|
|
66
66
|
|
|
67
|
-
declare const
|
|
67
|
+
declare const __SITE_TITLE__: string;
|
|
68
68
|
|
|
69
69
|
declare global {
|
|
70
70
|
interface Window {
|
|
@@ -114,9 +114,9 @@ export function setup(options: PatchworkOptions = {}): Promise<Patchwork> {
|
|
|
114
114
|
export default setup;
|
|
115
115
|
|
|
116
116
|
async function doSetup(options: PatchworkOptions): Promise<Patchwork> {
|
|
117
|
-
const
|
|
118
|
-
options.
|
|
119
|
-
(typeof
|
|
117
|
+
const siteTitle =
|
|
118
|
+
options.title ??
|
|
119
|
+
(typeof __SITE_TITLE__ !== "undefined" ? __SITE_TITLE__ : "Patchwork");
|
|
120
120
|
const moduleSources = resolveDefaultModules(options);
|
|
121
121
|
const routing = options.routing ?? "hash";
|
|
122
122
|
|
|
@@ -151,10 +151,7 @@ async function doSetup(options: PatchworkOptions): Promise<Patchwork> {
|
|
|
151
151
|
});
|
|
152
152
|
|
|
153
153
|
let workerAdapter = new MessageChannelNetworkAdapter(workerPort);
|
|
154
|
-
({ repo, hive, signerIdentity } = await createRepo(
|
|
155
|
-
siteName,
|
|
156
|
-
workerAdapter
|
|
157
|
-
));
|
|
154
|
+
({ repo, hive, signerIdentity } = await createRepo(workerAdapter));
|
|
158
155
|
|
|
159
156
|
// The worker was recreated with cold state: wire the repo onto the fresh
|
|
160
157
|
// port and drop the adapter stranded on the dead one.
|
|
@@ -246,7 +243,7 @@ async function doSetup(options: PatchworkOptions): Promise<Patchwork> {
|
|
|
246
243
|
rootElement,
|
|
247
244
|
repo,
|
|
248
245
|
accountDocHandle,
|
|
249
|
-
|
|
246
|
+
siteTitle,
|
|
250
247
|
});
|
|
251
248
|
}
|
|
252
249
|
|
package/src/repo.ts
CHANGED
|
@@ -17,6 +17,10 @@ import {
|
|
|
17
17
|
import { initSync as initSubductionSync } from "@automerge/automerge-subduction/slim";
|
|
18
18
|
import { MemorySigner } from "@automerge/automerge-subduction/slim";
|
|
19
19
|
import setupServiceWorker from "@inkandswitch/patchwork-bootloader";
|
|
20
|
+
import {
|
|
21
|
+
keyhiveStorageName,
|
|
22
|
+
storagePrefix,
|
|
23
|
+
} from "@inkandswitch/patchwork-bootloader/storage";
|
|
20
24
|
import type { SignerIdentity } from "./types.js";
|
|
21
25
|
import debug from "debug";
|
|
22
26
|
|
|
@@ -50,7 +54,6 @@ export function initWasm(): Promise<void> {
|
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
export async function createRepo(
|
|
53
|
-
siteName: string,
|
|
54
57
|
workerAdapter: MessageChannelNetworkAdapter
|
|
55
58
|
): Promise<{
|
|
56
59
|
repo: Repo;
|
|
@@ -62,8 +65,8 @@ export async function createRepo(
|
|
|
62
65
|
initKeyhiveWasm();
|
|
63
66
|
const { hive, repo } = await initializeAutomergeRepoKeyhiveWithRepo({
|
|
64
67
|
createRepo: (repoConfig) => new Repo(repoConfig),
|
|
65
|
-
storage: new IndexedDBWorkerStorageAdapter(
|
|
66
|
-
peerIdSuffix:
|
|
68
|
+
storage: new IndexedDBWorkerStorageAdapter(keyhiveStorageName),
|
|
69
|
+
peerIdSuffix: storagePrefix + Math.random().toString(36).slice(2),
|
|
67
70
|
networkAdapter: workerAdapter,
|
|
68
71
|
automaticArchiveIngestion: true,
|
|
69
72
|
cachingMode: "periodic",
|
|
@@ -91,7 +94,8 @@ export async function createRepo(
|
|
|
91
94
|
return peerId.includes("automerge-worker");
|
|
92
95
|
},
|
|
93
96
|
enableRemoteHeadsGossiping: true,
|
|
94
|
-
peerId:
|
|
97
|
+
peerId:
|
|
98
|
+
`${storagePrefix}-tab-${crypto.randomUUID()}` as AutomergeRepo.PeerId,
|
|
95
99
|
});
|
|
96
100
|
const signerIdentity = {
|
|
97
101
|
peerId: signer.peerId().toString(),
|
package/src/router.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isValidAutomergeUrl,
|
|
3
3
|
isValidDocumentId,
|
|
4
|
+
parseAutomergeUrl,
|
|
4
5
|
stringifyAutomergeUrl,
|
|
5
6
|
type AutomergeUrl,
|
|
6
7
|
type DocHandle,
|
|
@@ -23,12 +24,12 @@ import {
|
|
|
23
24
|
const BIG_PATCHWORK_HASH_REGEX =
|
|
24
25
|
/^(?<title>[^=&?/#]*)--(?<docId>[1-9A-HJ-NP-Za-km-z]+)/;
|
|
25
26
|
|
|
26
|
-
// The `doc=`
|
|
27
|
+
// The `doc=` and `draft=` values are automerge URLs, kept literal rather than
|
|
27
28
|
// percent-encoded so links stay readable.
|
|
28
|
-
const RAW_HASH_KEYS = new Set(["doc"]);
|
|
29
|
+
const RAW_HASH_KEYS = new Set(["doc", "draft"]);
|
|
29
30
|
// A stable order means re-serializing the same logical params is
|
|
30
31
|
// byte-identical, avoiding spurious `hashchange` round-trips.
|
|
31
|
-
const HASH_KEY_ORDER = ["doc", "tool", "type", "title", "frame"];
|
|
32
|
+
const HASH_KEY_ORDER = ["doc", "tool", "type", "title", "frame", "draft"];
|
|
32
33
|
|
|
33
34
|
function serializeHashParams(params: URLSearchParams): string {
|
|
34
35
|
const keys = [...HASH_KEY_ORDER, ...params.keys()];
|
|
@@ -64,7 +65,7 @@ export interface RouterParams {
|
|
|
64
65
|
rootElement: HTMLElement;
|
|
65
66
|
repo: Repo;
|
|
66
67
|
accountDocHandle: DocHandle<AccountDoc>;
|
|
67
|
-
|
|
68
|
+
siteTitle: string;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
export interface Router {
|
|
@@ -82,7 +83,7 @@ export function createRouter({
|
|
|
82
83
|
rootElement,
|
|
83
84
|
repo,
|
|
84
85
|
accountDocHandle,
|
|
85
|
-
|
|
86
|
+
siteTitle,
|
|
86
87
|
}: RouterParams): Router {
|
|
87
88
|
const route = async () => {
|
|
88
89
|
// The first call seeds the root view's tool/doc so it can mount; later
|
|
@@ -166,6 +167,16 @@ export function createRouter({
|
|
|
166
167
|
// `doc` is the full automerge URL, so heads live inside it and the separate
|
|
167
168
|
// `heads=` param is gone.
|
|
168
169
|
params.delete("heads");
|
|
170
|
+
// `draft` is doc-scoped (owned by the drafts plugin, opaque here):
|
|
171
|
+
// navigating to a different document invalidates the selection, while
|
|
172
|
+
// same-doc navigation (tool switches, heads changes) keeps it.
|
|
173
|
+
const prevDoc = docParamToUrl(params.get("doc"));
|
|
174
|
+
if (
|
|
175
|
+
prevDoc &&
|
|
176
|
+
parseAutomergeUrl(prevDoc).documentId !== parseAutomergeUrl(url).documentId
|
|
177
|
+
) {
|
|
178
|
+
params.delete("draft");
|
|
179
|
+
}
|
|
169
180
|
params.set("doc", url);
|
|
170
181
|
for (const [key, value] of [
|
|
171
182
|
["tool", toolId],
|
|
@@ -192,7 +203,7 @@ export function createRouter({
|
|
|
192
203
|
const docTitle = (datatype.module as DatatypeImplementation).getTitle(
|
|
193
204
|
doc
|
|
194
205
|
);
|
|
195
|
-
if (docTitle) document.title = `${docTitle} | ${
|
|
206
|
+
if (docTitle) document.title = `${docTitle} | ${siteTitle}`;
|
|
196
207
|
} catch (e) {
|
|
197
208
|
console.error("Failed to update document title", e);
|
|
198
209
|
}
|
package/src/site-kit/html.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { DEFAULT_TITLE, type PatchworkSiteOptions } from "./options.js";
|
|
2
2
|
import { resolveSyncServers, PRELOAD_WASM_ASSETS } from "./sync-servers.js";
|
|
3
3
|
import { ICON_SPECS } from "./icons.js";
|
|
4
4
|
|
|
@@ -16,7 +16,7 @@ export function escapeHtml(value: string): string {
|
|
|
16
16
|
|
|
17
17
|
/** Builds the generated index.html as a plain string — no bundler involved. */
|
|
18
18
|
export function buildHtml(options: PatchworkSiteOptions): string {
|
|
19
|
-
const title = options.title ??
|
|
19
|
+
const title = options.title ?? DEFAULT_TITLE;
|
|
20
20
|
const lang = (options.html && options.html.lang) || "en";
|
|
21
21
|
const entry = options.entry ?? "/src/main.ts";
|
|
22
22
|
const syncServers = resolveSyncServers(options);
|
package/src/site-kit/manifest.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { DEFAULT_TITLE, type PatchworkSiteOptions } from "./options.js";
|
|
2
2
|
import { ICON_SPECS } from "./icons.js";
|
|
3
3
|
|
|
4
4
|
/** Builds the generated manifest.webmanifest object — no bundler involved. */
|
|
5
5
|
export function buildManifest(
|
|
6
6
|
options: PatchworkSiteOptions
|
|
7
7
|
): Record<string, unknown> {
|
|
8
|
-
const title = options.title ??
|
|
8
|
+
const title = options.title ?? DEFAULT_TITLE;
|
|
9
9
|
const icons = !options.icons
|
|
10
10
|
? []
|
|
11
11
|
: ICON_SPECS.filter(
|
package/src/site-kit/options.ts
CHANGED
|
@@ -42,10 +42,24 @@ export type PatchworkSyncServersOptions = {
|
|
|
42
42
|
classic?: string | false;
|
|
43
43
|
} & PatchworkPrimarySyncServerOptions;
|
|
44
44
|
|
|
45
|
+
export const DEFAULT_TITLE = "Patchwork";
|
|
46
|
+
|
|
45
47
|
export interface PatchworkSiteOptions {
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Namespace for this site's IndexedDB databases and peer ids
|
|
50
|
+
* (-> __STORAGE_PREFIX__ define). Defaults to `"patchwork"`. Sites sharing
|
|
51
|
+
* an origin MUST use distinct prefixes.
|
|
52
|
+
*
|
|
53
|
+
* The tab and the shared automerge worker are separate bundles that have to
|
|
54
|
+
* open the same databases, so this is settable only here, where both of them
|
|
55
|
+
* receive it. Changing it on an existing site points it at empty storage.
|
|
56
|
+
*/
|
|
57
|
+
storagePrefix?: string;
|
|
58
|
+
/**
|
|
59
|
+
* This site's name: `<title>`, apple-mobile-web-app-title, manifest name,
|
|
60
|
+
* and — via the __SITE_TITLE__ define — the brand word the router appends to
|
|
61
|
+
* the document title as `"<doc> | <title>"`. Defaults to `"Patchwork"`.
|
|
62
|
+
*/
|
|
49
63
|
title?: string;
|
|
50
64
|
/** manifest short_name (defaults to title) */
|
|
51
65
|
shortName?: string;
|
package/src/types.ts
CHANGED
|
@@ -65,13 +65,14 @@ export interface PatchworkOptions {
|
|
|
65
65
|
createAccount?: AccountCreator;
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* for you), and used to namespace this site's storage and peer ids.
|
|
68
|
+
* This site's name, appended to the document title as `"<doc> | <title>"`
|
|
69
|
+
* when a document is open (the separator is provided for you).
|
|
71
70
|
*
|
|
72
|
-
* Defaults to the build-time `
|
|
71
|
+
* Defaults to the build-time `__SITE_TITLE__` define — the vite plugin's
|
|
72
|
+
* `title` option, which also names the html `<title>` and the manifest —
|
|
73
|
+
* then `"Patchwork"`.
|
|
73
74
|
*/
|
|
74
|
-
|
|
75
|
+
title?: string;
|
|
75
76
|
|
|
76
77
|
/** DOM id of the `<patchwork-view>` hosting the root tool. Defaults to "root". */
|
|
77
78
|
rootElementId?: string;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
2
|
import wasm from "vite-plugin-wasm";
|
|
3
|
+
import { DEFAULT_STORAGE_PREFIX } from "@inkandswitch/patchwork-bootloader/storage";
|
|
4
|
+
import { DEFAULT_TITLE } from "../site-kit/options.js";
|
|
3
5
|
import type { PatchworkVitePluginOptions } from "./patchwork-plugin.js";
|
|
4
6
|
import {
|
|
5
7
|
DEFAULT_SYNC_SERVERS,
|
|
@@ -9,7 +11,8 @@ import {
|
|
|
9
11
|
const CORS_HEADERS = { "Access-Control-Allow-Origin": "*" };
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
|
-
* Owns envPrefix, define (
|
|
14
|
+
* Owns envPrefix, define (__SITE_TITLE__/__STORAGE_PREFIX__/sync-server
|
|
15
|
+
* configuration),
|
|
13
16
|
* server/preview CORS defaults, worker format + the wasm plugin, and build
|
|
14
17
|
* defaults (firefox150 target, unminified, sourcemapped) — everything a site
|
|
15
18
|
* used to hand-write in its own vite.config.ts. Each is switched off
|
|
@@ -26,13 +29,16 @@ export function configPlugin(
|
|
|
26
29
|
options.syncServers && typeof options.syncServers.classic === "string"
|
|
27
30
|
? options.syncServers.classic
|
|
28
31
|
: DEFAULT_SYNC_SERVERS.classic;
|
|
32
|
+
// __STORAGE_PREFIX__ is defined unconditionally: the tab and the shared
|
|
33
|
+
// automerge worker resolve it separately, and only a define reaches both.
|
|
29
34
|
const define: Record<string, string> = {
|
|
30
35
|
__SYNC_SERVER__: JSON.stringify(primarySyncServer),
|
|
31
36
|
__CLASSIC_SYNC_SERVER__: JSON.stringify(classicSyncServer),
|
|
37
|
+
__SITE_TITLE__: JSON.stringify(options.title ?? DEFAULT_TITLE),
|
|
38
|
+
__STORAGE_PREFIX__: JSON.stringify(
|
|
39
|
+
options.storagePrefix ?? DEFAULT_STORAGE_PREFIX
|
|
40
|
+
),
|
|
32
41
|
};
|
|
33
|
-
if (options.siteName) {
|
|
34
|
-
define.__SITE_NAME__ = JSON.stringify(options.siteName);
|
|
35
|
-
}
|
|
36
42
|
|
|
37
43
|
return {
|
|
38
44
|
envPrefix: ["VITE_", "PATCHWORK_"],
|