finch-markdown-editor 0.1.5 → 0.1.8

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/dist/index.js CHANGED
@@ -1,11 +1,10 @@
1
1
  // src/index.ts
2
2
  import { createHash } from "node:crypto";
3
- import { mkdir, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
4
- import { gunzip } from "node:zlib";
5
- import { promisify } from "node:util";
3
+ import { mkdir, readFile, realpath, rm, stat, writeFile } from "node:fs/promises";
6
4
  import { watch } from "node:fs";
7
5
  import { spawn } from "node:child_process";
8
6
  import path from "node:path";
7
+ import { fileURLToPath } from "node:url";
9
8
  import os from "node:os";
10
9
  var PASTE_IMAGE_EXT = {
11
10
  "image/png": "png",
@@ -293,75 +292,9 @@ async function restoreDocument(ctx, panel) {
293
292
  return false;
294
293
  }
295
294
  }
296
- var BMMD_VERSION = "0.2.0";
297
- var BMMD_TARBALL_URL = `https://registry.npmjs.org/bmmd/-/bmmd-${BMMD_VERSION}.tgz`;
298
- var gunzipAsync = promisify(gunzip);
299
- var bmmdBinPromise;
300
- function tarString(buffer) {
301
- const zero = buffer.indexOf(0);
302
- return buffer.subarray(0, zero === -1 ? buffer.length : zero).toString("utf8");
303
- }
304
- async function extractBmmdTarball(tarball, destination) {
305
- const tar = await gunzipAsync(tarball);
306
- for (let offset = 0; offset + 512 <= tar.length; ) {
307
- const header = tar.subarray(offset, offset + 512);
308
- if (header.every((byte) => byte === 0)) break;
309
- const name = tarString(header.subarray(0, 100));
310
- const prefix = tarString(header.subarray(345, 500));
311
- const entryPath = prefix ? `${prefix}/${name}` : name;
312
- const sizeText = tarString(header.subarray(124, 136)).trim();
313
- const size = Number.parseInt(sizeText || "0", 8);
314
- if (!Number.isSafeInteger(size) || size < 0) throw new Error("Invalid bmmd package archive.");
315
- const bodyStart = offset + 512;
316
- const bodyEnd = bodyStart + size;
317
- if (bodyEnd > tar.length) throw new Error("Truncated bmmd package archive.");
318
- if (header[156] === 48 && entryPath.startsWith("package/bin/") && !entryPath.includes("..")) {
319
- const relative = entryPath.slice("package/bin/".length);
320
- if (relative && !path.isAbsolute(relative)) {
321
- const outputPath = path.join(destination, relative);
322
- await mkdir(path.dirname(outputPath), { recursive: true });
323
- await writeFile(outputPath, tar.subarray(bodyStart, bodyEnd));
324
- }
325
- }
326
- offset = bodyStart + Math.ceil(size / 512) * 512;
327
- }
328
- }
329
- async function ensureBmmdBin(ctx, onInstalling) {
330
- if (!bmmdBinPromise) {
331
- bmmdBinPromise = (async () => {
332
- const installDir = path.join(ctx.storagePath, "bmmd");
333
- const binPath = path.join(installDir, "bin", "bmmd.mjs");
334
- try {
335
- await stat(binPath);
336
- return binPath;
337
- } catch {
338
- }
339
- onInstalling?.();
340
- const response = await fetch(BMMD_TARBALL_URL);
341
- if (!response.ok) throw new Error(`Could not download bmmd ${BMMD_VERSION}: HTTP ${response.status}.`);
342
- const stagingDir = `${installDir}.staging-${process.pid}-${Date.now()}`;
343
- await rm(stagingDir, { recursive: true, force: true });
344
- try {
345
- await extractBmmdTarball(Buffer.from(await response.arrayBuffer()), path.join(stagingDir, "bin"));
346
- await stat(path.join(stagingDir, "bin", "bmmd.mjs"));
347
- await rm(installDir, { recursive: true, force: true });
348
- await rename(stagingDir, installDir);
349
- } catch (error) {
350
- await rm(stagingDir, { recursive: true, force: true });
351
- throw error;
352
- }
353
- return binPath;
354
- })();
355
- }
356
- try {
357
- return await bmmdBinPromise;
358
- } catch (error) {
359
- bmmdBinPromise = void 0;
360
- throw error;
361
- }
362
- }
363
- async function runBmmd(ctx, args, input, onInstalling) {
364
- const binPath = await ensureBmmdBin(ctx, onInstalling);
295
+ var BMMD_BIN_PATH = fileURLToPath(new URL("./bmmd/bin/bmmd.mjs", import.meta.url));
296
+ async function runBmmd(args, input) {
297
+ const binPath = BMMD_BIN_PATH;
365
298
  return new Promise((resolve, reject) => {
366
299
  const child = spawn(process.execPath, [binPath, ...args], { stdio: ["pipe", "pipe", "pipe"] });
367
300
  let output = "";
@@ -394,11 +327,11 @@ function substituteFinchFileImagesForBm(markdown) {
394
327
  });
395
328
  return { markdown: substituted, urls };
396
329
  }
397
- async function renderWithBm(ctx, markdown, markdownStyle, customCss, onInstalling) {
330
+ async function renderWithBm(markdown, markdownStyle, customCss) {
398
331
  const args = ["render", "--platform", "wechat", "--markdown-style", markdownStyle || "kami"];
399
332
  if (customCss && customCss.trim()) args.push("--custom-css", customCss);
400
333
  const prepared = substituteFinchFileImagesForBm(markdown);
401
- let html = await runBmmd(ctx, args, prepared.markdown, onInstalling);
334
+ let html = await runBmmd(args, prepared.markdown);
402
335
  for (const [placeholder, originalUrl] of prepared.urls) html = html.split(placeholder).join(originalUrl);
403
336
  return html;
404
337
  }
@@ -634,10 +567,7 @@ async function handleMessage(ctx, panel, raw) {
634
567
  }
635
568
  case "renderBm": {
636
569
  try {
637
- const html = await renderWithBm(ctx, String(message.markdown ?? ""), String(message.markdownStyle ?? "kami"), message.customCss, () => {
638
- panel.postMessage({ type: "status", message: "\u9996\u6B21\u6E32\u67D3\uFF1A\u6B63\u5728\u4E0B\u8F7D bmmd \u6E32\u67D3\u5F15\u64CE\uFF08\u7EA6\u51E0\u79D2\uFF0C\u4EC5\u4E00\u6B21\uFF09\u2026" }).catch(() => {
639
- });
640
- });
570
+ const html = await renderWithBm(String(message.markdown ?? ""), String(message.markdownStyle ?? "kami"), message.customCss);
641
571
  await panel.postMessage({ type: "bmRendered", html, requestId: message.requestId });
642
572
  } catch (error) {
643
573
  await panel.postMessage({ type: "error", message: `bm.md rendering failed: ${error instanceof Error ? error.message : String(error)}` });
@@ -773,6 +703,9 @@ function activate(ctx) {
773
703
  },
774
704
  feather: {
775
705
  svg: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.086 18.412A2 2 0 0 1 12.67 19H5v-7.672a2 2 0 0 1 .586-1.414L11.75 3.75a6 6 0 1 1 8.49 8.49z"/><path d="M16 8 2 22"/><path d="M17.488 15H9"/></svg>'
706
+ },
707
+ "swatch-book": {
708
+ svg: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 17a4 4 0 0 1-8 0V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2Z"/><path d="M16.7 13H19a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H7"/><path d="M 7 17h.01"/><path d="m11 8 2.3-2.3a2.4 2.4 0 0 1 3.404.004L18.6 7.6a2.4 2.4 0 0 1 .026 3.434L9.9 19.8"/></svg>'
776
709
  }
777
710
  }));
778
711
  ctx.subscriptions.push(ctx.ui.onDidOpenPanel((panel) => {
@@ -800,7 +733,7 @@ function activate(ctx) {
800
733
  description: `Open, create, revise, or restyle a Markdown document in Markdown Editor.
801
734
  action:
802
735
  open \u2014 read an absolute local Markdown path and open it as an editable WeChat article preview
803
- create \u2014 write brand-new Markdown content to an absolute path that does not exist yet, then open it in Markdown Editor. Use this whenever the user asks to create/draft a new Markdown file (Markdown Editor's own UI has no "new file" button on purpose \u2014 this tool action is the intended way to start a new document)
736
+ create \u2014 write brand-new Markdown content to an absolute path that does not exist yet, then open it in Markdown Editor. Use this whenever the user asks to write an article, start writing, write a post, create, or draft a new document \u2014 even if they do not mention Markdown. If title/topic or destination is missing, guide the user to provide it; once known, create and open the document rather than returning prose only. If they only want to begin, create a minimal titled starter document. Markdown Editor's own UI has no "new file" button on purpose \u2014 this tool action is the intended way to start a new document
804
737
  apply \u2014 replace a source document with reviewed Markdown (requires path and markdown); the open panel refreshes in place, no Diff window
805
738
  set_style \u2014 apply an AI-designed custom CSS layout to the currently open Markdown Editor preview (requires css). Write plain CSS scoped under #bm-md using tag/id selectors (no classes), use !important where needed to override the base style, and take inspiration from bm.md's built-in styles: kami (warm paper), bauhaus (geometric primary colors), blueprint (technical grid), botanical (soft green), newsprint (editorial serif), retro (nostalgic), sketch (hand-drawn), terminal (monospace dark).`,
806
739
  inputSchema: {