markmap-cli 0.18.4 → 0.18.5-alpha.7

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.
@@ -0,0 +1 @@
1
+ export {};
package/dist/client.js ADDED
@@ -0,0 +1,91 @@
1
+ const { mm, markmap } = window;
2
+ const { cliOptions } = markmap;
3
+ const key = new URLSearchParams(window.location.search).get("key") || "";
4
+ const state = {
5
+ content: {
6
+ ts: 0,
7
+ value: void 0
8
+ },
9
+ line: {
10
+ ts: 0,
11
+ value: 0
12
+ }
13
+ };
14
+ const activeNodeOptions = {};
15
+ const highlightEl = document.createElement("div");
16
+ highlightEl.className = "markmap-highlight-area";
17
+ checkData();
18
+ async function checkData() {
19
+ var _a;
20
+ try {
21
+ const query = new URLSearchParams(
22
+ [
23
+ ["key", key],
24
+ ["content", state.content.ts],
25
+ ["line", state.line.ts]
26
+ ].map((pair) => pair.map((s) => `${s}`))
27
+ );
28
+ const resp = await fetch(`/~data?${query}`);
29
+ if (!resp.ok)
30
+ throw {
31
+ status: resp.status
32
+ };
33
+ const res = await resp.json();
34
+ if (res.content) {
35
+ const value = res.content.value;
36
+ mm.setOptions(markmap.deriveOptions((_a = value.frontmatter) == null ? void 0 : _a.markmap));
37
+ await mm.setData(value.root);
38
+ if (!state.content.ts) await mm.fit();
39
+ }
40
+ Object.assign(state, res);
41
+ if (res.line && state.content.value) {
42
+ await setCursor({ line: res.line.value });
43
+ }
44
+ setTimeout(checkData);
45
+ } catch (err) {
46
+ if (err.status !== 404) {
47
+ setTimeout(checkData, 1e3);
48
+ }
49
+ }
50
+ }
51
+ function findActiveNode({
52
+ line,
53
+ autoExpand = true
54
+ }) {
55
+ function dfs(node, ancestors = []) {
56
+ var _a, _b, _c;
57
+ const [start, end] = ((_b = (_a = node.payload) == null ? void 0 : _a.lines) == null ? void 0 : _b.split(",").map((s) => +s)) || [];
58
+ if (start >= 0 && start <= line && line < end) {
59
+ best = node;
60
+ bestAncestors = ancestors;
61
+ }
62
+ ancestors = [...ancestors, node];
63
+ (_c = node.children) == null ? void 0 : _c.forEach((child) => {
64
+ dfs(child, ancestors);
65
+ });
66
+ }
67
+ let best;
68
+ let bestAncestors = [];
69
+ dfs(state.content.value.root);
70
+ if (autoExpand) {
71
+ bestAncestors.forEach((node) => {
72
+ var _a;
73
+ if ((_a = node.payload) == null ? void 0 : _a.fold) {
74
+ node.payload.fold = 0;
75
+ }
76
+ });
77
+ }
78
+ return best;
79
+ }
80
+ async function setCursor(options) {
81
+ if (!state.content.value) return;
82
+ const node = findActiveNode(options);
83
+ await highlightNode(node);
84
+ }
85
+ async function highlightNode(node) {
86
+ await mm.setHighlight(node);
87
+ if (!node) return;
88
+ await mm[activeNodeOptions.placement === "center" ? "centerNode" : "ensureVisible"](node, {
89
+ bottom: cliOptions.toolbar ? 80 : 0
90
+ });
91
+ }
@@ -1,5 +1,29 @@
1
+ import { ServerType } from '@hono/node-server';
2
+ import { Transformer } from 'markmap-lib';
3
+ import { AddressInfo } from 'net';
1
4
  import { IContentProvider, IDevelopOptions } from './types';
2
- export declare function develop(fileName: string | undefined, options: IDevelopOptions): Promise<{
3
- provider: IContentProvider;
4
- close(): Promise<void>;
5
- }>;
5
+ export declare class MarkmapDevServer {
6
+ options: IDevelopOptions;
7
+ providers: Record<string, IContentProvider>;
8
+ private transformer;
9
+ private html;
10
+ private watcher;
11
+ private callbacks;
12
+ private disposeList;
13
+ serverInfo: {
14
+ server: ServerType;
15
+ address: AddressInfo;
16
+ } | null;
17
+ constructor(options: IDevelopOptions, transformer?: Transformer);
18
+ private _buildHtml;
19
+ setup(): Promise<void>;
20
+ shutdown(): Promise<void>;
21
+ destroy(): Promise<void>;
22
+ private _watch;
23
+ addProvider(options?: {
24
+ key?: string;
25
+ filePath?: string;
26
+ }): IContentProvider;
27
+ delProvider(key: string): void;
28
+ }
29
+ export declare function develop(options: IDevelopOptions): Promise<MarkmapDevServer>;
package/dist/index.d.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import { type IMarkmapCreateOptions } from 'markmap-lib';
2
- import { config } from './util';
3
- import { IDevelopOptions } from './types';
4
- import { develop } from './dev-server';
5
2
  import { fetchAssets } from './fetch-assets';
6
- export * from 'markmap-lib';
3
+ import { IDevelopOptions } from './types';
4
+ import { config } from './util';
5
+ export * from './dev-server';
7
6
  export * from './types';
8
- export { config, develop, fetchAssets };
9
- export declare function createMarkmap(options: IMarkmapCreateOptions & IDevelopOptions): Promise<void>;
7
+ export { config, fetchAssets };
8
+ export * as markmap from 'markmap-lib';
9
+ export declare function createMarkmap(options: IMarkmapCreateOptions & IDevelopOptions & {
10
+ open: boolean;
11
+ }): Promise<void>;
10
12
  export declare function main(): Promise<void>;
package/dist/index.js CHANGED
@@ -1,77 +1,21 @@
1
- import { readFile, stat, mkdir, writeFile } from "fs/promises";
2
- import { dirname, join, resolve } from "path";
3
- import { fileURLToPath } from "url";
4
1
  import { Command } from "commander";
5
- import open from "open";
6
- import updateNotifier from "update-notifier";
7
- import { readPackageUp } from "read-package-up";
8
- import { buildCSSItem, buildJSItem, defer } from "markmap-common";
2
+ import { readFile, stat, mkdir, writeFile } from "fs/promises";
3
+ import { buildCSSItem, buildJSItem, mergeAssets, defer, extractAssets } from "markmap-common";
9
4
  import { Transformer } from "markmap-lib";
10
- export * from "markmap-lib";
5
+ import * as markmapLib from "markmap-lib";
11
6
  import { fillTemplate, baseJsPaths } from "markmap-render";
7
+ import open from "open";
8
+ import { dirname, join, resolve, basename } from "path";
9
+ import { readPackageUp } from "read-package-up";
10
+ import updateNotifier from "update-notifier";
11
+ import { fileURLToPath } from "url";
12
12
  import { serve } from "@hono/node-server";
13
13
  import { watch } from "chokidar";
14
- import { EventEmitter } from "events";
14
+ import { createHash } from "crypto";
15
15
  import { createReadStream, createWriteStream } from "fs";
16
16
  import { Hono } from "hono";
17
17
  import { Readable } from "stream";
18
18
  import { finished } from "stream/promises";
19
- const TOOLBAR_VERSION = "0.18.4";
20
- const TOOLBAR_CSS = `markmap-toolbar@${TOOLBAR_VERSION}/dist/style.css`;
21
- const TOOLBAR_JS = `markmap-toolbar@${TOOLBAR_VERSION}/dist/index.js`;
22
- const currentDir = dirname(fileURLToPath(import.meta.url));
23
- const ASSETS_PREFIX = "/assets/";
24
- const renderToolbar = () => {
25
- const { markmap, mm } = window;
26
- const toolbar = new markmap.Toolbar();
27
- toolbar.attach(mm);
28
- const el = toolbar.render();
29
- el.setAttribute("style", "position:absolute;bottom:20px;right:20px");
30
- document.body.append(el);
31
- };
32
- function addToolbar(urlBuilder, assets) {
33
- return {
34
- styles: [
35
- ...assets.styles || [],
36
- ...[TOOLBAR_CSS].map((path) => urlBuilder.getFullUrl(path)).map((path) => buildCSSItem(path))
37
- ],
38
- scripts: [
39
- ...assets.scripts || [],
40
- ...[TOOLBAR_JS].map((path) => urlBuilder.getFullUrl(path)).map((path) => buildJSItem(path)),
41
- {
42
- type: "iife",
43
- data: {
44
- fn: (r) => {
45
- setTimeout(r);
46
- },
47
- getParams: () => [renderToolbar]
48
- }
49
- }
50
- ]
51
- };
52
- }
53
- function localProvider(path) {
54
- return `${ASSETS_PREFIX}${path}`;
55
- }
56
- function createStreamBody(stream) {
57
- const body = new ReadableStream({
58
- start(controller) {
59
- stream.on("data", (chunk) => {
60
- controller.enqueue(chunk);
61
- });
62
- stream.on("end", () => {
63
- controller.close();
64
- });
65
- },
66
- cancel() {
67
- stream.destroy();
68
- }
69
- });
70
- return body;
71
- }
72
- const config = {
73
- assetsDir: `${currentDir}${ASSETS_PREFIX}`
74
- };
75
19
  var getMimeType = (filename, mimes = baseMimes) => {
76
20
  const regexp = /\.([a-zA-Z0-9]+?)$/;
77
21
  const match = filename.match(regexp);
@@ -79,7 +23,7 @@ var getMimeType = (filename, mimes = baseMimes) => {
79
23
  return;
80
24
  }
81
25
  let mimeType = mimes[match[1]];
82
- if (mimeType && mimeType.startsWith("text") || mimeType === "application/json") {
26
+ if (mimeType && mimeType.startsWith("text")) {
83
27
  mimeType += "; charset=utf-8";
84
28
  }
85
29
  return mimeType;
@@ -142,215 +86,343 @@ var _baseMimes = {
142
86
  glb: "model/gltf-binary"
143
87
  };
144
88
  var baseMimes = _baseMimes;
89
+ const TOOLBAR_VERSION = "0.18.5-alpha.7+41488b9";
90
+ const TOOLBAR_CSS = `markmap-toolbar@${TOOLBAR_VERSION}/dist/style.css`;
91
+ const TOOLBAR_JS = `markmap-toolbar@${TOOLBAR_VERSION}/dist/index.js`;
92
+ const distDir = dirname(fileURLToPath(import.meta.url));
93
+ const ASSETS_PREFIX = "/assets/";
94
+ const renderToolbar = () => {
95
+ const { markmap, mm } = window;
96
+ const toolbar = new markmap.Toolbar();
97
+ toolbar.attach(mm);
98
+ const el = toolbar.render();
99
+ el.setAttribute("style", "position:absolute;bottom:20px;right:20px");
100
+ document.body.append(el);
101
+ };
102
+ const toolbarAssets = {
103
+ styles: [buildCSSItem(TOOLBAR_CSS)],
104
+ scripts: [
105
+ buildJSItem(TOOLBAR_JS),
106
+ {
107
+ type: "iife",
108
+ data: {
109
+ fn: (r) => {
110
+ setTimeout(r);
111
+ },
112
+ getParams: () => [renderToolbar]
113
+ }
114
+ }
115
+ ]
116
+ };
117
+ function localProvider(path) {
118
+ return `${ASSETS_PREFIX}${path}`;
119
+ }
120
+ function createStreamBody(stream) {
121
+ const body = new ReadableStream({
122
+ start(controller) {
123
+ stream.on("data", (chunk) => {
124
+ controller.enqueue(chunk);
125
+ });
126
+ stream.on("end", () => {
127
+ controller.close();
128
+ });
129
+ },
130
+ cancel() {
131
+ stream.destroy();
132
+ }
133
+ });
134
+ return body;
135
+ }
136
+ const config = {
137
+ distDir,
138
+ assetsDir: `${distDir}${ASSETS_PREFIX}`
139
+ };
145
140
  function sequence(fn) {
146
141
  let promise;
147
142
  return () => {
148
- promise || (promise = fn().finally(() => {
143
+ promise || (promise = Promise.resolve(fn()).finally(() => {
149
144
  promise = void 0;
150
145
  }));
151
146
  return promise;
152
147
  };
153
148
  }
154
149
  class BufferContentProvider {
155
- constructor() {
150
+ constructor(key) {
151
+ this.key = key;
156
152
  this.deferredSet = /* @__PURE__ */ new Set();
157
- this.events = new EventEmitter();
158
- this.ts = 0;
159
- this.content = "";
160
- this.line = -1;
161
- this.events.on("content", () => {
162
- this.feed({
163
- ts: this.ts,
164
- content: this.content
165
- });
166
- });
167
- this.events.on("cursor", () => {
168
- this.feed({
169
- line: this.line
170
- });
171
- });
153
+ this.state = {
154
+ content: {
155
+ ts: 0,
156
+ value: ""
157
+ },
158
+ line: {
159
+ ts: 0,
160
+ value: 0
161
+ }
162
+ };
163
+ this.disposeList = [];
172
164
  }
173
- async getUpdate(ts, timeout = 1e4) {
165
+ async getUpdate(query, timeout = 1e4) {
174
166
  const deferred = defer();
175
167
  this.deferredSet.add(deferred);
176
168
  setTimeout(() => {
177
- this.feed({}, deferred);
169
+ this.feed(null, deferred);
178
170
  }, timeout);
179
- if (ts < this.ts) {
180
- this.feed({ ts: this.ts, content: this.content }, deferred);
171
+ if (Object.keys(query).some((key) => query[key] < this.state[key].ts)) {
172
+ this.feed(null, deferred);
181
173
  }
182
- return deferred.promise;
174
+ await deferred.promise;
183
175
  }
184
176
  feed(data, deferred) {
177
+ if (data) {
178
+ Object.assign(this.state, data);
179
+ }
185
180
  if (deferred) {
186
- deferred.resolve(data);
181
+ deferred.resolve();
187
182
  this.deferredSet.delete(deferred);
188
183
  } else {
189
184
  for (const d of this.deferredSet) {
190
- d.resolve(data);
185
+ d.resolve();
191
186
  }
192
187
  this.deferredSet.clear();
193
188
  }
194
189
  }
195
190
  setCursor(line) {
196
- this.line = line;
197
- this.events.emit("cursor");
191
+ this.feed({
192
+ line: {
193
+ ts: Date.now(),
194
+ value: line
195
+ }
196
+ });
198
197
  }
199
198
  setContent(content) {
200
- this.ts = Date.now();
201
- this.content = content;
202
- this.events.emit("content");
199
+ this.feed({
200
+ content: {
201
+ ts: Date.now(),
202
+ value: content
203
+ }
204
+ });
203
205
  }
204
206
  dispose() {
207
+ this.disposeList.forEach((dispose) => dispose());
205
208
  }
206
209
  }
207
210
  class FileSystemProvider extends BufferContentProvider {
208
- constructor(fileName) {
209
- super();
210
- this.fileName = fileName;
211
- this.watcher = watch(fileName).on(
212
- "all",
213
- sequence(() => this.update())
214
- );
211
+ constructor(key, filePath, watch2) {
212
+ super(key);
213
+ this.filePath = filePath;
214
+ this.disposeList.push(watch2(() => this.update()));
215
215
  }
216
216
  async update() {
217
- const content = await readFile(this.fileName, "utf8");
217
+ const content = await readFile(this.filePath, "utf8");
218
218
  this.setContent(content);
219
219
  }
220
- dispose() {
221
- super.dispose();
222
- this.watcher.close();
220
+ }
221
+ function sha256(input) {
222
+ return createHash("sha256").update(input, "utf8").digest("hex").slice(0, 7);
223
+ }
224
+ async function sendStatic(c, realpath) {
225
+ try {
226
+ const result = await stat(realpath);
227
+ if (!result.isFile()) throw new Error("File not found");
228
+ } catch {
229
+ return c.body("File not found", 404);
223
230
  }
231
+ const stream = createReadStream(realpath);
232
+ const type = getMimeType(realpath);
233
+ if (type) c.header("content-type", type);
234
+ return c.body(createStreamBody(stream));
224
235
  }
225
- function startServer(paddingBottom) {
226
- let ts = 0;
227
- let root;
228
- let line;
229
- const { mm, markmap } = window;
230
- refresh();
231
- function refresh() {
232
- fetch(`/~data?ts=${ts}`).then((res) => res.json()).then(
233
- (res) => {
234
- if (res.ts && res.ts > ts && res.result) {
235
- let frontmatter;
236
- ({ root, frontmatter } = res.result);
237
- mm.setOptions(markmap.deriveOptions(frontmatter == null ? void 0 : frontmatter.markmap));
238
- mm.setData(root);
239
- if (!ts) mm.fit();
240
- ts = res.ts;
241
- line = -1;
242
- }
243
- if (root && res.line != null && line !== res.line) {
244
- line = res.line;
245
- const active = findActiveNode();
246
- if (active) mm.ensureView(active, { bottom: paddingBottom });
247
- }
248
- setTimeout(refresh, 300);
236
+ class MarkmapDevServer {
237
+ constructor(options, transformer) {
238
+ this.options = options;
239
+ this.providers = {};
240
+ this.callbacks = {};
241
+ this.disposeList = [];
242
+ this.serverInfo = null;
243
+ this.transformer = transformer || new Transformer();
244
+ this.html = this._buildHtml();
245
+ this.disposeList.push(() => {
246
+ var _a;
247
+ (_a = this.watcher) == null ? void 0 : _a.close();
248
+ });
249
+ }
250
+ _buildHtml() {
251
+ var _a, _b;
252
+ const otherAssets = mergeAssets(
253
+ this.options.toolbar ? toolbarAssets : null,
254
+ {
255
+ scripts: [
256
+ {
257
+ type: "iife",
258
+ data: {
259
+ fn: (options) => {
260
+ window.markmap.cliOptions = options;
261
+ },
262
+ getParams: () => [this.options]
263
+ }
264
+ }
265
+ ]
249
266
  }
250
267
  );
268
+ const assets = mergeAssets(this.transformer.getAssets(), {
269
+ scripts: (_a = otherAssets.scripts) == null ? void 0 : _a.map(
270
+ (item) => this.transformer.resolveJS(item)
271
+ ),
272
+ styles: (_b = otherAssets.styles) == null ? void 0 : _b.map(
273
+ (item) => this.transformer.resolveCSS(item)
274
+ )
275
+ });
276
+ const html = fillTemplate(null, assets, {
277
+ urlBuilder: this.transformer.urlBuilder
278
+ }) + '<script src="/~client.js"><\/script>';
279
+ return html;
251
280
  }
252
- function findActiveNode() {
253
- let best;
254
- dfs(root);
255
- return best;
256
- function dfs(node) {
257
- var _a, _b, _c;
258
- const [start, end] = ((_b = (_a = node.payload) == null ? void 0 : _a.lines) == null ? void 0 : _b.split(",").map((s) => +s)) || [];
259
- if (start >= 0 && start <= line && line < end) {
260
- best = node;
261
- }
262
- (_c = node.children) == null ? void 0 : _c.forEach(dfs);
281
+ async setup() {
282
+ if (this.serverInfo) throw new Error("Server already set up");
283
+ const app = new Hono();
284
+ app.get("/", (c) => {
285
+ const key = c.req.query("key") || "";
286
+ if (!this.providers[key]) return c.notFound();
287
+ return c.html(this.html);
288
+ });
289
+ app.get("/~data", async (c) => {
290
+ const key = c.req.query("key") || "";
291
+ const provider = this.providers[key];
292
+ if (!provider) return c.json({}, 404);
293
+ const query = Object.fromEntries(
294
+ ["content", "line"].map((key2) => [key2, +(c.req.query(key2) || "") || 0])
295
+ );
296
+ await provider.getUpdate(query);
297
+ const updatedKeys = Object.keys(query).filter(
298
+ (key2) => query[key2] < provider.state[key2].ts
299
+ );
300
+ const result = Object.fromEntries(
301
+ updatedKeys.map((key2) => {
302
+ let data = provider.state[key2];
303
+ if (key2 === "content") {
304
+ const result2 = this.transformer.transform(data.value);
305
+ data = {
306
+ ...data,
307
+ value: {
308
+ frontmatter: result2.frontmatter,
309
+ root: result2.root
310
+ }
311
+ };
312
+ }
313
+ return [key2, data];
314
+ })
315
+ );
316
+ return c.json(result);
317
+ });
318
+ app.post("/~api", async (c) => {
319
+ var _a;
320
+ const key = c.req.query("key") || "";
321
+ const provider = this.providers[key];
322
+ if (!provider) return c.json({}, 404);
323
+ const { cmd, args } = await c.req.json();
324
+ await ((_a = provider[cmd]) == null ? void 0 : _a.call(provider, ...args));
325
+ return c.body(null, 204);
326
+ });
327
+ const { distDir: distDir2, assetsDir } = config;
328
+ app.get("/~client.*", async (c) => {
329
+ const realpath = join(distDir2, c.req.path.slice(2));
330
+ return sendStatic(c, realpath);
331
+ });
332
+ app.get(`${ASSETS_PREFIX}*`, async (c) => {
333
+ const relpath = c.req.path.slice(ASSETS_PREFIX.length);
334
+ const realpath = join(assetsDir, relpath);
335
+ return sendStatic(c, realpath);
336
+ });
337
+ const deferred = defer();
338
+ const server = serve(
339
+ {
340
+ fetch: app.fetch,
341
+ port: this.options.port
342
+ },
343
+ deferred.resolve
344
+ );
345
+ const address = await deferred.promise;
346
+ this.serverInfo = {
347
+ server,
348
+ address
349
+ };
350
+ }
351
+ async shutdown() {
352
+ if (!this.serverInfo) throw new Error("Server is not set up yet");
353
+ const deferred = defer();
354
+ this.serverInfo.server.close((err) => {
355
+ if (err) deferred.reject();
356
+ else deferred.resolve();
357
+ });
358
+ await deferred.promise;
359
+ this.serverInfo = null;
360
+ }
361
+ async destroy() {
362
+ await this.shutdown();
363
+ this.disposeList.forEach((dispose) => dispose());
364
+ }
365
+ _watch(filePath, callback) {
366
+ let { watcher } = this;
367
+ if (!watcher) {
368
+ watcher = watch([]).on("all", (_event, path) => {
369
+ const callback2 = this.callbacks[path];
370
+ callback2 == null ? void 0 : callback2();
371
+ });
372
+ this.watcher = watcher;
263
373
  }
374
+ watcher.add(filePath);
375
+ this.callbacks[filePath] = sequence(callback);
376
+ return () => {
377
+ watcher.unwatch(filePath);
378
+ delete this.callbacks[filePath];
379
+ };
264
380
  }
265
- }
266
- async function setUpServer(transformer, provider, options) {
267
- let assets = transformer.getAssets();
268
- if (options.toolbar) assets = addToolbar(transformer.urlBuilder, assets);
269
- const html = `${fillTemplate(null, assets, {
270
- urlBuilder: transformer.urlBuilder
271
- })}<script>(${startServer.toString()})(${options.toolbar ? 60 : 0})<\/script>`;
272
- const app = new Hono();
273
- app.get("/", (c) => c.html(html));
274
- app.get("/~data", async (c) => {
275
- const update = await provider.getUpdate(+(c.req.query("ts") || 0));
276
- const result = update.content == null ? null : transformer.transform(update.content || "");
277
- return c.json({ ts: update.ts, result, line: update.line });
278
- });
279
- app.post("/~api", async (c) => {
381
+ addProvider(options) {
280
382
  var _a;
281
- const { cmd, args } = await c.req.json();
282
- await ((_a = provider[cmd]) == null ? void 0 : _a.call(provider, ...args));
283
- return c.body(null, 204);
284
- });
285
- const { assetsDir } = config;
286
- app.get(`${ASSETS_PREFIX}*`, async (c) => {
287
- const relpath = c.req.path.slice(ASSETS_PREFIX.length);
288
- const realpath = join(assetsDir, relpath);
289
- try {
290
- const result = await stat(realpath);
291
- if (!result.isFile()) throw new Error("File not found");
292
- } catch {
293
- return c.body("File not found", 404);
294
- }
295
- const stream = createReadStream(realpath);
296
- const type = getMimeType(relpath);
297
- if (type) c.header("content-type", type);
298
- return c.body(createStreamBody(stream));
299
- });
300
- const server = serve(
301
- {
302
- fetch: app.fetch,
303
- port: options.port
304
- },
305
- (info) => {
306
- const { port } = info;
307
- console.info(`Listening at http://localhost:${port}`);
308
- if (options.open) open(`http://localhost:${port}`);
309
- }
310
- );
311
- let closing;
312
- return {
313
- provider,
314
- close() {
315
- if (!closing) {
316
- closing = new Promise(
317
- (resolve2, reject) => server.close((err) => {
318
- if (err) reject(err);
319
- else resolve2();
320
- })
321
- );
322
- }
323
- return closing;
324
- }
325
- };
383
+ const filePath = (options == null ? void 0 : options.filePath) && resolve(options.filePath);
384
+ const key = (options == null ? void 0 : options.key) || (filePath ? sha256(filePath) : Math.random().toString(36).slice(2, 9));
385
+ (_a = this.providers)[key] || (_a[key] = filePath ? new FileSystemProvider(
386
+ key,
387
+ filePath,
388
+ (callback) => this._watch(filePath, callback)
389
+ ) : new BufferContentProvider(key));
390
+ return this.providers[key];
391
+ }
392
+ delProvider(key) {
393
+ const provider = this.providers[key];
394
+ provider == null ? void 0 : provider.dispose();
395
+ delete this.providers[key];
396
+ }
326
397
  }
327
- async function develop(fileName, options) {
398
+ async function develop(options) {
328
399
  const transformer = new Transformer();
329
400
  transformer.urlBuilder.setProvider("local", localProvider);
330
401
  transformer.urlBuilder.provider = "local";
331
- const provider = fileName ? new FileSystemProvider(fileName) : new BufferContentProvider();
332
- return setUpServer(transformer, provider, options);
402
+ const devServer = new MarkmapDevServer(options, transformer);
403
+ await devServer.setup();
404
+ return devServer;
333
405
  }
334
406
  const providerName = "local-hook";
335
407
  async function fetchAssets(assetsDir = config.assetsDir) {
336
- var _a, _b;
337
408
  const transformer = new Transformer();
338
- const { provider } = transformer.urlBuilder;
339
409
  transformer.urlBuilder.setProvider(providerName, localProvider);
340
410
  transformer.urlBuilder.provider = providerName;
341
- let assets = transformer.getAssets();
342
- assets = addToolbar(transformer.urlBuilder, assets);
411
+ const assets = transformer.getAssets();
343
412
  delete transformer.urlBuilder.providers[providerName];
344
- transformer.urlBuilder.provider = provider;
345
- const pluginPaths = [
346
- ...((_a = assets.scripts) == null ? void 0 : _a.map(
347
- (item) => item.type === "script" && item.data.src || ""
348
- )) || [],
349
- ...((_b = assets.styles) == null ? void 0 : _b.map(
350
- (item) => item.type === "stylesheet" && item.data.href || ""
351
- )) || []
352
- ].filter((url) => url.startsWith(ASSETS_PREFIX)).map((url) => url.slice(ASSETS_PREFIX.length));
353
- const paths = [...baseJsPaths, ...pluginPaths];
413
+ const pluginPaths = extractAssets(assets).filter((url) => url.startsWith(ASSETS_PREFIX)).map((url) => url.slice(ASSETS_PREFIX.length));
414
+ const resources = transformer.plugins.flatMap(
415
+ (plugin) => {
416
+ var _a;
417
+ return ((_a = plugin.config) == null ? void 0 : _a.resources) || [];
418
+ }
419
+ );
420
+ const paths = [
421
+ ...baseJsPaths,
422
+ ...pluginPaths,
423
+ ...resources,
424
+ ...extractAssets(toolbarAssets)
425
+ ];
354
426
  let findingProvider;
355
427
  const findProvider = () => {
356
428
  findingProvider || (findingProvider = transformer.urlBuilder.getFastestProvider());
@@ -417,6 +489,7 @@ async function inlineAssets(assets) {
417
489
  };
418
490
  }
419
491
  async function createMarkmap(options) {
492
+ var _a, _b;
420
493
  const transformer = new Transformer();
421
494
  if (options.offline) {
422
495
  transformer.urlBuilder.setProvider("local", localProvider);
@@ -430,20 +503,20 @@ async function createMarkmap(options) {
430
503
  const { root, features, frontmatter } = transformer.transform(
431
504
  options.content || ""
432
505
  );
433
- let assets = transformer.getUsedAssets(features);
434
- assets = {
435
- ...assets,
436
- scripts: [
437
- ...baseJsPaths.map((path) => transformer.urlBuilder.getFullUrl(path)).map((path) => buildJSItem(path)),
438
- ...assets.scripts || []
439
- ]
440
- };
441
- if (options.toolbar) {
442
- assets = addToolbar(transformer.urlBuilder, assets);
443
- }
444
- if (options.offline) {
445
- assets = await inlineAssets(assets);
446
- }
506
+ const otherAssets = mergeAssets(
507
+ {
508
+ scripts: baseJsPaths.map(buildJSItem)
509
+ },
510
+ options.toolbar ? toolbarAssets : null
511
+ );
512
+ let assets = mergeAssets(
513
+ {
514
+ scripts: (_a = otherAssets.scripts) == null ? void 0 : _a.map((item) => transformer.resolveJS(item)),
515
+ styles: (_b = otherAssets.styles) == null ? void 0 : _b.map((item) => transformer.resolveCSS(item))
516
+ },
517
+ transformer.getUsedAssets(features)
518
+ );
519
+ if (options.offline) assets = await inlineAssets(assets);
447
520
  const html = fillTemplate(root, assets, {
448
521
  baseJs: [],
449
522
  jsonOptions: frontmatter == null ? void 0 : frontmatter.markmap,
@@ -475,11 +548,16 @@ async function main() {
475
548
  const content = await readFile(input, "utf8");
476
549
  const output = cmd.output || `${input.replace(/\.\w*$/, "")}.html`;
477
550
  if (cmd.watch) {
478
- await develop(input, {
479
- open: cmd.open,
551
+ const devServer = await develop({
480
552
  toolbar: cmd.toolbar,
481
553
  offline
482
554
  });
555
+ const address = devServer.serverInfo.address;
556
+ const provider = devServer.addProvider({ filePath: input });
557
+ const filename = basename(input);
558
+ const url = `http://localhost:${address.port}/?key=${provider.key}&filename=${encodeURIComponent(filename)}`;
559
+ console.log(`Listening at ${url}`);
560
+ if (cmd.open) open(url);
483
561
  } else {
484
562
  await createMarkmap({
485
563
  content,
@@ -493,9 +571,11 @@ async function main() {
493
571
  program.parse(process.argv);
494
572
  }
495
573
  export {
574
+ MarkmapDevServer,
496
575
  config,
497
576
  createMarkmap,
498
577
  develop,
499
578
  fetchAssets,
500
- main
579
+ main,
580
+ markmapLib as markmap
501
581
  };
package/dist/types.d.ts CHANGED
@@ -1,8 +1,4 @@
1
1
  export interface IDevelopOptions {
2
- /**
3
- * Whether to open the generated markmap in browser
4
- */
5
- open: boolean;
6
2
  /**
7
3
  * Whether to show the default toolbar
8
4
  */
@@ -15,13 +11,15 @@ export interface IDevelopOptions {
15
11
  /** Port number for the devServer to listen. */
16
12
  port?: number;
17
13
  }
18
- export interface IFileUpdate {
19
- ts?: number;
20
- content?: string;
21
- line?: number;
14
+ export interface IVersionedValue<T = unknown> {
15
+ ts: number;
16
+ value: T;
22
17
  }
18
+ export type IFileState = Record<string, IVersionedValue>;
23
19
  export interface IContentProvider {
24
- getUpdate: (ts: number, timeout?: number) => Promise<IFileUpdate>;
20
+ key: string;
21
+ state: IFileState;
22
+ getUpdate: (query: Record<string, number>, timeout?: number) => Promise<void>;
25
23
  setContent: (content: string) => void;
26
24
  setCursor: (line: number) => void;
27
25
  dispose: () => void;
package/dist/util.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import type { ReadStream } from 'fs';
2
- import { UrlBuilder } from 'markmap-common';
3
2
  import { IAssets } from 'markmap-lib';
4
3
  export declare const ASSETS_PREFIX = "/assets/";
5
- export declare function addToolbar(urlBuilder: UrlBuilder, assets: IAssets): IAssets;
4
+ export declare const toolbarAssets: IAssets;
6
5
  export declare function localProvider(path: string): string;
7
6
  export declare function createStreamBody(stream: ReadStream): ReadableStream<any>;
8
7
  export declare const config: {
8
+ distDir: string;
9
9
  assetsDir: string;
10
10
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markmap-cli",
3
- "version": "0.18.4",
3
+ "version": "0.18.5-alpha.7+41488b9",
4
4
  "description": "Create markmaps from CLI",
5
5
  "author": "Gerald <gera2ld@live.com>",
6
6
  "license": "MIT",
@@ -49,14 +49,13 @@
49
49
  "chokidar": "^4.0.1",
50
50
  "commander": "^12.1.0",
51
51
  "hono": "^4.6.13",
52
- "markmap-common": "0.18.4",
53
- "markmap-lib": "0.18.4",
54
- "markmap-render": "0.18.4",
55
- "markmap-toolbar": "0.18.4",
52
+ "markmap-common": "0.18.5-alpha.7+41488b9",
53
+ "markmap-lib": "0.18.5-alpha.7+41488b9",
54
+ "markmap-render": "0.18.5-alpha.7+41488b9",
55
+ "markmap-toolbar": "0.18.5-alpha.7+41488b9",
56
56
  "open": "^10.1.0",
57
- "pkg-dir": "^8.0.0",
58
57
  "read-package-up": "^11.0.0",
59
58
  "update-notifier": "^7.3.1"
60
59
  },
61
- "gitHead": "eb77f9eb79a44d60237f9a9cdadf0a7ca7fa9e29"
60
+ "gitHead": "41488b9988a1f49f7dadc029ef6be18fed981cac"
62
61
  }