@mikedown/core 0.3.0 → 0.4.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/dist/index.js CHANGED
@@ -2,9 +2,6 @@ import {
2
2
  countWords,
3
3
  readingMinutes
4
4
  } from "./chunk-GWCRPPCX.js";
5
- import {
6
- githubAnchorId
7
- } from "./chunk-TSP7BAV3.js";
8
5
  import {
9
6
  AiRewriteHighlight,
10
7
  cmAiRewriteHighlight,
@@ -18,12 +15,15 @@ import {
18
15
  aiToolbarButtonMode,
19
16
  loadAiUi,
20
17
  loadedAiUi
21
- } from "./chunk-LXZG7IKN.js";
18
+ } from "./chunk-N4N54LK2.js";
22
19
  import {
23
20
  defaultAiSettings,
24
21
  findInlineTags,
25
22
  normalizeTag
26
- } from "./chunk-QDVY7WW6.js";
23
+ } from "./chunk-APPBNQEY.js";
24
+ import {
25
+ githubAnchorId
26
+ } from "./chunk-TSP7BAV3.js";
27
27
 
28
28
  // src/editor.ts
29
29
  import { Editor as Editor2, Extension as Extension14, mergeAttributes as mergeAttributes5 } from "@tiptap/core";
@@ -3298,6 +3298,9 @@ function hidePopup2() {
3298
3298
  }
3299
3299
  state2 = null;
3300
3300
  }
3301
+ function isEmojiAutocompleteOpen() {
3302
+ return popupEl2 !== null && state2 !== null && state2.matches.length > 0;
3303
+ }
3301
3304
  function ensurePopup2() {
3302
3305
  if (!popupEl2) {
3303
3306
  popupEl2 = document.createElement("div");
@@ -3614,6 +3617,9 @@ function hidePopup3() {
3614
3617
  }
3615
3618
  state3 = null;
3616
3619
  }
3620
+ function isTagAutocompleteOpen() {
3621
+ return popupEl3 !== null && state3 !== null && state3.matches.length > 0;
3622
+ }
3617
3623
  function ensurePopup3() {
3618
3624
  if (!popupEl3) {
3619
3625
  popupEl3 = document.createElement("div");
@@ -4744,6 +4750,9 @@ function hidePopup4() {
4744
4750
  }
4745
4751
  state4 = null;
4746
4752
  }
4753
+ function isWikilinkAutocompleteOpen() {
4754
+ return popupEl4 !== null && state4 !== null && state4.matches.length > 0;
4755
+ }
4747
4756
  function ensurePopup4() {
4748
4757
  if (!popupEl4) {
4749
4758
  popupEl4 = document.createElement("div");
@@ -7799,6 +7808,14 @@ var AI_CHIP_GLYPHS = {
7799
7808
  on: aiGlyph('<circle cx="8" cy="8" r="4" fill="currentColor"/>'),
7800
7809
  warn: aiGlyph('<path d="M8 2.2l6.2 11H1.8z"/><path d="M8 6.5v3M8 11.4v.1"/>')
7801
7810
  };
7811
+ function headingDomText(heading2) {
7812
+ if (!heading2.querySelector(".mikedown-ghost")) {
7813
+ return heading2.textContent || "";
7814
+ }
7815
+ const copy = heading2.cloneNode(true);
7816
+ copy.querySelectorAll(".mikedown-ghost").forEach((n) => n.remove());
7817
+ return copy.textContent || "";
7818
+ }
7802
7819
  function createOutlineSidebar(opts) {
7803
7820
  let pref = "never";
7804
7821
  let position = "right";
@@ -8597,7 +8614,7 @@ function createOutlineSidebar(opts) {
8597
8614
  const seen = /* @__PURE__ */ new Map();
8598
8615
  let currentAnchor = "";
8599
8616
  for (const h of headings) {
8600
- const base = anchorFnRef(h.textContent || "");
8617
+ const base = anchorFnRef(headingDomText(h));
8601
8618
  const count = seen.get(base) ?? 0;
8602
8619
  const anchor = count === 0 ? base : `${base}-${count}`;
8603
8620
  seen.set(base, count + 1);
@@ -8609,7 +8626,7 @@ function createOutlineSidebar(opts) {
8609
8626
  }
8610
8627
  if (!currentAnchor) {
8611
8628
  const first = headings[0];
8612
- currentAnchor = anchorFnRef(first.textContent || "");
8629
+ currentAnchor = anchorFnRef(headingDomText(first));
8613
8630
  }
8614
8631
  if (currentAnchor === activeAnchor) {
8615
8632
  return;
@@ -8625,7 +8642,7 @@ function createOutlineSidebar(opts) {
8625
8642
  const headings = editorContainer.querySelectorAll("h1,h2,h3,h4,h5,h6");
8626
8643
  const seen = /* @__PURE__ */ new Map();
8627
8644
  for (const h of headings) {
8628
- const base = anchorFnRef(h.textContent || "");
8645
+ const base = anchorFnRef(headingDomText(h));
8629
8646
  const count = seen.get(base) ?? 0;
8630
8647
  const id = count === 0 ? base : `${base}-${count}`;
8631
8648
  seen.set(base, count + 1);
@@ -13816,7 +13833,8 @@ var MIKEDOWN_HOTKEYS = [
13816
13833
  { command: "mikedown.undo", label: "Undo", win: "ctrl+z", mac: "cmd+z" },
13817
13834
  { command: "mikedown.redo", label: "Redo", win: "ctrl+shift+z", mac: "cmd+shift+z" },
13818
13835
  // Not Cmd+Shift+K (the plan's pick): that is Toggle Inline Code.
13819
- { command: "mikedown.ai.rewrite", label: "Rewrite Selection with AI", win: "ctrl+alt+k", mac: "cmd+alt+k" }
13836
+ { command: "mikedown.ai.rewrite", label: "Rewrite Selection with AI", win: "ctrl+alt+k", mac: "cmd+alt+k" },
13837
+ { command: "mikedown.ai.suggestContinuation", label: "Suggest Continuation with AI", win: "ctrl+alt+space", mac: "cmd+alt+space" }
13820
13838
  ];
13821
13839
  var MIKEDOWN_TABLE_HOTKEYS = [
13822
13840
  { label: "Insert Row Below", win: "ctrl+enter", mac: "cmd+enter" },
@@ -14122,6 +14140,8 @@ var toolbarIcons = {
14122
14140
  more: toolbarSvg('<circle cx="3.5" cy="8" r="1.2" fill="currentColor" stroke="none"/><circle cx="8" cy="8" r="1.2" fill="currentColor" stroke="none"/><circle cx="12.5" cy="8" r="1.2" fill="currentColor" stroke="none"/>'),
14123
14141
  ai: toolbarSvg('<path d="M7 2.5l1.25 3.25L11.5 7 8.25 8.25 7 11.5 5.75 8.25 2.5 7l3.25-1.25z"/><path d="M12.5 10v3.5M10.75 11.75h3.5"/>', 1.5),
14124
14142
  rewrite: toolbarSvg('<path d="M10.5 2.5l3 3L6 13H3v-3z"/><path d="M9 4l3 3"/>', 1.5),
14143
+ aiActivity: toolbarSvg('<polyline points="1.5 8 4.5 8 6.5 3 9.5 13 11.5 8 14.5 8"/>', 1.5),
14144
+ aiChat: toolbarSvg('<path d="M2.5 3.5h11v8H7l-3 2.5v-2.5H2.5z"/>', 1.5),
14125
14145
  explain: toolbarSvg('<circle cx="8" cy="8" r="6"/><path d="M6.3 6.3a1.8 1.8 0 1 1 2.4 1.7c-.5.2-.7.6-.7 1.1"/><circle cx="8" cy="11.3" r="0.6" fill="currentColor" stroke="none"/>', 1.5),
14126
14146
  translate: toolbarSvg('<path d="M2 4h7M5.5 2.5V4M4 4c.5 2.5 2 4.5 4.5 5.5M7.5 4c-.6 2.6-2.3 4.7-5 6"/><path d="M8.5 14l2.5-6 2.5 6M9.3 12h3.4"/>', 1.5),
14127
14147
  tag: toolbarSvg('<path d="M2.5 2.5h5l6 6-5 5-6-6z"/><circle cx="5.5" cy="5.5" r="1" fill="currentColor" stroke="none"/>', 1.5),
@@ -14148,6 +14168,7 @@ function calloutBadgeIcon(kind) {
14148
14168
  const char = CALLOUT_KIND_ICON_CHAR[kind];
14149
14169
  return `<span class="mikedown-callout-badge mikedown-callout-badge--${kind}" aria-hidden="true">${char}</span>`;
14150
14170
  }
14171
+ var DOCK_MIN_ROOT_PX = 720;
14151
14172
  var SOURCE_MODE_DISABLED_ACTIONS = /* @__PURE__ */ new Set([
14152
14173
  "textFormat",
14153
14174
  "listBlock",
@@ -14341,7 +14362,7 @@ function createMikeDownEditor(container, options) {
14341
14362
  const headings = container2.querySelectorAll("h1,h2,h3,h4,h5,h6");
14342
14363
  const seenIds = /* @__PURE__ */ new Map();
14343
14364
  for (const h of headings) {
14344
- const base = githubAnchorId(h.textContent || "");
14365
+ const base = githubAnchorId(headingDomText(h));
14345
14366
  const count = seenIds.get(base) ?? 0;
14346
14367
  const id = count === 0 ? base : `${base}-${count}`;
14347
14368
  seenIds.set(base, count + 1);
@@ -14489,9 +14510,9 @@ function createMikeDownEditor(container, options) {
14489
14510
  editor2.view.dispatch(tr);
14490
14511
  }
14491
14512
  if (url === "") {
14492
- editor2.chain().focus().unsetLink().run();
14513
+ editor2.chain().focus().extendMarkRange("link").unsetLink().run();
14493
14514
  } else {
14494
- editor2.chain().focus().setLink({ href: url }).run();
14515
+ editor2.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
14495
14516
  }
14496
14517
  }
14497
14518
  confirmBtn.addEventListener("click", confirm);
@@ -14719,6 +14740,9 @@ function createMikeDownEditor(container, options) {
14719
14740
  if (aiPage?.fromModal) {
14720
14741
  closeAiEnginesPage();
14721
14742
  }
14743
+ if (aiActivityPage?.fromModal) {
14744
+ closeAiActivityPanel();
14745
+ }
14722
14746
  const discard = discardSettings;
14723
14747
  discardSettings = null;
14724
14748
  refreshToolbarSettingsRows = null;
@@ -15219,8 +15243,10 @@ function createMikeDownEditor(container, options) {
15219
15243
  onConsentForgotten: (engineId) => {
15220
15244
  delete aiConsent[engineId];
15221
15245
  },
15246
+ docsAvailable: !!host.docs,
15222
15247
  onSaved: (saved) => {
15223
- currentAiSettings = saved;
15248
+ currentAiSettings = { ...saved, docBases: currentAiSettings.docBases };
15249
+ syncAiActivitySetting();
15224
15250
  renderAiIndicator();
15225
15251
  refreshAiToolbarButton();
15226
15252
  scheduleAiStatusRefresh();
@@ -15231,7 +15257,8 @@ function createMikeDownEditor(container, options) {
15231
15257
  session: request.session,
15232
15258
  onDone: (patch, saved) => request.onDone(patch, saved),
15233
15259
  fromModal: true
15234
- })
15260
+ }),
15261
+ openActivityPanel: () => showAiActivityPanel({ fromModal: true })
15235
15262
  })
15236
15263
  }] : [];
15237
15264
  const toolbarRows = [];
@@ -15730,6 +15757,27 @@ function createMikeDownEditor(container, options) {
15730
15757
  let aiStatuses = [];
15731
15758
  let aiServed = null;
15732
15759
  const aiConsent = {};
15760
+ let aiActivity = null;
15761
+ let aiActivityPage = null;
15762
+ function aiActivityLog(ui) {
15763
+ aiActivity ?? (aiActivity = ui.createAiActivityLog());
15764
+ aiActivity.setEnabled(currentAiSettings.activityPanel);
15765
+ return aiActivity;
15766
+ }
15767
+ function syncAiActivitySetting() {
15768
+ aiActivity?.setEnabled(currentAiSettings.activityPanel);
15769
+ aiActivityPage?.handle.refresh();
15770
+ aiChatPage?.handle.refresh();
15771
+ }
15772
+ function aiRunContext(ui, ai) {
15773
+ return {
15774
+ ai,
15775
+ settings: () => currentAiSettings,
15776
+ statuses: () => aiStatuses,
15777
+ consent: aiConsent,
15778
+ activity: aiActivityLog(ui)
15779
+ };
15780
+ }
15733
15781
  const aiSettingsWaiters = /* @__PURE__ */ new Set();
15734
15782
  let aiStatusTimer;
15735
15783
  disposers.push(() => window.clearTimeout(aiStatusTimer));
@@ -15769,6 +15817,72 @@ function createMikeDownEditor(container, options) {
15769
15817
  }).catch(() => {
15770
15818
  });
15771
15819
  }
15820
+ let aiGhost = null;
15821
+ disposers.push(() => {
15822
+ aiGhost?.destroy();
15823
+ aiGhost = null;
15824
+ });
15825
+ function syncAiGhost() {
15826
+ const ai = aiHost;
15827
+ const ui = aiUi;
15828
+ if (!ai || !ui || destroyed) {
15829
+ return;
15830
+ }
15831
+ const on = ui.ghostFeatureOn(currentAiSettings);
15832
+ if (on === (aiGhost !== null)) {
15833
+ return;
15834
+ }
15835
+ if (!on) {
15836
+ aiGhost?.destroy();
15837
+ aiGhost = null;
15838
+ return;
15839
+ }
15840
+ aiGhost = ui.createGhostSession({
15841
+ editor,
15842
+ run: (req, events, signal) => ui.runAiCompletion({ ...aiRunContext(ui, ai), activity: void 0 }, req, events, signal),
15843
+ settings: () => currentAiSettings,
15844
+ statuses: () => aiStatuses,
15845
+ consent: aiConsent,
15846
+ popupOpen: () => isEmojiAutocompleteOpen() || isWikilinkAutocompleteOpen() || isTagAutocompleteOpen() || isSlashMenuOpen() || isDropdownActive() || isToolbarDropdownOpen() || isEmojiPickerOpen() || !!aiRewrite?.isOpen(),
15847
+ enabled: () => !sourceMode,
15848
+ onBusy: (busy) => {
15849
+ aiGhostBusy = busy;
15850
+ syncAiToolbarBusy();
15851
+ },
15852
+ onServed: (served, viaFallback) => {
15853
+ aiServed = { served, viaFallback };
15854
+ renderAiIndicator();
15855
+ },
15856
+ record: (entry) => aiActivityLog(ui).record(entry)
15857
+ });
15858
+ }
15859
+ function setAiInlineSuggestions(next) {
15860
+ const ui = aiUi;
15861
+ if (!aiHost || !ui || destroyed) {
15862
+ return;
15863
+ }
15864
+ const { settings, patch } = ui.withGhostFeature(currentAiSettings, next);
15865
+ currentAiSettings = settings;
15866
+ renderAiIndicator();
15867
+ host.saveSettings?.(patch, { silent: true });
15868
+ }
15869
+ function startAiGhost() {
15870
+ if (!aiHost || destroyed || sourceMode || readOnly) {
15871
+ return false;
15872
+ }
15873
+ if (!aiUi) {
15874
+ whenAiUi(() => {
15875
+ startAiGhost();
15876
+ });
15877
+ return true;
15878
+ }
15879
+ if (!aiGhost) {
15880
+ noteAiFeatureOff("ghostText");
15881
+ return true;
15882
+ }
15883
+ aiGhost.trigger();
15884
+ return true;
15885
+ }
15772
15886
  function onAiUiLoaded(ui) {
15773
15887
  if (aiUi) {
15774
15888
  return;
@@ -15785,6 +15899,7 @@ function createMikeDownEditor(container, options) {
15785
15899
  }
15786
15900
  }
15787
15901
  function renderAiIndicator() {
15902
+ syncAiGhost();
15788
15903
  syncAiSlashCommands();
15789
15904
  syncAiPropertiesAvailability();
15790
15905
  const ui = aiUi;
@@ -15801,13 +15916,14 @@ function createMikeDownEditor(container, options) {
15801
15916
  if (!ui) {
15802
15917
  pendingAiSettings = { raw };
15803
15918
  const r = raw ?? {};
15804
- currentAiSettings = { ...currentAiSettings, enabled: r.enabled === true, showToolbarButton: r.showToolbarButton !== false };
15919
+ currentAiSettings = { ...currentAiSettings, enabled: r.enabled === true, showToolbarButton: r.showToolbarButton !== false, activityPanel: r.activityPanel === true };
15805
15920
  refreshAiToolbarButton();
15806
15921
  return;
15807
15922
  }
15808
15923
  const before = currentAiSettings;
15809
15924
  currentAiSettings = ui.parseAiSettings(raw, { onWarning: () => {
15810
15925
  } });
15926
+ syncAiActivitySetting();
15811
15927
  if (aiServed && ui.aiNextRouteChanged(before, currentAiSettings, aiStatuses)) {
15812
15928
  aiServed = null;
15813
15929
  }
@@ -15893,6 +16009,7 @@ function createMikeDownEditor(container, options) {
15893
16009
  }
15894
16010
  const opener = document.activeElement instanceof HTMLElement ? document.activeElement : null;
15895
16011
  closeAiEnginesPage();
16012
+ closeAiActivityPanel();
15896
16013
  const overlay = document.createElement("div");
15897
16014
  overlay.id = "mikedown-ai-page-overlay";
15898
16015
  const frame = document.createElement("div");
@@ -15903,12 +16020,14 @@ function createMikeDownEditor(container, options) {
15903
16020
  getPortal().appendChild(overlay);
15904
16021
  const finish = () => {
15905
16022
  closeAiEnginesPage();
16023
+ aiChatPage?.handle.refresh();
15906
16024
  restoreFocus(opener);
15907
16025
  };
15908
16026
  const handle2 = ui.mountAiEnginesPage(frame, settingsSectionContext(finish, (href) => openLinkViaHost(href)), {
15909
16027
  ...o.session ?? aiSessionOptions(ai),
15910
16028
  initial: o.initial,
15911
16029
  ...o.turnOnWhenDone ? { turnOnWhenDone: true } : {},
16030
+ ...host.docs ? { docs: host.docs } : {},
15912
16031
  onDone: (patch, settings) => {
15913
16032
  if (Object.keys(patch).length > 0) {
15914
16033
  host.saveSettings?.(patch);
@@ -15936,6 +16055,190 @@ function createMikeDownEditor(container, options) {
15936
16055
  });
15937
16056
  handle2.focus();
15938
16057
  }
16058
+ function closeAiActivityPanel() {
16059
+ const page = aiActivityPage;
16060
+ aiActivityPage = null;
16061
+ if (!page) {
16062
+ return;
16063
+ }
16064
+ page.handle.destroy();
16065
+ page.overlay.remove();
16066
+ }
16067
+ function showAiActivityPanel(o = {}) {
16068
+ if (!aiHost) {
16069
+ return;
16070
+ }
16071
+ const ui = aiUi;
16072
+ if (!ui) {
16073
+ whenAiUi(() => showAiActivityPanel(o));
16074
+ return;
16075
+ }
16076
+ const opener = document.activeElement instanceof HTMLElement ? document.activeElement : null;
16077
+ closeAiEnginesPage();
16078
+ closeAiActivityPanel();
16079
+ const overlay = document.createElement("div");
16080
+ overlay.id = "mikedown-ai-page-overlay";
16081
+ const frame = document.createElement("div");
16082
+ frame.className = "ai-page-frame";
16083
+ frame.setAttribute("role", "dialog");
16084
+ frame.setAttribute("aria-modal", "true");
16085
+ overlay.appendChild(frame);
16086
+ getPortal().appendChild(overlay);
16087
+ const finish = () => {
16088
+ closeAiActivityPanel();
16089
+ restoreFocus(opener);
16090
+ };
16091
+ const handle2 = ui.mountAiActivityPanel(frame, settingsSectionContext(finish, (href) => openLinkViaHost(href)), {
16092
+ log: aiActivityLog(ui),
16093
+ ...o.selectId !== void 0 ? { selectId: o.selectId } : {},
16094
+ ...host.saveTextFile ? { saveTextFile: (name, text) => host.saveTextFile?.(name, text) ?? Promise.resolve() } : {}
16095
+ });
16096
+ aiActivityPage = { overlay, handle: handle2, fromModal: !!o.fromModal };
16097
+ const titleId = frame.querySelector(".ai-page-title")?.id;
16098
+ if (titleId) {
16099
+ frame.setAttribute("aria-labelledby", titleId);
16100
+ }
16101
+ frame.addEventListener("keydown", (e) => {
16102
+ if (e.key === "Tab") {
16103
+ trapTab(frame, e);
16104
+ }
16105
+ });
16106
+ handle2.focus();
16107
+ }
16108
+ let aiChatPage = null;
16109
+ let aiDocsChat = null;
16110
+ function closeAiChatPanel() {
16111
+ const page = aiChatPage;
16112
+ aiChatPage = null;
16113
+ if (!page) {
16114
+ return;
16115
+ }
16116
+ page.resize?.disconnect();
16117
+ page.handle.destroy();
16118
+ page.dock.remove();
16119
+ root.classList.remove("ai-chat-open", "ai-chat-docked");
16120
+ }
16121
+ function showAiChatPanel() {
16122
+ const ai = aiHost;
16123
+ if (!ai || destroyed) {
16124
+ return;
16125
+ }
16126
+ const ui = aiUi;
16127
+ if (!ui) {
16128
+ whenAiUi(() => showAiChatPanel());
16129
+ return;
16130
+ }
16131
+ if (aiChatPage) {
16132
+ aiChatPage.handle.focus();
16133
+ return;
16134
+ }
16135
+ const off = ui.aiFeatureOffNotice(ai, currentAiSettings, "chat");
16136
+ if (off) {
16137
+ showTransientToast(off);
16138
+ return;
16139
+ }
16140
+ const opener = document.activeElement instanceof HTMLElement ? document.activeElement : null;
16141
+ const dock = document.createElement("aside");
16142
+ dock.className = "ai-chat-dock";
16143
+ root.appendChild(dock);
16144
+ root.classList.add("ai-chat-open");
16145
+ const tier2 = aiHostTier(ai) === 2;
16146
+ const handle2 = ui.mountAiChatPanel(dock, {
16147
+ chat: aiDocsChat ?? (aiDocsChat = ui.createAiDocsChat(aiRunContext(ui, ai))),
16148
+ ...host.docs ? { docs: host.docs } : {},
16149
+ documentText: aiBodyMarkdown,
16150
+ docsOptions: () => currentAiSettings.docs,
16151
+ engineName: (id) => currentAiSettings.engines.find((e) => e.id === id)?.name ?? id,
16152
+ thinking: ui.thinkingLevelFor,
16153
+ featureOffNotice: () => ui.aiFeatureOffNotice(ai, currentAiSettings, "chat"),
16154
+ canInsert: () => !readOnly,
16155
+ insertAtCursor: (markdown, policy) => {
16156
+ const target = aiCursorTarget();
16157
+ if (!target) {
16158
+ return false;
16159
+ }
16160
+ const ok = target.accept(markdown, policy);
16161
+ if (!ok) {
16162
+ target.release();
16163
+ }
16164
+ return ok;
16165
+ },
16166
+ ...host.documentFolder ? { documentFolder: () => host.documentFolder?.() ?? null } : {},
16167
+ ...host.openLink ? { openFile: (href, reveal) => host.openLink?.(href, "openNewTab", reveal) } : {},
16168
+ activityOn: () => currentAiSettings.activityPanel,
16169
+ openActivity: (selectId) => showAiActivityPanel({ selectId }),
16170
+ ...tier2 ? {
16171
+ openRouting: () => showAiEnginesPage({ initial: { section: "routing" } }),
16172
+ ...host.docs ? { openBases: () => showAiEnginesPage({ initial: { section: "bases" } }) } : {}
16173
+ } : {},
16174
+ ...host.saveTextFile ? { saveTextFile: (name, text) => host.saveTextFile?.(name, text) ?? Promise.resolve() } : {},
16175
+ onEscape: () => {
16176
+ if (sourceMode) {
16177
+ cmView?.focus();
16178
+ } else {
16179
+ editor.commands.focus();
16180
+ }
16181
+ },
16182
+ onClose: () => {
16183
+ closeAiChatPanel();
16184
+ restoreFocus(opener);
16185
+ }
16186
+ });
16187
+ const layout = () => {
16188
+ root.classList.toggle("ai-chat-docked", root.clientWidth >= DOCK_MIN_ROOT_PX);
16189
+ };
16190
+ layout();
16191
+ const resize = typeof ResizeObserver === "function" ? new ResizeObserver(layout) : null;
16192
+ resize?.observe(root);
16193
+ aiChatPage = { dock, handle: handle2, resize };
16194
+ handle2.focus();
16195
+ }
16196
+ function revealSection(slug) {
16197
+ if (!slug) {
16198
+ return;
16199
+ }
16200
+ whenAiUi((ui) => {
16201
+ let tries = 0;
16202
+ const attempt = () => {
16203
+ if (destroyed) {
16204
+ return;
16205
+ }
16206
+ if (sourceMode && cmView) {
16207
+ const hit = ui.docsHeadingAt(cmView.state.doc.toString(), slug);
16208
+ if (hit) {
16209
+ cmView.dispatch({ selection: { anchor: hit.offset }, scrollIntoView: true });
16210
+ return;
16211
+ }
16212
+ } else {
16213
+ const hit = ui.docsHeadingAt(editor.storage.markdown.getMarkdown(), slug);
16214
+ let seen = 0;
16215
+ let pos = -1;
16216
+ if (hit) {
16217
+ editor.state.doc.forEach((node, offset) => {
16218
+ if (node.type.name !== "heading") {
16219
+ return;
16220
+ }
16221
+ if (seen === hit.index) {
16222
+ pos = offset;
16223
+ }
16224
+ seen++;
16225
+ });
16226
+ }
16227
+ const dom = pos >= 0 ? editor.view.nodeDOM(pos) : null;
16228
+ if (dom instanceof HTMLElement) {
16229
+ smoothScrollHeadingIntoView(dom);
16230
+ return;
16231
+ }
16232
+ }
16233
+ if (tries++ < 13) {
16234
+ window.setTimeout(attempt, 150);
16235
+ } else {
16236
+ scrollToAnchor(slug);
16237
+ }
16238
+ };
16239
+ attempt();
16240
+ });
16241
+ }
15939
16242
  function trapTab(scope, e) {
15940
16243
  const focusables = Array.from(scope.querySelectorAll(
15941
16244
  'a[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])'
@@ -15992,6 +16295,12 @@ function createMikeDownEditor(container, options) {
15992
16295
  }
15993
16296
  return aiUi.aiFeatureAvailability(aiHost, currentAiSettings, aiStatuses, aiConsent, feature);
15994
16297
  }
16298
+ function noteAiFeatureOff(feature) {
16299
+ const msg = aiUi?.aiFeatureOffNotice(aiHost, currentAiSettings, feature);
16300
+ if (msg) {
16301
+ showTransientToast(msg);
16302
+ }
16303
+ }
15995
16304
  function syncAiSlashCommands() {
15996
16305
  const hidden = [...hiddenSlashCommands];
15997
16306
  for (const feature of ["summary", "title"]) {
@@ -16002,8 +16311,9 @@ function createMikeDownEditor(container, options) {
16002
16311
  setSlashHiddenCommands(hidden);
16003
16312
  }
16004
16313
  let aiPopoverBusy = false;
16314
+ let aiGhostBusy = false;
16005
16315
  function aiBusy() {
16006
- return aiPopoverBusy || propertiesController !== null;
16316
+ return aiPopoverBusy || aiGhostBusy || propertiesController !== null;
16007
16317
  }
16008
16318
  function syncAiToolbarBusy(btn = shell.toolbar?.querySelector('button[data-action="ai"]')) {
16009
16319
  btn?.classList.toggle("is-ai-running", aiBusy());
@@ -16124,8 +16434,14 @@ function createMikeDownEditor(container, options) {
16124
16434
  return a.state === "hidden" ? null : a.state === "disabled" ? { enabled: false, hint: a.reason } : { enabled: true };
16125
16435
  };
16126
16436
  const tier2 = aiHostTier(ai) === 2;
16437
+ const chatAvailability = aiUi?.aiFeatureAvailability(aiHost, currentAiSettings, aiStatuses, aiConsent, "chat") ?? { state: "hidden" };
16438
+ const chat = chatAvailability.state === "hidden" ? null : chatAvailability.state === "disabled" ? { enabled: false, hint: chatAvailability.reason } : { enabled: true };
16127
16439
  const summarize = docEntry("summary");
16128
16440
  const titles = docEntry("title");
16441
+ const ghostState = readOnly ? { state: "hidden" } : ui.ghostAvailability(ai, currentAiSettings, aiStatuses, aiConsent);
16442
+ const ghostReason = aiGhost?.reason();
16443
+ const continuation = ghostState.state === "hidden" ? null : ghostState.state === "disabled" ? { enabled: false, hint: ghostState.reason } : sourceMode ? { enabled: false, hint: "Not available in source view" } : { enabled: true, ...ghostReason ? { hint: ghostReason } : {} };
16444
+ const inlineSuggestions = readOnly ? null : { on: ui.ghostFeatureOn(currentAiSettings) };
16129
16445
  const propsAvailability = outlineSidebar && !readOnly ? aiPropertiesAvailability() : { state: "hidden" };
16130
16446
  const properties = propsAvailability.state === "hidden" ? null : propsAvailability.state === "disabled" ? { enabled: false, hint: propsAvailability.reason } : { enabled: true };
16131
16447
  const thinkKind = tier2 ? aiThinkingKind([
@@ -16137,19 +16453,25 @@ function createMikeDownEditor(container, options) {
16137
16453
  rewrite,
16138
16454
  explain: selectionEntry(true),
16139
16455
  translate: selectionEntry(false),
16456
+ continuation,
16457
+ inlineSuggestions,
16140
16458
  summarize,
16141
16459
  titles,
16142
16460
  properties,
16461
+ chat,
16143
16462
  ...thinkKind ? { thinking: { choices: ui.thinkingChoices(thinkKind), value: ui.loadThinkingLevel, onChange: ui.rememberThinkingLevel } } : {},
16144
16463
  canManageEngines: tier2,
16145
16464
  icons: {
16146
16465
  rewrite: toolbarIcons.rewrite,
16147
16466
  explain: toolbarIcons.explain,
16148
16467
  translate: toolbarIcons.translate,
16468
+ continuation: toolbarIcons.ai,
16149
16469
  summarize: toolbarIcons.toc,
16150
16470
  titles: toolbarIcons.ai,
16151
16471
  properties: toolbarIcons.tag,
16152
- manage: toolbarIcons.gear
16472
+ manage: toolbarIcons.gear,
16473
+ activity: toolbarIcons.aiActivity,
16474
+ chat: toolbarIcons.aiChat
16153
16475
  },
16154
16476
  onRewrite: () => {
16155
16477
  startAiRewrite();
@@ -16160,6 +16482,10 @@ function createMikeDownEditor(container, options) {
16160
16482
  onTranslate: () => {
16161
16483
  startAiRewrite("translate");
16162
16484
  },
16485
+ onSuggestContinuation: () => {
16486
+ startAiGhost();
16487
+ },
16488
+ onToggleInlineSuggestions: setAiInlineSuggestions,
16163
16489
  onSummarize: () => {
16164
16490
  startAiSummary();
16165
16491
  },
@@ -16169,6 +16495,9 @@ function createMikeDownEditor(container, options) {
16169
16495
  onSuggestProperties: () => {
16170
16496
  startAiPropertySuggestions();
16171
16497
  },
16498
+ onChat: () => {
16499
+ showAiChatPanel();
16500
+ },
16172
16501
  onManage: () => {
16173
16502
  if (tier2) {
16174
16503
  showAiEnginesPage();
@@ -16176,6 +16505,7 @@ function createMikeDownEditor(container, options) {
16176
16505
  showSettingsModal("ai");
16177
16506
  }
16178
16507
  },
16508
+ ...currentAiSettings.activityPanel ? { onActivity: () => showAiActivityPanel() } : {},
16179
16509
  // "Look again": retest the engines that failed, then open the menu afresh.
16180
16510
  ...ai.testEngine ? { onRetest: () => {
16181
16511
  void retestAiEngines(ai).then(() => {
@@ -17410,6 +17740,15 @@ function createMikeDownEditor(container, options) {
17410
17740
  }
17411
17741
  return { "Mod-Alt-k": () => startAiRewrite() };
17412
17742
  }
17743
+ }), Extension14.create({
17744
+ // Inline suggestions on demand. A host that owns shortcuts sends the aiSuggestContinuation command instead.
17745
+ name: "mikedownAiGhostShortcut",
17746
+ addKeyboardShortcuts() {
17747
+ if (hostOwnsShortcuts) {
17748
+ return {};
17749
+ }
17750
+ return { "Mod-Alt-Space": () => startAiGhost() };
17751
+ }
17413
17752
  })] : [],
17414
17753
  // ── Placeholder (M2c / M5) ────────────────────────────────────────────────
17415
17754
  // "Type / for commands…" is shown when the slash-command menu is enabled;
@@ -17688,12 +18027,7 @@ function createMikeDownEditor(container, options) {
17688
18027
  return null;
17689
18028
  }
17690
18029
  const popover = ui.createAiRewritePopover({
17691
- run: (req, events, signal) => ui.runAiCompletion({
17692
- ai,
17693
- settings: () => currentAiSettings,
17694
- statuses: () => aiStatuses,
17695
- consent: aiConsent
17696
- }, req, events, signal),
18030
+ run: (req, events, signal) => ui.runAiCompletion(aiRunContext(ui, ai), req, events, signal),
17697
18031
  badge: aiBadge,
17698
18032
  onServed: (served, viaFallback) => {
17699
18033
  aiServed = { served, viaFallback };
@@ -17793,6 +18127,9 @@ function createMikeDownEditor(container, options) {
17793
18127
  const model = currentAiSettings.engines.find((e) => e.id === r.engineId)?.models.find((m) => m.id === r.modelId);
17794
18128
  return { contextTokens: model?.capabilities.contextTokens ?? null, json: model?.capabilities.json === true };
17795
18129
  }
18130
+ function setWholeDocument(body, options2) {
18131
+ editor.chain().setMeta("preventAutolink", true).setContent(body, options2).run();
18132
+ }
17796
18133
  function aiBodyMarkdown() {
17797
18134
  if (sourceMode && cmView) {
17798
18135
  return extractFrontmatter(cmView.state.doc.toString()).body;
@@ -17805,7 +18142,7 @@ function createMikeDownEditor(container, options) {
17805
18142
  return null;
17806
18143
  }
17807
18144
  if (sourceMode) {
17808
- return cmView ? ui.sourceCursorTarget(cmView, (md) => md.length - extractFrontmatter(md).body.length) : null;
18145
+ return cmView ? ui.sourceCursorTarget(cmView, (md) => md.length - extractFrontmatter(md).body.length, editor) : null;
17809
18146
  }
17810
18147
  return ui.cursorInsertTarget(editor, slash);
17811
18148
  }
@@ -17813,7 +18150,11 @@ function createMikeDownEditor(container, options) {
17813
18150
  if (!aiRewrite || destroyed || readOnly) {
17814
18151
  return false;
17815
18152
  }
17816
- if (aiDocAvailability("summary").state !== "enabled") {
18153
+ const availability = aiDocAvailability("summary");
18154
+ if (availability.state !== "enabled") {
18155
+ if (availability.state === "hidden") {
18156
+ noteAiFeatureOff("summary");
18157
+ }
17817
18158
  return false;
17818
18159
  }
17819
18160
  const markdown = aiBodyMarkdown();
@@ -17937,7 +18278,11 @@ function createMikeDownEditor(container, options) {
17937
18278
  if (!ai || !ui || !sidebar || destroyed) {
17938
18279
  return false;
17939
18280
  }
17940
- if (aiPropertiesAvailability().state !== "enabled") {
18281
+ const availability = aiPropertiesAvailability();
18282
+ if (availability.state !== "enabled") {
18283
+ if (availability.state === "hidden" && !readOnly) {
18284
+ noteAiFeatureOff("frontmatter");
18285
+ }
17941
18286
  return false;
17942
18287
  }
17943
18288
  cancelAiPropertySuggestions();
@@ -17962,12 +18307,7 @@ function createMikeDownEditor(container, options) {
17962
18307
  let repairing = false;
17963
18308
  let streamed = "";
17964
18309
  const showRunning = () => sidebar.applyPropertySuggestions(null, "running", repairing ? "Fixing the answer format\u2026" : badge);
17965
- const request = (msgs) => ui.runAiCompletion({
17966
- ai,
17967
- settings: () => currentAiSettings,
17968
- statuses: () => aiStatuses,
17969
- consent: aiConsent
17970
- }, {
18310
+ const request = (msgs) => ui.runAiCompletion(aiRunContext(ui, ai), {
17971
18311
  feature: "frontmatter",
17972
18312
  messages: msgs,
17973
18313
  maxTokens,
@@ -18059,7 +18399,11 @@ function createMikeDownEditor(container, options) {
18059
18399
  if (!aiRewrite || !ui || destroyed || readOnly) {
18060
18400
  return false;
18061
18401
  }
18062
- if (aiDocAvailability("title").state !== "enabled") {
18402
+ const availability = aiDocAvailability("title");
18403
+ if (availability.state !== "enabled") {
18404
+ if (availability.state === "hidden") {
18405
+ noteAiFeatureOff("title");
18406
+ }
18063
18407
  return false;
18064
18408
  }
18065
18409
  const markdown = aiBodyMarkdown();
@@ -18113,6 +18457,9 @@ function createMikeDownEditor(container, options) {
18113
18457
  }
18114
18458
  const availability = aiRewriteAvailability();
18115
18459
  if (availability.state === "hidden") {
18460
+ if (!readOnly) {
18461
+ noteAiFeatureOff("rewrite");
18462
+ }
18116
18463
  return false;
18117
18464
  }
18118
18465
  aiRewrite.close();
@@ -19460,6 +19807,7 @@ function createMikeDownEditor(container, options) {
19460
19807
  return;
19461
19808
  }
19462
19809
  aiRewrite?.close();
19810
+ aiGhost?.dismiss();
19463
19811
  cancelAiPropertySuggestions(true);
19464
19812
  const serialized = serializeDocument();
19465
19813
  const isPristine = lastSavedUndoDepth !== null && pmUndoDepth2(editor.state) === lastSavedUndoDepth;
@@ -19553,7 +19901,7 @@ function createMikeDownEditor(container, options) {
19553
19901
  const wasPristine = pmStale && md === originalContent;
19554
19902
  originalContent = md;
19555
19903
  trailingNewline = md.endsWith("\n");
19556
- editor.commands.setContent(body);
19904
+ setWholeDocument(body);
19557
19905
  resetPmHistory(editor);
19558
19906
  lastSavedUndoDepth = wasPristine ? 0 : null;
19559
19907
  isLoading = false;
@@ -19740,7 +20088,7 @@ function createMikeDownEditor(container, options) {
19740
20088
  if (sourceMode && cmView) {
19741
20089
  cmView.dispatch({ changes: { from: 0, to: cmView.state.doc.length, insert: markdown } });
19742
20090
  } else {
19743
- editor.commands.setContent(body2, { emitUpdate: true });
20091
+ setWholeDocument(body2, { emitUpdate: true });
19744
20092
  }
19745
20093
  renderFrontmatterBlock();
19746
20094
  refreshPropertiesSidebar();
@@ -19773,7 +20121,7 @@ function createMikeDownEditor(container, options) {
19773
20121
  trailingNewline = originalContent.endsWith("\n");
19774
20122
  const { frontmatter, body } = extractFrontmatter(originalContent);
19775
20123
  frontmatterContent = frontmatter;
19776
- editor.commands.setContent(body);
20124
+ setWholeDocument(body);
19777
20125
  resetPmHistory(editor);
19778
20126
  lastSavedUndoDepth = 0;
19779
20127
  const reserialized = serializeDocument();
@@ -19815,6 +20163,7 @@ function createMikeDownEditor(container, options) {
19815
20163
  ` : md;
19816
20164
  }
19817
20165
  function getHtml() {
20166
+ aiGhost?.dismiss();
19818
20167
  return editor.view.dom.innerHTML;
19819
20168
  }
19820
20169
  function markSaved(content) {
@@ -19870,6 +20219,7 @@ function createMikeDownEditor(container, options) {
19870
20219
  }
19871
20220
  root.classList.toggle("mikedown-readonly", next);
19872
20221
  if (next) {
20222
+ aiGhost?.dismiss();
19873
20223
  cancelAiPropertySuggestions(true);
19874
20224
  }
19875
20225
  syncAiPropertiesAvailability();
@@ -19991,6 +20341,13 @@ function createMikeDownEditor(container, options) {
19991
20341
  case "aiSuggestProperties":
19992
20342
  startAiPropertySuggestions();
19993
20343
  break;
20344
+ // The chat panel (a second press goes back to its input).
20345
+ case "aiSuggestContinuation":
20346
+ startAiGhost();
20347
+ break;
20348
+ case "aiChat":
20349
+ showAiChatPanel();
20350
+ break;
19994
20351
  // M11 — Copy as rich text: write HTML + plain text to clipboard.
19995
20352
  case "copyAsRichText":
19996
20353
  copyAsRichText();
@@ -20052,6 +20409,7 @@ function createMikeDownEditor(container, options) {
20052
20409
  }
20053
20410
  }
20054
20411
  function copyAsRichText() {
20412
+ aiGhost?.dismiss();
20055
20413
  const editorEl2 = editor.view.dom;
20056
20414
  if (!navigator.clipboard) {
20057
20415
  return;
@@ -20219,7 +20577,7 @@ function createMikeDownEditor(container, options) {
20219
20577
  const seenIds = /* @__PURE__ */ new Map();
20220
20578
  let targetEl = null;
20221
20579
  for (const h of headings) {
20222
- const base = githubAnchorId(h.textContent || "");
20580
+ const base = githubAnchorId(headingDomText(h));
20223
20581
  const count = seenIds.get(base) ?? 0;
20224
20582
  const id = count === 0 ? base : `${base}-${count}`;
20225
20583
  seenIds.set(base, count + 1);
@@ -20343,6 +20701,8 @@ function createMikeDownEditor(container, options) {
20343
20701
  }
20344
20702
  setSlashDisableHandler(null);
20345
20703
  closeAiEnginesPage();
20704
+ closeAiActivityPanel();
20705
+ closeAiChatPanel();
20346
20706
  closeSettingsModal();
20347
20707
  cmView?.destroy();
20348
20708
  cmView = null;
@@ -20361,6 +20721,7 @@ function createMikeDownEditor(container, options) {
20361
20721
  updateSettings,
20362
20722
  scrollToAnchor,
20363
20723
  revealLink,
20724
+ revealSection,
20364
20725
  revealText,
20365
20726
  setImageStatus,
20366
20727
  insertLink,
@@ -20380,7 +20741,7 @@ function createMikeDownEditor(container, options) {
20380
20741
  renderAiIndicator();
20381
20742
  },
20382
20743
  setAiEngineStatuses: (list) => setAiStatuses(Array.isArray(list) ? list : []),
20383
- isModalOpen: () => !!settingsOverlay?.isConnected || !!aiPage?.overlay.isConnected,
20744
+ isModalOpen: () => !!settingsOverlay?.isConnected || !!aiPage?.overlay.isConnected || !!aiActivityPage?.overlay.isConnected,
20384
20745
  getViewState,
20385
20746
  setViewState,
20386
20747
  focus: () => {