@memorysmithapp/markdown-renderer 0.4.0 → 0.6.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/host.d.ts CHANGED
@@ -71,8 +71,13 @@ export interface NoteHost {
71
71
  * has its own name and none), and answers the name it is kept under, which the editor embeds as
72
72
  * `![[name]]` (#75, #78). The package never stores a file. Without it, the editor offers no file
73
73
  * from the computer.
74
+ *
75
+ * While it keeps the file, the host may report each step through `progress`, which the paperclip
76
+ * shows and announces; a promise rejected with an `AttachRefusal` has the paperclip say why until
77
+ * the author acts (#88). A host that ignores `progress` and rejects with an error keeps the
78
+ * paperclip's own words.
74
79
  */
75
- readonly attach?: (file: File, attributes: AttachAttributes) => string | PromiseLike<string>;
80
+ readonly attach?: (file: File, attributes: AttachAttributes, progress: (report: AttachProgress) => void) => string | PromiseLike<string>;
76
81
  /**
77
82
  * The files the notebook keeps, with their names, which the attach dialog lists for the author to
78
83
  * embed one (#78). Without it, the dialog offers no file of the notebook.
@@ -87,9 +92,21 @@ export interface NoteHost {
87
92
  /**
88
93
  * Draws a file the notebook keeps where a note embeds it: an image, audio, video, or a card. The
89
94
  * dimensions are the ones the embed declared, in CSS pixels, or `null`.
95
+ *
96
+ * In edit mode the node may be drawn outside the host's React tree, in the root of a block the
97
+ * editor draws, so it must not depend on the host's context; a node that throws there leaves the
98
+ * block's Markdown in its place (#90).
90
99
  */
91
100
  readonly renderAttachment?: (file: AttachmentRequest) => ReactNode;
92
- /** Draws a link to an internal address, through the host's router. */
101
+ /**
102
+ * Draws a link to an internal address, through the host's router.
103
+ *
104
+ * In edit mode the node may be drawn outside the host's React tree, in the root of a block the
105
+ * editor draws — a callout, a table, an embed, the properties — so it must not depend on the host's
106
+ * context: a router's `<Link>`, which reads its router from context, throws there. Hand it what it
107
+ * needs (a `navigate` captured where the router is) or draw an anchor whose click the host follows.
108
+ * A node that throws leaves the block's Markdown in its place (#90).
109
+ */
93
110
  readonly renderLink?: (link: LinkRequest) => ReactNode;
94
111
  /** The words the page shows, in the reader's language; each one left out is MemorySmith's English. */
95
112
  readonly strings?: NoteStringsInput;
@@ -155,6 +172,23 @@ export interface KeptFile {
155
172
  export interface KeptFileEntry extends KeptFile {
156
173
  readonly name: string;
157
174
  }
175
+ /** A step of keeping a file, as the host words it (#88). */
176
+ export interface AttachProgress {
177
+ /** What is happening, in the reader's language: "Sending part 12 of 40". */
178
+ readonly message: string;
179
+ /** How much is done, from 0 to 1, when the host knows it: drawn as a determinate indicator. */
180
+ readonly fraction?: number;
181
+ }
182
+ /**
183
+ * Why the host would not keep a file, what `attach` rejects with (#88): a plain object, so that an
184
+ * `Error`, whose message is meant for a developer, keeps the paperclip's own words.
185
+ */
186
+ export interface AttachRefusal {
187
+ /** Why, in the reader's language: "An attached file goes up to 100 MB". */
188
+ readonly message: string;
189
+ }
190
+ /** Whether a rejection is a refusal the host worded for the author, and not an error. */
191
+ export declare function isAttachRefusal(value: unknown): value is AttachRefusal;
158
192
  /** What the author said of a file they attach (#78). */
159
193
  export interface AttachAttributes {
160
194
  /** The name to keep it under; the host may answer another, when that one is taken. */
@@ -188,7 +222,7 @@ export interface ResolvedHost {
188
222
  readonly loadNote: ((name: string) => Promise<EmbeddedNote | null>) | null;
189
223
  readonly keeps: (name: string) => boolean;
190
224
  readonly suggest: ((query: string) => readonly string[] | PromiseLike<readonly string[]>) | null;
191
- readonly attach: ((file: File, attributes: AttachAttributes) => string | PromiseLike<string>) | null;
225
+ readonly attach: ((file: File, attributes: AttachAttributes, progress: (report: AttachProgress) => void) => string | PromiseLike<string>) | null;
192
226
  readonly files: (() => readonly KeptFileEntry[] | PromiseLike<readonly KeptFileEntry[]>) | null;
193
227
  readonly fileOf: ((name: string) => KeptFile | null | PromiseLike<KeptFile | null>) | null;
194
228
  readonly renderAttachment: ((file: AttachmentRequest) => ReactNode) | null;
package/dist/host.js CHANGED
@@ -2,6 +2,14 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useContext } from 'react';
3
3
  import { withDefaults } from './strings.js';
4
4
  import { DEFAULT_THEME, schemeOf } from './theme.js';
5
+ /** Whether a rejection is a refusal the host worded for the author, and not an error. */
6
+ export function isAttachRefusal(value) {
7
+ return (typeof value === 'object' &&
8
+ value !== null &&
9
+ !(value instanceof Error) &&
10
+ typeof value.message === 'string' &&
11
+ value.message.length > 0);
12
+ }
5
13
  /**
6
14
  * Every member of the contract, by name. The `satisfies` is what makes it complete: a member added
7
15
  * to `NoteHost` and not listed here does not compile.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export { NoteBody, type NoteBodyProps, type NoteMode } from './NoteBody.js';
2
- export type { AttachAttributes, AttachmentRequest, EmbeddedNote, KeptFile, KeptFileEntry, LinkChoiceNote, LinkChoices, LinkRequest, NoteHost } from './host.js';
2
+ export type { SaveAction, SaveNotice, SaveOutcome, SaveRefusal } from './edit/saves.js';
3
+ export { isAttachRefusal } from './host.js';
4
+ export type { AttachAttributes, AttachmentRequest, AttachProgress, AttachRefusal, EmbeddedNote, KeptFile, KeptFileEntry, LinkChoiceNote, LinkChoices, LinkRequest, NoteHost } from './host.js';
3
5
  export { ENGLISH, type EditorWords, type NoteStrings, type NoteStringsInput } from './strings.js';
4
6
  export { customPropertyOf, DEFAULT_THEME, defineTheme, THEME_PROPERTIES, THEME_SLOTS, type BlockquoteSlotProps, type CalloutSlotProps, type CodeBlockSlotProps, type DiagramSlotProps, type EmbedSlotProps, type FootnotesSlotProps, type HeadingSlotProps, type InlineCodeSlotProps, type ModeSwitchSlotProps, type AttachmentSlotProps, type PropertiesSlotProps, type Scheme, type TableSlotProps, type TaskBoxSlotProps, type Theme, type ThemeComponents, type ThemeProperties, type WikilinkSlotProps, } from './theme.js';
5
7
  export { followable } from './address.js';
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // The entry point of @memorysmithapp/markdown-renderer.
2
2
  export { NoteBody } from './NoteBody.js';
3
+ export { isAttachRefusal } from './host.js';
3
4
  export { ENGLISH } from './strings.js';
4
5
  export { customPropertyOf, DEFAULT_THEME, defineTheme, THEME_PROPERTIES, THEME_SLOTS, } from './theme.js';
5
6
  export { followable } from './address.js';
package/dist/strings.d.ts CHANGED
@@ -107,6 +107,10 @@ export interface EditorWords {
107
107
  readonly attach: string;
108
108
  readonly attaching: string;
109
109
  readonly attachFailed: string;
110
+ /** Said after the host's step while more files wait their turn (#88). */
111
+ readonly attachQueued: (count: number) => string;
112
+ /** Said over a drawn block's lines when drawing it failed, so the block is never left empty (#90). */
113
+ readonly blockFailed: string;
110
114
  /** A code block's language, chosen at its top right (#84). */
111
115
  readonly language: string;
112
116
  readonly plainText: string;
package/dist/strings.js CHANGED
@@ -112,6 +112,8 @@ export const ENGLISH = {
112
112
  attach: 'Attach a file',
113
113
  attaching: 'Attaching…',
114
114
  attachFailed: 'The file was not attached',
115
+ attachQueued: (count) => (count === 1 ? '1 more file waiting' : `${count} more files waiting`),
116
+ blockFailed: 'This block could not be drawn; its Markdown is below',
115
117
  language: 'Language',
116
118
  plainText: 'Plain text',
117
119
  mermaidDiagram: 'Mermaid diagram',
package/dist/theme.d.ts CHANGED
@@ -315,7 +315,10 @@ export interface Theme {
315
315
  };
316
316
  /** An icon per callout kind, drawn in the title in place of the stylesheet's. */
317
317
  readonly icons?: Readonly<Record<string, ReactNode>>;
318
- /** The slots the theme fills. */
318
+ /**
319
+ * The slots the theme fills. In edit mode a slot may be drawn outside the host's React tree, in
320
+ * the root of a block the editor draws, so it must not depend on the host's context (#90).
321
+ */
319
322
  readonly components?: Partial<ThemeComponents>;
320
323
  }
321
324
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memorysmithapp/markdown-renderer",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "A React component that draws a MemorySmith note exactly as MemorySmith's reading page does.",
5
5
  "keywords": [
6
6
  "markdown",
@@ -79,7 +79,7 @@
79
79
  "unified": "11.0.5"
80
80
  },
81
81
  "devDependencies": {
82
- "@memorysmithapp/markdown-spec": "0.4.0",
82
+ "@memorysmithapp/markdown-spec": "0.6.0",
83
83
  "@types/hast": "3.0.5",
84
84
  "@types/react": "19.2.18",
85
85
  "@types/react-dom": "19.2.4",
package/structure.css CHANGED
@@ -15,6 +15,13 @@
15
15
  box-sizing: border-box;
16
16
  }
17
17
 
18
+ /* What is laid out of the flow hangs from the note, never from the page: a
19
+ host that scrolls the note in a pane of its own keeps a page of its own
20
+ height (#93). */
21
+ .markdown {
22
+ position: relative;
23
+ }
24
+
18
25
  .markdown img {
19
26
  max-width: 100%;
20
27
  }
@@ -233,6 +240,17 @@
233
240
  /* The attach dialog (#78): its ways in as tabs, a panel that alone scrolls,
234
241
  and the files of the notebook as a list of buttons. */
235
242
  /* One height for every way in, so the dialog does not jump between them. */
243
+ /* The paperclip and, under it, the bar of the host's fraction (#88). */
244
+ .markdown .edit-attach-control {
245
+ position: relative;
246
+ display: inline-flex;
247
+ }
248
+ .markdown .edit-attach-progress {
249
+ position: absolute;
250
+ inset-inline: 0.2rem;
251
+ bottom: 0;
252
+ pointer-events: none;
253
+ }
236
254
  .markdown .edit-attach .edit-source-frame {
237
255
  height: min(34rem, calc(100vh - 4rem));
238
256
  }
@@ -291,6 +309,19 @@
291
309
  display: flex;
292
310
  justify-content: flex-end;
293
311
  }
312
+ /* What the host said of a save (#86): its title, its explanation, and its
313
+ choices in a row that wraps, as narrow as its words. */
314
+ .markdown .edit-notice {
315
+ max-width: min(32rem, calc(100vw - 2rem));
316
+ }
317
+ .markdown .edit-notice-message {
318
+ margin: 0;
319
+ }
320
+ .markdown .edit-notice-actions {
321
+ display: flex;
322
+ flex-wrap: wrap;
323
+ justify-content: flex-end;
324
+ }
294
325
  @media (max-width: 560px) {
295
326
  .markdown .edit-attach-file {
296
327
  grid-template-columns: 1fr auto;
@@ -373,6 +404,12 @@
373
404
  .markdown .cm-ms-math-block {
374
405
  display: flow-root;
375
406
  }
407
+ /* A block that could not be drawn shows its lines as they were written (#90). */
408
+ .markdown .cm-ms-block-failed pre {
409
+ margin: 0;
410
+ white-space: pre-wrap;
411
+ overflow-wrap: anywhere;
412
+ }
376
413
  /* A table's cell is where a click edits it: it fills its column and keeps a
377
414
  size of its own when it is empty, as every cell of a new table is, or it
378
415
  would be nothing wide and nothing could be clicked (#68). */
package/theme-default.css CHANGED
@@ -1298,6 +1298,9 @@
1298
1298
  .markdown .edit-save[data-save='failed'] {
1299
1299
  color: rgb(var(--ms-callout-stop));
1300
1300
  }
1301
+ .markdown .edit-save[data-save='alert'] {
1302
+ color: rgb(var(--ms-callout-warn));
1303
+ }
1301
1304
  /* Asked to leave while the last save failed: the corner shakes once. */
1302
1305
  .markdown .edit-corner[data-blocked] {
1303
1306
  animation: markdown-corner-refuse 360ms ease-in-out;
@@ -1322,6 +1325,14 @@
1322
1325
  .markdown .edit-control[data-attach='failed'] {
1323
1326
  color: rgb(var(--ms-callout-stop));
1324
1327
  }
1328
+ .markdown .edit-attach-progress {
1329
+ height: 2px;
1330
+ border-radius: 1px;
1331
+ background: linear-gradient(to right, var(--ms-accent) calc(var(--edit-attach-fraction, 0) * 100%), var(--ms-border) 0);
1332
+ }
1333
+ .markdown .edit-attach-refusal {
1334
+ padding: 0.6rem 1rem 0;
1335
+ }
1325
1336
  @media (prefers-reduced-motion: reduce) {
1326
1337
  .markdown .edit-control[data-attach='busy'] > svg {
1327
1338
  animation: none;
@@ -1332,7 +1343,8 @@
1332
1343
  transform: rotate(360deg);
1333
1344
  }
1334
1345
  }
1335
- .markdown .edit-save-retry {
1346
+ .markdown .edit-save-retry,
1347
+ .markdown .edit-save-notice {
1336
1348
  padding: 0;
1337
1349
  border: 0;
1338
1350
  border-radius: calc(var(--ms-radius) - 2px);
@@ -1340,10 +1352,12 @@
1340
1352
  color: inherit;
1341
1353
  cursor: pointer;
1342
1354
  }
1343
- .markdown .edit-save-retry:hover {
1355
+ .markdown .edit-save-retry:hover,
1356
+ .markdown .edit-save-notice:hover {
1344
1357
  background: var(--ms-code-bg);
1345
1358
  }
1346
- .markdown .edit-save-retry:focus-visible {
1359
+ .markdown .edit-save-retry:focus-visible,
1360
+ .markdown .edit-save-notice:focus-visible {
1347
1361
  outline: 2px solid var(--ms-accent);
1348
1362
  outline-offset: 2px;
1349
1363
  }
@@ -1637,6 +1651,19 @@
1637
1651
  font-size: 1rem;
1638
1652
  font-weight: 600;
1639
1653
  }
1654
+ /* What the host said of a save: the dialog's own size, whatever the toolbar it opens from. */
1655
+ .markdown .edit-notice {
1656
+ font-family: var(--ms-font-sans);
1657
+ font-size: 0.95rem;
1658
+ line-height: 1.55;
1659
+ }
1660
+ .markdown .edit-notice-message {
1661
+ padding: 1rem 1rem 0;
1662
+ }
1663
+ .markdown .edit-notice-actions {
1664
+ gap: 0.5rem;
1665
+ padding: 1rem;
1666
+ }
1640
1667
  .markdown .edit-source-copied {
1641
1668
  color: var(--ms-text-soft);
1642
1669
  font-size: 0.8rem;
@@ -1886,6 +1913,20 @@
1886
1913
  .markdown .cm-ms-block:hover {
1887
1914
  box-shadow: 0 0 0 1px var(--ms-border);
1888
1915
  }
1916
+ .markdown .cm-ms-block-failed {
1917
+ padding: 0.5rem 0.75rem;
1918
+ border: 1px dashed var(--ms-border);
1919
+ border-radius: var(--ms-radius);
1920
+ }
1921
+ .markdown .cm-ms-block-failed-notice {
1922
+ margin: 0 0 0.35rem;
1923
+ color: var(--ms-text-soft);
1924
+ font-size: 0.85em;
1925
+ }
1926
+ .markdown .cm-ms-block-failed pre {
1927
+ font-family: var(--ms-font-mono);
1928
+ font-size: 0.9em;
1929
+ }
1889
1930
  .markdown .edit-block-controls {
1890
1931
  opacity: 0;
1891
1932
  padding: 0.1rem;