mmm-mcp-server 0.1.1 → 0.1.2
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/README.en.md +228 -0
- package/README.md +2 -0
- package/dist/src/domain/levels.js +3 -1
- package/dist/src/domain/levelsMode.js +8 -7
- package/dist/src/server.js +1 -1
- package/dist/src/tools/autoStructureThought.js +1 -1
- package/dist/src/tools/deleteElements.js +8 -4
- package/dist/src/tools/editElement.js +1 -1
- package/dist/src/tools/importMarkdownContext.js +1 -1
- package/dist/src/tools/listElements.js +6 -1
- package/dist/src/tools/moveElement.js +7 -3
- package/dist/src/tools/updateTaskStatus.js +1 -1
- package/package.json +1 -1
package/README.en.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# mmm-mcp-server
|
|
2
|
+
|
|
3
|
+
[日本語](README.md) | English
|
|
4
|
+
|
|
5
|
+
A tool that lets you read and write your own maps in Matryoshka Mind Map (a mind map / TODO app) directly from conversations with AI agents. Because it is built on the Model Context Protocol (MCP) open standard, it works with any MCP-compatible AI agent, not just Claude Desktop and Claude Code (**tested on Claude Desktop and Claude Code**; for other clients, see their documentation on how to register an MCP server).
|
|
6
|
+
|
|
7
|
+
- Say "Show me my current maps" and it lists your maps.
|
|
8
|
+
- Turn ideas from your conversation directly into a hierarchical mind map.
|
|
9
|
+
- Just say "I finished task X" to check it off your TODO list.
|
|
10
|
+
- Import long notes or Markdown straight into a map.
|
|
11
|
+
|
|
12
|
+
This tool runs entirely on your computer, with no always-on server. It starts only when needed and accesses your own maps with your own login credentials (the developer cannot see your data).
|
|
13
|
+
|
|
14
|
+
> **For users who installed via `git clone` before September 30, 2026**: Due to a data format migration in the app, older versions will fail when writing to maps. Please replace the registration in Step 5 below with `npx -y mmm-mcp-server` (you do not need to log in again). If you want to keep using your cloned copy, run `git pull && npm install && npm run build` in the `mmm_mcp` folder and restart your AI agent.
|
|
15
|
+
|
|
16
|
+
> This guide walks you through every step, starting with how to open the terminal (the window where you type commands), so that you can follow along even if you are not used to working with a computer.
|
|
17
|
+
>
|
|
18
|
+
> **Supported OS**: It should work on both macOS and Windows. It runs purely on Node.js and does not depend on OS-specific features. However, **it has only been tested on real machines running macOS.** If you notice anything while trying it on Windows, please let us know via an Issue in this repository. Steps that differ by OS (opening the terminal and installing Node.js) are described separately for macOS and Windows. All other commands are the same on both.
|
|
19
|
+
|
|
20
|
+
## 1. Prerequisites
|
|
21
|
+
|
|
22
|
+
- A Matryoshka Mind Map account (**anonymous-only accounts cannot be used.** Make sure you can log in with Google, Apple, or an email address. You can also link one later with the "Link email address" feature in the app).
|
|
23
|
+
- An MCP-compatible AI agent (tested with Claude Desktop / Claude Code; other MCP-compatible clients should generally work too).
|
|
24
|
+
- Node.js (the runtime that runs the tool; you will install it in Step 3).
|
|
25
|
+
|
|
26
|
+
## 2. Open the Terminal
|
|
27
|
+
|
|
28
|
+
### On macOS
|
|
29
|
+
|
|
30
|
+
1. Press `Command` + `Space` on your keyboard (opens Spotlight search).
|
|
31
|
+
2. Type "Terminal".
|
|
32
|
+
3. Click "Terminal" in the results, or press Enter.
|
|
33
|
+
|
|
34
|
+
### On Windows
|
|
35
|
+
|
|
36
|
+
1. Press the `Windows` key on your keyboard (opens Start menu search).
|
|
37
|
+
2. Type "PowerShell".
|
|
38
|
+
3. Click "Windows PowerShell" in the results, or press Enter.
|
|
39
|
+
|
|
40
|
+
Once a dark (or light) window opens, you are ready. For the rest of the steps, paste the commands into this window one line at a time and press Enter (the commands themselves are the same on macOS and Windows).
|
|
41
|
+
|
|
42
|
+
## 3. Install Node.js
|
|
43
|
+
|
|
44
|
+
Check whether Node.js is already installed with this command:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
node --version
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
If a version number of `v18` or higher is displayed (e.g., `v20.11.0`), it is already installed. If you see an error like "command not found", download the installer from:
|
|
51
|
+
|
|
52
|
+
https://nodejs.org/
|
|
53
|
+
|
|
54
|
+
The site shows two large buttons. Click the one labeled **"LTS"** to download (the `.pkg` installer for macOS or the `.msi` installer for Windows is chosen automatically for your OS). Double-click the downloaded file and follow the on-screen instructions (on macOS: "Continue" → "Agree" → "Install"; on Windows: "Next" → "I accept..." → "Install").
|
|
55
|
+
|
|
56
|
+
When the installation is finished, go back to the terminal and check again:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
node --version
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 4. Log In
|
|
63
|
+
|
|
64
|
+
Choose one of the following three options based on how you usually log in to the Matryoshka Mind Map app. **None of these methods require any setup on your part beforehand.** The first run downloads the tool, so it may take a moment.
|
|
65
|
+
|
|
66
|
+
### If you log in with Google
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx -y -p mmm-mcp-server mmm-login login --method google
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Your browser opens automatically; log in with the Google account you usually use.
|
|
73
|
+
|
|
74
|
+
### If you log in with Apple
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx -y -p mmm-mcp-server mmm-login login --method apple
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Your browser opens automatically; log in with your Apple ID.
|
|
81
|
+
|
|
82
|
+
### If you log in with an email address and password
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npx -y -p mmm-mcp-server mmm-login login --method email
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
You will be asked for your email address and password (the password is not shown on screen).
|
|
89
|
+
|
|
90
|
+
> **If you get a "wrong password" error**, it does not necessarily mean you mistyped your password. If your account signs in to the app with Google or Apple and has no email/password credentials at all, you get the same error. If that may be the case, try the "Google" or "Apple" steps above.
|
|
91
|
+
|
|
92
|
+
Once you have logged in, you do not need to log in again each time (your credentials are stored only on your computer, in a form that only you can read).
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npx -y -p mmm-mcp-server mmm-login status
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
shows your login status. To log out:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npx -y -p mmm-mcp-server mmm-login logout
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## 5. Register It with Your AI Agent
|
|
105
|
+
|
|
106
|
+
Below are the instructions for Claude Desktop and Claude Code, which have been tested. If you use another MCP-compatible client (e.g., Codex), in most cases you only need to register the server launch command (`npx -y mmm-mcp-server`) in that client's MCP server settings. See your client's documentation for the exact steps.
|
|
107
|
+
|
|
108
|
+
### Claude Code
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
claude mcp add mmm -- npx -y mmm-mcp-server
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Claude Desktop
|
|
115
|
+
|
|
116
|
+
Open the Claude Desktop configuration file (`claude_desktop_config.json`) and add the following under `mcpServers`:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"mcpServers": {
|
|
121
|
+
"mmm": {
|
|
122
|
+
"command": "npx",
|
|
123
|
+
"args": ["-y", "mmm-mcp-server"]
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Save the file and restart Claude Desktop.
|
|
130
|
+
|
|
131
|
+
## 6. Try It Out
|
|
132
|
+
|
|
133
|
+
Try talking to your AI agent like this:
|
|
134
|
+
|
|
135
|
+
- "Show me my Matryoshka maps."
|
|
136
|
+
- "Add a task called [task] to the [map name] map."
|
|
137
|
+
- "I finished the [task] task in the [map name] map."
|
|
138
|
+
- "I'll paste my notes in Markdown; import them into the [map name] map."
|
|
139
|
+
|
|
140
|
+
**Only the first time the contents of a map (the element text) are read**, the tool asks for your permission before returning the actual contents. This is out of consideration for personal information that the contents may contain.
|
|
141
|
+
|
|
142
|
+
Instead of the contents, the tool returns only an instruction: "confirm directly with the user, then call again with `confirmed: true`." This is best-effort: the server has no way to verify whether the agent actually asked you, so whether the confirmation happens properly depends on the AI agent you use. Once confirmed, the same map is not confirmed again while that server is running (roughly one conversation).
|
|
143
|
+
|
|
144
|
+
(Previously, MCP's standard confirmation dialog, "elicitation", was also used to enforce this. However, some clients advertised support without actually showing a dialog, so contents became unreadable without the user ever seeing a confirmation screen. It was removed on 2026-09-03 in favor of the approach above.)
|
|
145
|
+
|
|
146
|
+
## Keeping Your Maps Tidy
|
|
147
|
+
|
|
148
|
+
Letting an AI agent write your maps is convenient, but left alone the maps tend to become hard to read. Paste the following into the instruction file your AI agent loads every time (for Claude Code, `~/.claude/CLAUDE.md`) to keep the writing style consistent:
|
|
149
|
+
|
|
150
|
+
```markdown
|
|
151
|
+
## How to write in mmm
|
|
152
|
+
|
|
153
|
+
- Keep each element short enough to read in one line. Do not pack in explanations.
|
|
154
|
+
If you want to keep reasons or notes, put them one level down as child elements.
|
|
155
|
+
- Put a URL in its own element. Do not mix it with text like "Docs here: https://...".
|
|
156
|
+
Create an element named "Docs" and put only the URL as its child.
|
|
157
|
+
- Order reminder items chronologically. Name them by when to do them, such as
|
|
158
|
+
"Right after release", "In 2 weeks", or "When X happens".
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The reasons:
|
|
162
|
+
|
|
163
|
+
- **One line per element**: Matryoshka shows elements nested inside each other, so long elements hurt readability and lose the benefit of drilling down through the hierarchy.
|
|
164
|
+
- **Do not mix URLs with text**: A URL in the same element as other text is not treated as a link in the app, so tapping it does nothing.
|
|
165
|
+
- **Chronological order**: Compared with ordering by task name, checking items off in order makes it easier to notice anything you missed.
|
|
166
|
+
|
|
167
|
+
## Troubleshooting
|
|
168
|
+
|
|
169
|
+
| Symptom | Cause / Solution |
|
|
170
|
+
|---|---|
|
|
171
|
+
| `node --version` says "command not found" | Node.js from Step 3 is not installed yet. After installing, close the terminal and open it again. |
|
|
172
|
+
| "Wrong password" when logging in | See the note in Step 4. Try logging in with Google or Apple instead. |
|
|
173
|
+
| The agent does not operate on your maps when asked | Check that the registration in Step 5 is correct. Claude Desktop needs a restart after registration. |
|
|
174
|
+
| The agent will not show map contents / stops at the confirmation | Check that you answered "yes" when asked for permission to read. |
|
|
175
|
+
| The AI cannot load a large map | With several hundred elements or more, the `list_elements` response gets large and may not fit in one go. Some AI agents handle this by saving it to a file and reading only the parts they need. If it happens often, split the map (swiping left on a top-level element turns it and everything under it into a separate map). |
|
|
176
|
+
|
|
177
|
+
## Advanced Example: Recording Tasks per Folder Automatically
|
|
178
|
+
|
|
179
|
+
What follows is **just one example of how it can be used**. It is not a standard workflow recommended for everyone, but shows what becomes possible with this kind of setup. Feel free to ignore it if it does not suit you.
|
|
180
|
+
|
|
181
|
+
**Goal**: Pair each folder (project) you work on in Claude Code with a map of the same name, and have the AI agent record and update tasks that come up during work in that map in real time, without asking.
|
|
182
|
+
|
|
183
|
+
> Setting (2) below uses Claude Code's "global CLAUDE.md", an instruction file that is always loaded. If your AI agent has a similar feature for instructions loaded every time, the same idea should apply.
|
|
184
|
+
|
|
185
|
+
### Setup
|
|
186
|
+
|
|
187
|
+
**(1) Re-register the mmm server so it is available in every folder**
|
|
188
|
+
|
|
189
|
+
When registering with Claude Code in Step 5, the `mmm` tool is only available in the current folder by default. To use it no matter which folder you start `claude` in, register it with `--scope user`:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
claude mcp add mmm --scope user -- npx -y mmm-mcp-server
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**(2) Write the workflow rules in your global CLAUDE.md (`~/.claude/CLAUDE.md`)**
|
|
196
|
+
|
|
197
|
+
For example, add something like this:
|
|
198
|
+
|
|
199
|
+
```markdown
|
|
200
|
+
## Task management with mmm
|
|
201
|
+
|
|
202
|
+
Record tasks that come up in the working folder in mmm (the Matryoshka Mind Map
|
|
203
|
+
integration) and manage them in real time.
|
|
204
|
+
|
|
205
|
+
- Use the working folder name as the map title.
|
|
206
|
+
- Check with list_maps whether a map with the same name exists. If not, create it
|
|
207
|
+
right away with create_map(title=folder name, isTodo=true) without confirming
|
|
208
|
+
the title (confirmation is not required for this workflow only).
|
|
209
|
+
- Calling list_elements to see existing tasks asks for confirmation only the first
|
|
210
|
+
time the map is read in that process. After that, add and update tasks in real
|
|
211
|
+
time with auto_structure_thought / update_task_status.
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Why this works
|
|
215
|
+
|
|
216
|
+
- `create_map` takes the title directly as an argument; "confirm in the conversation before calling" is only a recommended behavior for AI agents written in the tool description. A more specific instruction in CLAUDE.md takes priority, so maps can be created automatically without confirmation.
|
|
217
|
+
- Only `list_elements`, which reads map contents, has confirmation enforced on the server side out of consideration for confidential information (see "Try It Out" above). This does not change with the per-folder workflow: confirmation happens once, the first time a map is read in that process.
|
|
218
|
+
|
|
219
|
+
### Caveats
|
|
220
|
+
|
|
221
|
+
- Maps are created automatically for every folder, so work-log style maps will mix in with your carefully curated mind maps. Some people may find this cluttered. Adjust it to suit you, for example by using a separate account or limiting it to certain folders.
|
|
222
|
+
- Skipping confirmation for `create_map` applies only when you spell out this workflow in your own CLAUDE.md. Without any setup, the agent confirms in the conversation before creating a map, as usual.
|
|
223
|
+
|
|
224
|
+
## About This Tool
|
|
225
|
+
|
|
226
|
+
- Your login credentials (refresh token) are stored only on your computer (macOS: `~/.config/mmm-mcp/credentials.json`, Windows: `C:\Users\<username>\.config\mmm-mcp\credentials.json`) and are never sent anywhere else.
|
|
227
|
+
- Your map contents are read from and written to your Firebase account directly, without going through any third-party server, including the developer's (only when logging in with Apple, the last step of login goes through a signing-only relay server, but your map contents never pass through it).
|
|
228
|
+
- There is no always-running process; it runs only while you are in a conversation with your AI agent.
|
package/README.md
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
// そのまま兄弟順。子を持つ要素だけドキュメントが存在する(無ければ葉)。
|
|
9
9
|
//
|
|
10
10
|
// MCPは「新形式のみ対応」(移行ロジックはDart側だけに持たせる)。levels/root
|
|
11
|
-
//
|
|
11
|
+
// が無いマップに対する操作は、切替前(bodiesとlevelsの両方へ書く間)だけ、
|
|
12
|
+
// アプリで一度開いてもらうよう案内して止める。切替後は空のマップとして扱う
|
|
13
|
+
// (levelsMode.tsのreadLevelsSnapshot)。
|
|
12
14
|
export class LevelsNotInitializedError extends Error {
|
|
13
15
|
constructor(title) {
|
|
14
16
|
super(`「${title}」はまだ新形式(levels)のデータがありません。` +
|
|
@@ -9,16 +9,17 @@
|
|
|
9
9
|
// アプリで足した要素が見つからず、bodiesを読み取り専用にするルールの適用後は
|
|
10
10
|
// コミットごと失敗する。呼び出しのたびに読み直すので、MCPサーバーを
|
|
11
11
|
// 再起動しなくても切替に追従する。
|
|
12
|
-
import { LevelsNotInitializedError } from "./levels.js";
|
|
13
12
|
import { levelsSnapshotFromDocs } from "./levelsOps.js";
|
|
14
13
|
export async function isLevelsOnly(client) {
|
|
15
14
|
const config = await client.getDocument("config/app");
|
|
16
15
|
return config?.data.migrationEnabled !== false;
|
|
17
16
|
}
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
/**
|
|
18
|
+
* levels全件を読む。levels/rootが無いマップは空のマップとして扱う
|
|
19
|
+
* (rootへの最初の書き込みは、存在しないことを前提条件にした新規作成になる)。
|
|
20
|
+
* 切替後はアプリでマップを開いてもrootが作られないので、「アプリで開いて」と
|
|
21
|
+
* 止めると中身の無い古いマップへ永久に書き込めなくなる。
|
|
22
|
+
*/
|
|
23
|
+
export async function readLevelsSnapshot(client, levelsPath) {
|
|
24
|
+
return levelsSnapshotFromDocs(await client.listDocuments(levelsPath));
|
|
24
25
|
}
|
package/dist/src/server.js
CHANGED
|
@@ -12,7 +12,7 @@ import { registerDeleteElements } from "./tools/deleteElements.js";
|
|
|
12
12
|
import { registerMoveElement } from "./tools/moveElement.js";
|
|
13
13
|
const server = new McpServer({
|
|
14
14
|
name: "matryoshka-mindmap",
|
|
15
|
-
version: "0.1.
|
|
15
|
+
version: "0.1.2",
|
|
16
16
|
}, {
|
|
17
17
|
instructions: "このサーバーはリアルタイム同期を行わない。各ツールは呼び出された瞬間の" +
|
|
18
18
|
"Firestoreの状態を返すだけで、アプリ側でユーザーが直接行った変更は" +
|
|
@@ -35,7 +35,7 @@ export function registerAutoStructureThought(server) {
|
|
|
35
35
|
if (await isLevelsOnly(client)) {
|
|
36
36
|
let levelsPathResult = "";
|
|
37
37
|
await client.runOptimistic(async () => {
|
|
38
|
-
const snapshot = await readLevelsSnapshot(client, levelsPath
|
|
38
|
+
const snapshot = await readLevelsSnapshot(client, levelsPath);
|
|
39
39
|
if (targetParentId !== null && !locateInLevels(snapshot, targetParentId)) {
|
|
40
40
|
throw new Error(`parentElementId(${targetParentId})がこのマップに見つかりません。list_elementsで確認してください。`);
|
|
41
41
|
}
|
|
@@ -23,10 +23,14 @@ export function registerDeleteElements(server) {
|
|
|
23
23
|
assertCanEditMap(map, session);
|
|
24
24
|
const levelsPath = levelsPathOf(map);
|
|
25
25
|
const bodiesPath = bodiesPathOf(map);
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
// 切替後はrootが無いマップを空のマップとして扱うので、この確認は切替前だけ。
|
|
27
|
+
const levelsOnly = await isLevelsOnly(client);
|
|
28
|
+
if (!levelsOnly) {
|
|
29
|
+
assertLevelsRootExists(await levelsDocExists(client, levelsPath, "root"), map.header.title);
|
|
30
|
+
}
|
|
31
|
+
if (levelsOnly) {
|
|
28
32
|
if (confirmed !== true) {
|
|
29
|
-
const snapshot = await readLevelsSnapshot(client, levelsPath
|
|
33
|
+
const snapshot = await readLevelsSnapshot(client, levelsPath);
|
|
30
34
|
const allTargets = new Set();
|
|
31
35
|
const targets = [];
|
|
32
36
|
for (const elementId of elementIds) {
|
|
@@ -61,7 +65,7 @@ export function registerDeleteElements(server) {
|
|
|
61
65
|
let levelsTotal = 0;
|
|
62
66
|
await client.runOptimistic(async () => {
|
|
63
67
|
levelsDeleted.length = 0;
|
|
64
|
-
const snapshot = await readLevelsSnapshot(client, levelsPath
|
|
68
|
+
const snapshot = await readLevelsSnapshot(client, levelsPath);
|
|
65
69
|
for (const elementId of elementIds) {
|
|
66
70
|
if (!locateInLevels(snapshot, elementId))
|
|
67
71
|
continue; // 既に削除済み(リトライ時など)
|
|
@@ -21,7 +21,7 @@ export function registerEditElement(server) {
|
|
|
21
21
|
if (await isLevelsOnly(client)) {
|
|
22
22
|
let levelsPathResult = null;
|
|
23
23
|
await client.runOptimistic(async () => {
|
|
24
|
-
const snapshot = await readLevelsSnapshot(client, levelsPath
|
|
24
|
+
const snapshot = await readLevelsSnapshot(client, levelsPath);
|
|
25
25
|
const { writes, found } = buildLevelsPatchWrites(levelsPath, snapshot, [
|
|
26
26
|
{ elementId, patch: { detail } },
|
|
27
27
|
]);
|
|
@@ -35,7 +35,7 @@ export function registerImportMarkdownContext(server) {
|
|
|
35
35
|
if (await isLevelsOnly(client)) {
|
|
36
36
|
let levelsPathResult = "";
|
|
37
37
|
await client.runOptimistic(async () => {
|
|
38
|
-
const snapshot = await readLevelsSnapshot(client, levelsPath
|
|
38
|
+
const snapshot = await readLevelsSnapshot(client, levelsPath);
|
|
39
39
|
if (targetParentId !== null && !locateInLevels(snapshot, targetParentId)) {
|
|
40
40
|
throw new Error(`parentElementId(${targetParentId})がこのマップに見つかりません。list_elementsで確認してください。`);
|
|
41
41
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { getContext } from "../mcpContext.js";
|
|
3
3
|
import { resolveMap, levelsPathOf } from "../domain/mapResolver.js";
|
|
4
|
+
import { isLevelsOnly } from "../domain/levelsMode.js";
|
|
4
5
|
import { assertLevelsInitialized, buildTreeFromLevels, readAllLevelDocs, } from "../domain/levels.js";
|
|
5
6
|
// ElementNodeの`done`を`checked`という名前で公開する(仕様書の用語に合わせる)。
|
|
6
7
|
function toChecked(nodes) {
|
|
@@ -78,7 +79,11 @@ export function registerListElements(server) {
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
const levelsById = await readAllLevelDocs(client, levelsPathOf(map));
|
|
81
|
-
|
|
82
|
+
// 切替前はrootが無ければ未移行(bodiesに中身があり得る)なので止める。
|
|
83
|
+
// 切替後はアプリで開いてもrootが作られないので、空のマップとして返す。
|
|
84
|
+
if (!(await isLevelsOnly(client))) {
|
|
85
|
+
assertLevelsInitialized(levelsById, map.header.title);
|
|
86
|
+
}
|
|
82
87
|
const tree = buildTreeFromLevels(levelsById);
|
|
83
88
|
return {
|
|
84
89
|
content: [
|
|
@@ -25,17 +25,21 @@ export function registerMoveElement(server) {
|
|
|
25
25
|
assertCanEditMap(map, session);
|
|
26
26
|
const levelsPath = levelsPathOf(map);
|
|
27
27
|
const bodiesPath = bodiesPathOf(map);
|
|
28
|
-
|
|
28
|
+
// 切替後はrootが無いマップを空のマップとして扱うので、この確認は切替前だけ。
|
|
29
|
+
const levelsOnly = await isLevelsOnly(client);
|
|
30
|
+
if (!levelsOnly) {
|
|
31
|
+
assertLevelsRootExists(await levelsDocExists(client, levelsPath, "root"), map.header.title);
|
|
32
|
+
}
|
|
29
33
|
if (elementId === newParentElementId) {
|
|
30
34
|
throw new Error("要素を自分自身の子にすることはできません。");
|
|
31
35
|
}
|
|
32
36
|
if (elementId === afterElementId) {
|
|
33
37
|
throw new Error("afterElementIdにelementId自身は指定できません。");
|
|
34
38
|
}
|
|
35
|
-
if (
|
|
39
|
+
if (levelsOnly) {
|
|
36
40
|
let levelsPathResult = null;
|
|
37
41
|
await client.runOptimistic(async () => {
|
|
38
|
-
const snapshot = await readLevelsSnapshot(client, levelsPath
|
|
42
|
+
const snapshot = await readLevelsSnapshot(client, levelsPath);
|
|
39
43
|
if (afterElementId !== null && !locateInLevels(snapshot, afterElementId)) {
|
|
40
44
|
throw new Error(`afterElementId(${afterElementId})がこのマップに見つかりません。`);
|
|
41
45
|
}
|
|
@@ -30,7 +30,7 @@ export function registerUpdateTaskStatus(server) {
|
|
|
30
30
|
if (await isLevelsOnly(client)) {
|
|
31
31
|
let levelsResults = [];
|
|
32
32
|
await client.runOptimistic(async () => {
|
|
33
|
-
const snapshot = await readLevelsSnapshot(client, levelsPath
|
|
33
|
+
const snapshot = await readLevelsSnapshot(client, levelsPath);
|
|
34
34
|
const { writes, found } = buildLevelsPatchWrites(levelsPath, snapshot, updates.map((u) => ({ elementId: u.elementId, patch: { done: u.checked } })));
|
|
35
35
|
levelsResults = updates.map((u) => ({
|
|
36
36
|
elementId: u.elementId,
|