@kanf/wema-kake 0.0.0-stage → 0.1.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/LICENSE +15 -0
- package/README.md +123 -2
- package/bin/wema-kake.mjs +220 -0
- package/dist/lib/index.d.ts +13 -0
- package/dist/lib/index.js +14 -0
- package/dist/lib/shared/api.d.ts +97 -0
- package/dist/lib/shared/api.js +23 -0
- package/dist/lib/shared/delta.d.ts +24 -0
- package/dist/lib/shared/delta.js +19 -0
- package/dist/lib/shared/guide.d.ts +32 -0
- package/dist/lib/shared/guide.js +259 -0
- package/dist/lib/shared/hierarchy.d.ts +31 -0
- package/dist/lib/shared/hierarchy.js +40 -0
- package/dist/lib/shared/i18n.d.ts +23 -0
- package/dist/lib/shared/i18n.js +58 -0
- package/dist/lib/shared/note-html.d.ts +10 -0
- package/dist/lib/shared/note-html.js +28 -0
- package/dist/lib/shared/note-info.d.ts +20 -0
- package/dist/lib/shared/note-info.js +29 -0
- package/dist/lib/shared/note-text.d.ts +11 -0
- package/dist/lib/shared/note-text.js +118 -0
- package/dist/lib/shared/op-summary.d.ts +30 -0
- package/dist/lib/shared/op-summary.js +37 -0
- package/dist/lib/shared/protocol.d.ts +74 -0
- package/dist/lib/shared/protocol.js +12 -0
- package/dist/lib/shared/site.d.ts +16 -0
- package/dist/lib/shared/site.js +28 -0
- package/dist/lib/shared/slug.d.ts +4 -0
- package/dist/lib/shared/slug.js +6 -0
- package/dist/lib/shared/tools.d.ts +67 -0
- package/dist/lib/shared/tools.js +353 -0
- package/dist/lib/worker/access.d.ts +34 -0
- package/dist/lib/worker/access.js +114 -0
- package/dist/lib/worker/apply-ops.d.ts +75 -0
- package/dist/lib/worker/apply-ops.js +335 -0
- package/dist/lib/worker/assets.d.ts +4 -0
- package/dist/lib/worker/assets.js +35 -0
- package/dist/lib/worker/config.d.ts +52 -0
- package/dist/lib/worker/config.js +23 -0
- package/dist/lib/worker/first-page.d.ts +10 -0
- package/dist/lib/worker/first-page.js +30 -0
- package/dist/lib/worker/images.d.ts +3 -0
- package/dist/lib/worker/images.js +54 -0
- package/dist/lib/worker/index.d.ts +8 -0
- package/dist/lib/worker/index.js +72 -0
- package/dist/lib/worker/indexer.d.ts +42 -0
- package/dist/lib/worker/indexer.js +93 -0
- package/dist/lib/worker/mcp/board-access.d.ts +28 -0
- package/dist/lib/worker/mcp/board-access.js +67 -0
- package/dist/lib/worker/mcp/server.d.ts +9 -0
- package/dist/lib/worker/mcp/server.js +39 -0
- package/dist/lib/worker/page-do.d.ts +330 -0
- package/dist/lib/worker/page-do.js +1168 -0
- package/dist/lib/worker/page-queries.d.ts +28 -0
- package/dist/lib/worker/page-queries.js +88 -0
- package/dist/lib/worker/pages-api.d.ts +5 -0
- package/dist/lib/worker/pages-api.js +257 -0
- package/dist/lib/worker/plain-text.d.ts +22 -0
- package/dist/lib/worker/plain-text.js +113 -0
- package/dist/lib/worker/revert.d.ts +21 -0
- package/dist/lib/worker/revert.js +126 -0
- package/dist/lib/worker/sanitize.d.ts +4 -0
- package/dist/lib/worker/sanitize.js +135 -0
- package/dist/lib/worker/validate.d.ts +21 -0
- package/dist/lib/worker/validate.js +218 -0
- package/dist/web/assets/index-CVwMafiH.js +5 -0
- package/dist/web/assets/index-Cgi5orcJ.css +1 -0
- package/dist/web/favicon.svg +13 -0
- package/dist/web/index.html +14 -0
- package/migrations/0001_init.sql +18 -0
- package/migrations/0002_fts.sql +9 -0
- package/migrations/0003_hierarchy.sql +9 -0
- package/migrations/0004_settings.sql +12 -0
- package/migrations/0005_bookmarks.sql +8 -0
- package/migrations/0006_page_color.sql +5 -0
- package/package.json +75 -4
- package/template/.dev.vars.example +4 -0
- package/template/README.md +55 -0
- package/template/gitignore +8 -0
- package/template/package.json +25 -0
- package/template/src/config.ts +18 -0
- package/template/src/index.ts +9 -0
- package/template/tsconfig.json +16 -0
- package/template/wrangler.jsonc +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kan
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
14
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,124 @@
|
|
|
1
|
-
#
|
|
1
|
+
# wema-kake
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](https://github.com/kan/wema-kake/commits/main)
|
|
5
|
+
[](https://www.npmjs.com/package/@kanf/wema)
|
|
6
|
+
[](https://developers.cloudflare.com/workers/)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
[](https://modelcontextprotocol.io/)
|
|
9
|
+
|
|
10
|
+
付箋を並べて書く Wiki です。1 ページが 1 枚のボードで、付箋を置き、線でつなぎ、ページの中に子ページを置いて整理します。Cloudflare Workers で動き、Claude などの LLM からも MCP で読み書きできます。
|
|
11
|
+
|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
**wema-kake はライブラリ(npm: `@kanf/wema-kake`)で、デプロイ物ではありません。** 使う人は、雛形から自分のリポジトリを作り、自分の Worker としてデプロイします。Wiki の処理と画面はパッケージにあり、手元に持つのは設定だけです。更新は `npm update` とデプロイで済みます。
|
|
15
|
+
## 始め方
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npx @kanf/wema-kake init my-wiki
|
|
19
|
+
cd my-wiki
|
|
20
|
+
npm run dev
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`init` は、ディレクトリ、サイトの名前、既定の言語を聞いて、雛形を書き出します。`http://localhost:8787/` で開けます。
|
|
24
|
+
|
|
25
|
+
雛形から GitHub のリポジトリを作ってデプロイするなら、Deploy to Cloudflare のボタンも使えます。
|
|
26
|
+
|
|
27
|
+
[](https://deploy.workers.cloudflare.com/?url=https://github.com/kan/wema-kake/tree/main/template)
|
|
28
|
+
|
|
29
|
+
デプロイと、Cloudflare Access の設定の手順は、雛形の [README](template/README.md) にあります。**デプロイしただけでは、まだ使えません。** Access を設定するまで、画面と API は 500 を返します(保護のない状態で公開しないためです)。
|
|
30
|
+
|
|
31
|
+
### 手元に持つもの
|
|
32
|
+
|
|
33
|
+
| ファイル | 中身 |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| `src/config.ts` | サイトの名前、既定の言語、MCP を有効にするか、最初のページを作るか |
|
|
36
|
+
| `wrangler.jsonc` | Worker の名前、バインディング、デプロイ先ごとの値(Access の設定、サイトのオリジン) |
|
|
37
|
+
| `src/index.ts` | Worker の入口。設定を `@kanf/wema-kake` に渡すだけ |
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
// src/index.ts
|
|
41
|
+
import { createWemaKake, PageDO } from '@kanf/wema-kake';
|
|
42
|
+
import config from './config';
|
|
43
|
+
|
|
44
|
+
export { PageDO };
|
|
45
|
+
export default createWemaKake(config);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
D1 のマイグレーションと画面は、`wrangler.jsonc` がパッケージの中(`node_modules/@kanf/wema-kake`)を直接指します。コピーしないので、更新で取り残されません。
|
|
49
|
+
|
|
50
|
+
### 更新
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
npm update @kanf/wema-kake
|
|
54
|
+
npm run deploy
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`npm run deploy` は、D1 のマイグレーションを当ててから Worker をデプロイします。何を破壊的変更と呼び、版をまたいでデータをどう引き継ぐかは、[docs/package.md](docs/package.md) に書いてあります。
|
|
58
|
+
|
|
59
|
+
## できること
|
|
60
|
+
|
|
61
|
+
- 付箋を置く、動かす、色を付ける、線でつなぐ。箇条書き、チェックリスト、画像、リンクを書ける
|
|
62
|
+
- 複数のブラウザで同じページを開くと、変更がその場で反映される
|
|
63
|
+
- ページの中に子ページを置ける。子ページは、中の付箋の配置を縮小した図つきの付箋として表示され、押すとズームして入る
|
|
64
|
+
- ページの一覧もボードで、ページが付箋、ページ間のリンクが線になる。全文検索で絞り込める
|
|
65
|
+
- リモート MCP サーバーを備えていて、claude.ai のコネクタとして登録できる。LLM がボードを読み、付箋を足し、整理する
|
|
66
|
+
- LLM の操作は、操作の単位で記録される。ページの画面から、まとめて取り消せる
|
|
67
|
+
|
|
68
|
+
## wema との関係
|
|
69
|
+
|
|
70
|
+
名前は、神社で絵馬を掛ける場所「絵馬掛(えまかけ)」から来ています。wema が絵馬、wema-kake がそれを掛ける場所です。
|
|
71
|
+
|
|
72
|
+
ボードの表示と編集は、付箋ボードのライブラリ [wema](https://github.com/kan/wema)(npm: [`@kanf/wema`](https://www.npmjs.com/package/@kanf/wema))が受け持ちます。wema は、ブラウザの中だけで動くライブラリです。保存先や同期の仕組みは持ちません。
|
|
73
|
+
|
|
74
|
+
wema-kake は、wema に次のものを足して Wiki にしたものです。
|
|
75
|
+
|
|
76
|
+
| wema が受け持つ | wema-kake が足す |
|
|
77
|
+
| --- | --- |
|
|
78
|
+
| 付箋と接続線の表示と編集 | ページという単位と、ページ間のリンク、ページの階層 |
|
|
79
|
+
| Undo / Redo、パンとズーム | サーバーへの保存と、複数のブラウザの間の同期 |
|
|
80
|
+
| 変更の内容を表すデータ(デルタ) | 全文検索、画像の保存、認証 |
|
|
81
|
+
| | LLM 向けの MCP サーバーと、その操作の取り消し |
|
|
82
|
+
|
|
83
|
+
wema の側に要る機能は、wema へ依頼して入れてもらい、その版を取り込んでいます。使っている wema の版は、`package.json` の `dependencies` にあります。
|
|
84
|
+
|
|
85
|
+
## 構成
|
|
86
|
+
|
|
87
|
+
| 役割 | 使っているもの |
|
|
88
|
+
| --- | --- |
|
|
89
|
+
| サーバー | Cloudflare Workers + [Hono](https://hono.dev/) |
|
|
90
|
+
| ページの内容と同期 | Durable Objects(1 ページ = 1 オブジェクト、SQLite、WebSocket) |
|
|
91
|
+
| ページの一覧と全文検索 | D1 |
|
|
92
|
+
| 画像 | R2 |
|
|
93
|
+
| 認証 | Cloudflare Access |
|
|
94
|
+
| LLM との連携 | リモート MCP(認可は、Access の Managed OAuth) |
|
|
95
|
+
|
|
96
|
+
## このリポジトリでの開発
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
npm install
|
|
100
|
+
cp .dev.vars.example .dev.vars
|
|
101
|
+
npx wrangler d1 migrations apply DB --local
|
|
102
|
+
npm run dev
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`http://localhost:8787/` で開けます。ローカルでは Access を通らず、`.dev.vars` の `DEV_USER_EMAIL` が利用者になります。リポジトリの中の利用側(`dev/`)が、ビルドせずに `src/` を使います。
|
|
106
|
+
|
|
107
|
+
| コマンド | 内容 |
|
|
108
|
+
| --- | --- |
|
|
109
|
+
| `npm run dev` | 画面をビルドして、ローカルのサーバーを起動する |
|
|
110
|
+
| `npm run dev:roji` | 同じサーバーを、[roji](https://github.com/kan/roji)(1.5.0 以降)を通して起動する。空いているポートを使い、`https://wema-kake.<roji のドメイン>` で開ける |
|
|
111
|
+
| `npm test` | テストを実行する(Workers のランタイムの上で動く) |
|
|
112
|
+
| `npm run lint` | 型チェック |
|
|
113
|
+
| `npm run build` | 配るものを作る。画面(`dist/web`)と Worker の側(`dist/lib`) |
|
|
114
|
+
| `npm pack` | パッケージを作る(`prepack` が `build` を回す) |
|
|
115
|
+
|
|
116
|
+
## 文書
|
|
117
|
+
|
|
118
|
+
- [docs/package.md](docs/package.md): パッケージの入口、設定、互換の決まり
|
|
119
|
+
- [AGENTS.md](AGENTS.md): 設計と、実装で守る決まり。コーディングエージェント向けの指示を兼ねる
|
|
120
|
+
- [docs/plan.md](docs/plan.md): 実装の計画と、フェーズごとの記録
|
|
121
|
+
|
|
122
|
+
## ライセンス
|
|
123
|
+
|
|
124
|
+
[ISC](LICENSE)
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// wema-kake の入口のコマンド。今あるのは init だけ。
|
|
3
|
+
//
|
|
4
|
+
// npx @kanf/wema-kake init [directory] [--yes] [--no-install]
|
|
5
|
+
//
|
|
6
|
+
// Wiki 1 つ分のディレクトリを作る。中身は、パッケージに同梱した template/ で、聞いた値を
|
|
7
|
+
// src/config.ts と wrangler.jsonc と package.json に埋めてから書き出す。
|
|
8
|
+
//
|
|
9
|
+
// 雛形は GitHub から取らず、同梱のものを使う。init が作るのは、その版の wema-kake に合った雛形になる。
|
|
10
|
+
// 依存は足さない(対話は node:readline/promises で足りる)
|
|
11
|
+
import { spawn } from 'node:child_process';
|
|
12
|
+
import { cp, readdir, readFile, rename, stat, writeFile } from 'node:fs/promises';
|
|
13
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
14
|
+
import process from 'node:process';
|
|
15
|
+
import { createInterface } from 'node:readline/promises';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
|
|
18
|
+
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
19
|
+
const template = join(root, 'template');
|
|
20
|
+
const LANGS = ['ja', 'en'];
|
|
21
|
+
|
|
22
|
+
const USAGE = `usage: npx @kanf/wema-kake init [directory] [--yes] [--no-install]
|
|
23
|
+
|
|
24
|
+
init create a wiki in a new directory
|
|
25
|
+
--yes, -y take every default without asking
|
|
26
|
+
--no-install write the files and stop, without running npm install
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
function fail(reason) {
|
|
30
|
+
console.error(`wema-kake: ${reason}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const argv = process.argv.slice(2);
|
|
35
|
+
if (argv.length === 0 || argv.includes('--help') || argv.includes('-h')) {
|
|
36
|
+
process.stdout.write(USAGE);
|
|
37
|
+
process.exit(argv.length === 0 ? 1 : 0);
|
|
38
|
+
}
|
|
39
|
+
if (argv[0] !== 'init') fail(`unknown command: ${argv[0]}\n\n${USAGE}`);
|
|
40
|
+
|
|
41
|
+
const useDefaults = argv.includes('--yes') || argv.includes('-y');
|
|
42
|
+
const skipInstall = argv.includes('--no-install');
|
|
43
|
+
const positional = argv.slice(1).find((arg) => !arg.startsWith('-'));
|
|
44
|
+
|
|
45
|
+
// 聞ける相手がいないなら、既定の値で進めない。readline は端末でないとき、質問を出す前に入力を
|
|
46
|
+
// 読み切って閉じるので、パイプで答えを流す形は支えない。黙って既定の値で作るより、--yes を促して落ちる
|
|
47
|
+
if (!useDefaults && !process.stdin.isTTY) fail('no terminal to ask on. Pass --yes to take every default.');
|
|
48
|
+
|
|
49
|
+
await init();
|
|
50
|
+
|
|
51
|
+
async function init() {
|
|
52
|
+
const rl = useDefaults ? null : createInterface({ input: process.stdin, output: process.stdout });
|
|
53
|
+
rl?.on('SIGINT', cancel);
|
|
54
|
+
const ask = async (question, fallback) => {
|
|
55
|
+
if (rl === null) return fallback;
|
|
56
|
+
const answer = (await rl.question(`${question} (${fallback}) `).catch(cancel)).trim();
|
|
57
|
+
return answer === '' ? fallback : answer;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const directory = positional ?? (await ask('Directory', 'my-wiki'));
|
|
62
|
+
const target = resolve(process.cwd(), directory);
|
|
63
|
+
await checkEmpty(target, directory);
|
|
64
|
+
|
|
65
|
+
// Worker、D1、R2 の名前は、ディレクトリ名(パスの最後の部分)から作る
|
|
66
|
+
const slug = slugify(basename(target));
|
|
67
|
+
const siteName = await ask('Site name', 'My wiki');
|
|
68
|
+
let defaultLang;
|
|
69
|
+
for (;;) {
|
|
70
|
+
defaultLang = await ask(`Default language (${LANGS.join(' / ')})`, 'en');
|
|
71
|
+
if (LANGS.includes(defaultLang)) break;
|
|
72
|
+
// --yes では同じ既定の値が返り続けるので、回り続けない
|
|
73
|
+
if (rl === null) fail(`unknown language: ${defaultLang}`);
|
|
74
|
+
process.stdout.write(` choose one of ${LANGS.join(', ')}\n`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await cp(template, target, { recursive: true, filter: packableOnly });
|
|
78
|
+
await restoreGitignore(target);
|
|
79
|
+
await fill(target, { slug, siteName, defaultLang });
|
|
80
|
+
// ローカル開発の主体。.dev.vars は .gitignore に入っている
|
|
81
|
+
await cp(join(target, '.dev.vars.example'), join(target, '.dev.vars'));
|
|
82
|
+
|
|
83
|
+
process.stdout.write(`\nCreated ${target}\n`);
|
|
84
|
+
const install = skipInstall
|
|
85
|
+
? false
|
|
86
|
+
: (await ask('Install dependencies now? (y/n)', 'y')).toLowerCase().startsWith('y');
|
|
87
|
+
rl?.close();
|
|
88
|
+
if (install) await npmInstall(target);
|
|
89
|
+
printNextSteps(directory, install);
|
|
90
|
+
} finally {
|
|
91
|
+
rl?.close();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 雛形に値を埋める。埋める場所は 1 つずつ名指しする(replaceOnce が見つからなければ落ちる) */
|
|
96
|
+
async function fill(target, { slug, siteName, defaultLang }) {
|
|
97
|
+
await replaceEach(join(target, 'src', 'config.ts'), [
|
|
98
|
+
[" siteName: 'My wiki',", ` siteName: ${quote(siteName)},`],
|
|
99
|
+
[" defaultLang: 'en',", ` defaultLang: ${quote(defaultLang)},`],
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
await replaceEach(join(target, 'wrangler.jsonc'), [
|
|
103
|
+
['"name": "my-wiki",', `"name": "${slug}",`],
|
|
104
|
+
['"database_name": "my-wiki",', `"database_name": "${slug}",`],
|
|
105
|
+
['"bucket_name": "my-wiki-images"', `"bucket_name": "${slug}-images"`],
|
|
106
|
+
['https://my-wiki.<account>', `https://${slug}.<account>`],
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
// 自分と同じ版を指す。雛形に書いてある範囲は、雛形を直したときのものなので、古い版を指しうる。
|
|
110
|
+
// 今の範囲を雛形から読んでから、名指しで置き換える(正規表現で当てると、書き方が変わった日に
|
|
111
|
+
// 0 件の置き換えで素通りする)
|
|
112
|
+
const manifest = join(target, 'package.json');
|
|
113
|
+
const current = JSON.parse(await readFile(manifest, 'utf8')).dependencies['@kanf/wema-kake'];
|
|
114
|
+
await replaceEach(manifest, [
|
|
115
|
+
['"name": "my-wiki",', `"name": "${slug}",`],
|
|
116
|
+
[`"@kanf/wema-kake": "${current}"`, `"@kanf/wema-kake": "^${await version()}"`],
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
await replaceEach(join(target, 'README.md'), [['# my-wiki', `# ${siteName}`]]);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** ファイルの中の `[from, to]` を、順に 1 か所ずつ置き換える */
|
|
123
|
+
async function replaceEach(path, pairs) {
|
|
124
|
+
const source = await readFile(path, 'utf8');
|
|
125
|
+
await writeFile(path, pairs.reduce((text, [from, to]) => replaceOnce(text, from, to), source));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function npmInstall(target) {
|
|
129
|
+
process.stdout.write('\n');
|
|
130
|
+
const code = await new Promise((done) => {
|
|
131
|
+
const npm = spawn('npm', ['install'], { cwd: target, stdio: 'inherit', shell: process.platform === 'win32' });
|
|
132
|
+
npm.on('error', () => done(1));
|
|
133
|
+
npm.on('close', (status) => done(status ?? 1));
|
|
134
|
+
});
|
|
135
|
+
if (code !== 0) fail('npm install failed. Run it yourself in the new directory.');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function printNextSteps(directory, installed) {
|
|
139
|
+
// 空白などを含む名前は、そのまま貼り付けて通るように引用符で囲む
|
|
140
|
+
const cd = /^[\w./-]+$/.test(directory) ? directory : `'${directory.replaceAll("'", "'\\''")}'`;
|
|
141
|
+
const steps = [`cd ${cd}`, ...(installed ? [] : ['npm install']), 'npm run dev # http://localhost:8787'];
|
|
142
|
+
process.stdout.write(`\nNext:\n\n${steps.map((step) => ` ${step}`).join('\n')}
|
|
143
|
+
|
|
144
|
+
To deploy:
|
|
145
|
+
|
|
146
|
+
npx wrangler login
|
|
147
|
+
npm run deploy:first
|
|
148
|
+
|
|
149
|
+
Then set up Cloudflare Access ("Access の設定" in the README). Until then the wiki answers 500.\n`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 空でないディレクトリには書かない。上書きの意味が人によって違うので、ここで止める。
|
|
154
|
+
* ディレクトリはまだ作らない(質問の途中で抜けたときに、空のディレクトリを残さない)。書き出す cp が作る
|
|
155
|
+
*/
|
|
156
|
+
async function checkEmpty(target, directory) {
|
|
157
|
+
let entries;
|
|
158
|
+
try {
|
|
159
|
+
entries = await readdir(target);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (error?.code !== 'ENOENT') fail(`cannot write to ${directory} (${error instanceof Error ? error.message : error})`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (entries.length > 0) fail(`${directory} is not empty`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 無視の設定を .gitignore に戻す。npm は .gitignore をパッケージに入れないので、配るときは
|
|
169
|
+
* gitignore という名前の複製を入れてある(scripts/pack-template.mjs)。repo から直に走らせたときは
|
|
170
|
+
* .gitignore がそのまま来るので、何もしない
|
|
171
|
+
*/
|
|
172
|
+
async function restoreGitignore(target) {
|
|
173
|
+
const ignore = join(target, '.gitignore');
|
|
174
|
+
try {
|
|
175
|
+
await rename(join(target, 'gitignore'), ignore);
|
|
176
|
+
} catch {
|
|
177
|
+
// 複製がないのは、repo から走らせたとき(.gitignore がある)か、prepack を通していない tarball
|
|
178
|
+
// から走らせたとき。後者を黙って通すと、無視の設定がない木ができる
|
|
179
|
+
await stat(ignore).catch(() => fail('the template has no .gitignore (was it packed without prepack?)'));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** 生成物と手元の物は持っていかない(repo から直に走らせたときのため) */
|
|
184
|
+
function packableOnly(source) {
|
|
185
|
+
return !/[\\/](node_modules|\.wrangler|package-lock\.json|\.dev\.vars|worker-configuration\.d\.ts)$/.test(source);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** 1 か所だけ置き換える。見つからないか、2 か所以上あれば落とす(雛形を直した日に黙って素通りしない) */
|
|
189
|
+
function replaceOnce(source, from, to) {
|
|
190
|
+
const at = source.indexOf(from);
|
|
191
|
+
if (at === -1) fail(`the template no longer contains: ${from}`);
|
|
192
|
+
if (source.indexOf(from, at + from.length) !== -1) fail(`the template repeats: ${from}`);
|
|
193
|
+
return `${source.slice(0, at)}${to}${source.slice(at + from.length)}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** TypeScript の文字列として書ける形にする */
|
|
197
|
+
function quote(value) {
|
|
198
|
+
return `'${value.replaceAll('\\', '\\\\').replaceAll("'", "\\'")}'`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Worker、D1、R2 の名前に使える形 */
|
|
202
|
+
function slugify(name) {
|
|
203
|
+
const slug = name
|
|
204
|
+
.toLowerCase()
|
|
205
|
+
.replace(/[^a-z0-9-]+/g, '-')
|
|
206
|
+
.slice(0, 54)
|
|
207
|
+
// 切り詰めた後に削る(先に削ると、切った位置の `-` が末尾に残る。Worker の名前は `-` で終われない)
|
|
208
|
+
.replace(/^-+|-+$/g, '');
|
|
209
|
+
return slug === '' ? 'my-wiki' : slug;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function version() {
|
|
213
|
+
return JSON.parse(await readFile(join(root, 'package.json'), 'utf8')).version;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** 途中で抜けた人向け。失敗ではないので、理由を並べずに終わる */
|
|
217
|
+
function cancel() {
|
|
218
|
+
process.stdout.write('\ncancelled\n');
|
|
219
|
+
process.exit(130);
|
|
220
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { WemaKakeConfig, WemaKakeEnv } from './worker/config';
|
|
2
|
+
export type { Lang } from './shared/i18n';
|
|
3
|
+
export type { WemaKakeConfig, WemaKakeEnv } from './worker/config';
|
|
4
|
+
/**
|
|
5
|
+
* ページごとの Durable Object。**利用側の Worker の入口から、この名前で再 export すること**
|
|
6
|
+
* (Durable Object のクラスは、Worker の入口から export されている必要がある)。
|
|
7
|
+
* クラス名は、利用側の `wrangler.jsonc`(`durable_objects` と `migrations`)にも書かれる
|
|
8
|
+
*/
|
|
9
|
+
export { PageDO } from './worker/page-do';
|
|
10
|
+
/**
|
|
11
|
+
* Worker の入口(`export default` にするもの)を作る。設定の不正は、ここで例外にする
|
|
12
|
+
*/
|
|
13
|
+
export declare function createWemaKake(config?: WemaKakeConfig): ExportedHandler<WemaKakeEnv>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createApp } from './worker/index';
|
|
2
|
+
/**
|
|
3
|
+
* ページごとの Durable Object。**利用側の Worker の入口から、この名前で再 export すること**
|
|
4
|
+
* (Durable Object のクラスは、Worker の入口から export されている必要がある)。
|
|
5
|
+
* クラス名は、利用側の `wrangler.jsonc`(`durable_objects` と `migrations`)にも書かれる
|
|
6
|
+
*/
|
|
7
|
+
export { PageDO } from './worker/page-do';
|
|
8
|
+
/**
|
|
9
|
+
* Worker の入口(`export default` にするもの)を作る。設定の不正は、ここで例外にする
|
|
10
|
+
*/
|
|
11
|
+
export function createWemaKake(config = {}) {
|
|
12
|
+
const app = createApp(config);
|
|
13
|
+
return { fetch: (request, env, ctx) => app.fetch(request, env, ctx) };
|
|
14
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/** ページの一覧で 1 回に返す件数の上限 */
|
|
2
|
+
export declare const MAX_LIST_LIMIT = 500;
|
|
3
|
+
/** ページを変更してから、D1 の索引へ反映するまでの時間 */
|
|
4
|
+
export declare const INDEX_DELAY_MS = 5000;
|
|
5
|
+
/** 表示名の長さの上限 */
|
|
6
|
+
export declare const MAX_TITLE_LENGTH = 200;
|
|
7
|
+
/**
|
|
8
|
+
* ページに付けられる色。wema の付箋の色と同じ並び(子ページの付箋と、一覧の付箋の色になる)。
|
|
9
|
+
* 付けていないページは null
|
|
10
|
+
*/
|
|
11
|
+
export declare const PAGE_COLORS: readonly ['#FFF9C4', '#FFCDD2', '#FFE0B2', '#E1BEE7', '#BBDEFB', '#B2DFDB', '#C8E6C9', '#F5F5F5'];
|
|
12
|
+
export type PageColor = (typeof PAGE_COLORS)[number];
|
|
13
|
+
/** 色を付けていないページの、付箋(子ページの付箋、一覧の付箋)の色 */
|
|
14
|
+
export declare const UNSET_PAGE_COLOR: PageColor;
|
|
15
|
+
/** 検索語の長さの上限 */
|
|
16
|
+
export declare const MAX_QUERY_LENGTH = 200;
|
|
17
|
+
/** 1 人が付けられるブックマークの数 */
|
|
18
|
+
export declare const MAX_BOOKMARKS = 100;
|
|
19
|
+
/**
|
|
20
|
+
* 一覧のボードの付箋に出す、本文の冒頭の長さ(文字数)。
|
|
21
|
+
* 付箋の大きさ(src/web/index-board.ts)に収まる量にしてある
|
|
22
|
+
*/
|
|
23
|
+
export declare const INDEX_EXCERPT_LENGTH = 60;
|
|
24
|
+
/** `GET /api/index` が返すページ */
|
|
25
|
+
export interface IndexPage {
|
|
26
|
+
name: string;
|
|
27
|
+
title: string | null;
|
|
28
|
+
/** ページの色(PAGE_COLORS のどれか)。付けていなければ null */
|
|
29
|
+
color: string | null;
|
|
30
|
+
note_count: number;
|
|
31
|
+
updated_at: number;
|
|
32
|
+
/** 本文の冒頭(INDEX_EXCERPT_LENGTH 文字まで) */
|
|
33
|
+
excerpt: string;
|
|
34
|
+
/** 直接の子ページの数 */
|
|
35
|
+
child_count: number;
|
|
36
|
+
}
|
|
37
|
+
/** `POST /api/pages-info` が返す、ページの概要(子ページの付箋の表示に使う) */
|
|
38
|
+
export interface PageSummary {
|
|
39
|
+
name: string;
|
|
40
|
+
title: string | null;
|
|
41
|
+
/** ページの色(PAGE_COLORS のどれか)。付けていなければ null */
|
|
42
|
+
color: string | null;
|
|
43
|
+
note_count: number;
|
|
44
|
+
/** 親ページのスラッグ。ルートのページなら null */
|
|
45
|
+
parent: string | null;
|
|
46
|
+
/** 付箋の配置(`[[x, y, 幅, 高さ, 色], ...]` の JSON)。索引がまだなら null */
|
|
47
|
+
layout: string | null;
|
|
48
|
+
}
|
|
49
|
+
/** `GET /api/search?roots=1` が返す、一致したページと、そのルート */
|
|
50
|
+
export interface RootHit {
|
|
51
|
+
/** 一致したページ */
|
|
52
|
+
name: string;
|
|
53
|
+
/** そのページのルート。ルートのページ自身が一致したなら、`name` と同じ */
|
|
54
|
+
root: string;
|
|
55
|
+
}
|
|
56
|
+
/** `GET /api/index` が返すリンク */
|
|
57
|
+
export interface IndexLink {
|
|
58
|
+
from_page: string;
|
|
59
|
+
to_page: string;
|
|
60
|
+
/** リンク先のページがまだ作られていなければ 1 */
|
|
61
|
+
missing: number;
|
|
62
|
+
}
|
|
63
|
+
/** 記録済みの操作 1 件の概要(一覧に出すもの) */
|
|
64
|
+
export interface OpSummary {
|
|
65
|
+
seq: number;
|
|
66
|
+
actor: string;
|
|
67
|
+
summary: string | null;
|
|
68
|
+
/** この操作が取り消しなら、取り消した対象の seq。取り消しでなければ null */
|
|
69
|
+
reverts: number | null;
|
|
70
|
+
/** この操作を取り消した操作の seq。取り消されていなければ null */
|
|
71
|
+
revertedBy: number | null;
|
|
72
|
+
createdAt: number;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 取り消さなかった理由。
|
|
76
|
+
* - modified: その後に変更されていた(一部のフィールドだけ変更されていた場合は、残りは取り消している)
|
|
77
|
+
* - connected: 付箋の作成を取り消そうとしたが、取り消さない接続線がつながっている
|
|
78
|
+
* - deleted: 更新を取り消そうとしたが、対象がもうない
|
|
79
|
+
* - exists: 削除を取り消そうとしたが、同じ id のものがある
|
|
80
|
+
* - endpoint-missing: 接続線の削除を取り消そうとしたが、両端の付箋がない
|
|
81
|
+
*/
|
|
82
|
+
export type SkipReason = 'modified' | 'connected' | 'deleted' | 'exists' | 'endpoint-missing';
|
|
83
|
+
/** 取り消さなかったものと、その理由 */
|
|
84
|
+
export interface Skipped {
|
|
85
|
+
target: 'note' | 'edge';
|
|
86
|
+
id: string;
|
|
87
|
+
reason: SkipReason;
|
|
88
|
+
}
|
|
89
|
+
/** 取り消しの結果 */
|
|
90
|
+
export interface RevertOutcome {
|
|
91
|
+
/** 取り消しとして記録した操作の seq。取り消せるものが 1 つもなかった場合は null */
|
|
92
|
+
seq: number | null;
|
|
93
|
+
/** 取り消したデルタの数 */
|
|
94
|
+
applied: number;
|
|
95
|
+
/** その後に変更されていたなどの理由で、取り消さなかったもの */
|
|
96
|
+
skipped: Skipped[];
|
|
97
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// サーバーとブラウザの両方が使う、HTTP API と DO のメソッドの型と定数。
|
|
2
|
+
/** ページの一覧で 1 回に返す件数の上限 */
|
|
3
|
+
export const MAX_LIST_LIMIT = 500;
|
|
4
|
+
/** ページを変更してから、D1 の索引へ反映するまでの時間 */
|
|
5
|
+
export const INDEX_DELAY_MS = 5000;
|
|
6
|
+
/** 表示名の長さの上限 */
|
|
7
|
+
export const MAX_TITLE_LENGTH = 200;
|
|
8
|
+
/**
|
|
9
|
+
* ページに付けられる色。wema の付箋の色と同じ並び(子ページの付箋と、一覧の付箋の色になる)。
|
|
10
|
+
* 付けていないページは null
|
|
11
|
+
*/
|
|
12
|
+
export const PAGE_COLORS = ['#FFF9C4', '#FFCDD2', '#FFE0B2', '#E1BEE7', '#BBDEFB', '#B2DFDB', '#C8E6C9', '#F5F5F5'];
|
|
13
|
+
/** 色を付けていないページの、付箋(子ページの付箋、一覧の付箋)の色 */
|
|
14
|
+
export const UNSET_PAGE_COLOR = '#FFF9C4';
|
|
15
|
+
/** 検索語の長さの上限 */
|
|
16
|
+
export const MAX_QUERY_LENGTH = 200;
|
|
17
|
+
/** 1 人が付けられるブックマークの数 */
|
|
18
|
+
export const MAX_BOOKMARKS = 100;
|
|
19
|
+
/**
|
|
20
|
+
* 一覧のボードの付箋に出す、本文の冒頭の長さ(文字数)。
|
|
21
|
+
* 付箋の大きさ(src/web/index-board.ts)に収まる量にしてある
|
|
22
|
+
*/
|
|
23
|
+
export const INDEX_EXCERPT_LENGTH = 60;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { HistoryDelta, WemaBoardData, WemaEdge, WemaNote } from '@kanf/wema';
|
|
2
|
+
import type { NoteInfos } from './note-info';
|
|
3
|
+
export type { HistoryDelta, WemaEdge, WemaNote };
|
|
4
|
+
/** 操作を打ち消すデルタ。逆順にし、作成と削除、before と after を入れ替える */
|
|
5
|
+
export declare function invertDeltas(deltas: HistoryDelta[]): HistoryDelta[];
|
|
6
|
+
/** viewport は各クライアントの表示状態なので保存しない */
|
|
7
|
+
export type BoardData = Omit<WemaBoardData, 'viewport'>;
|
|
8
|
+
/** 付箋と接続線の一部(拒否したときに返す現在値など) */
|
|
9
|
+
export type BoardContent = Pick<BoardData, 'notes' | 'edges'>;
|
|
10
|
+
export interface Snapshot {
|
|
11
|
+
seq: number;
|
|
12
|
+
/** 表示名。未設定なら null(表示にはスラッグを使う) */
|
|
13
|
+
title: string | null;
|
|
14
|
+
/** ページの色(PAGE_COLORS のどれか)。付けていなければ null */
|
|
15
|
+
color: string | null;
|
|
16
|
+
/**
|
|
17
|
+
* ページを作るたびに変わる値。まだ作られていなければ null。
|
|
18
|
+
* 削除して作り直したページを、前のページと見分けるのに使う
|
|
19
|
+
*/
|
|
20
|
+
epoch: string | null;
|
|
21
|
+
data: BoardData;
|
|
22
|
+
/** 付箋ごとの付加情報(作成者、更新日時)。`data` の付箋と同じ id を持つ */
|
|
23
|
+
info: NoteInfos;
|
|
24
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** 操作を打ち消すデルタ。逆順にし、作成と削除、before と after を入れ替える */
|
|
2
|
+
export function invertDeltas(deltas) {
|
|
3
|
+
return [...deltas].reverse().map((d) => {
|
|
4
|
+
switch (d.type) {
|
|
5
|
+
case 'note:create':
|
|
6
|
+
return { type: 'note:delete', note: d.note };
|
|
7
|
+
case 'note:delete':
|
|
8
|
+
return { type: 'note:create', note: d.note };
|
|
9
|
+
case 'edge:create':
|
|
10
|
+
return { type: 'edge:delete', edge: d.edge };
|
|
11
|
+
case 'edge:delete':
|
|
12
|
+
return { type: 'edge:create', edge: d.edge };
|
|
13
|
+
case 'note:update':
|
|
14
|
+
return { type: d.type, noteId: d.noteId, before: d.after, after: d.before };
|
|
15
|
+
case 'edge:update':
|
|
16
|
+
return { type: d.type, edgeId: d.edgeId, before: d.after, after: d.before };
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { BoardContent } from './delta';
|
|
2
|
+
import type { Lang } from './i18n';
|
|
3
|
+
/** 最初のページのスラッグ */
|
|
4
|
+
export declare const FIRST_PAGE_SLUG = "first-page";
|
|
5
|
+
/** 付箋の名前。接続線が指す名前で、付箋の id にもなる */
|
|
6
|
+
type Key = 'sample' | 'intro' | 'notes' | 'text' | 'edges' | 'view' | 'pages' | 'llm';
|
|
7
|
+
/** 1 つの言語の文面 */
|
|
8
|
+
interface GuideText {
|
|
9
|
+
/** 最初のページの表示名 */
|
|
10
|
+
firstPageTitle: string;
|
|
11
|
+
/** 案内の付箋。最初のページ用と、使い方のボード用 */
|
|
12
|
+
intro: {
|
|
13
|
+
first: string;
|
|
14
|
+
help: string;
|
|
15
|
+
};
|
|
16
|
+
/** 本文の装飾の見本 */
|
|
17
|
+
sample: string;
|
|
18
|
+
/** 見出しと、項目の並び */
|
|
19
|
+
sections: Record<Exclude<Key, 'sample' | 'intro'>, [heading: string, items: string[]]>;
|
|
20
|
+
/**
|
|
21
|
+
* 列の間隔。付箋は autoSize なので、幅は最も長い行で決まる。**長い行を足すと、隣の付箋と重なる。**
|
|
22
|
+
* 1 行を短く保つか、ここを広げる
|
|
23
|
+
*/
|
|
24
|
+
column: number;
|
|
25
|
+
}
|
|
26
|
+
/** 最初のページの表示名 */
|
|
27
|
+
export declare const firstPageTitle: (lang: Lang) => string;
|
|
28
|
+
/** 案内の付箋の id。付箋を縦に積む画面で、先頭に並べるのに使う(src/web/stacked-view.ts) */
|
|
29
|
+
export declare const GUIDE_INTRO_ID: string;
|
|
30
|
+
/** 使い方の付箋と接続線。`intro` で、案内の付箋の文面を選ぶ */
|
|
31
|
+
export declare function guideBoard(intro: keyof GuideText['intro'], lang: Lang): BoardContent;
|
|
32
|
+
export {};
|