@memorysmithapp/markdown-renderer 0.5.0 → 0.7.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
@@ -38,7 +38,9 @@ export interface NoteHost {
38
38
  /**
39
39
  * The note a target names, for an embed to draw in place: its body as the host keeps it, its name
40
40
  * and its address. Asked only for a target exactly one note reaches; `null` draws the embed as
41
- * pending. Without it, every embed of a note is pending. The package never fetches.
41
+ * pending. Without it, every embed of a note is pending. The package never fetches. In edit mode
42
+ * each embedded note is also asked for as the editor opens, to lay the page out at its height
43
+ * before it is reached (#94): a host that loads from the network may cache what it answered.
42
44
  */
43
45
  readonly loadNote?: (name: string) => Promise<EmbeddedNote | null>;
44
46
  /**
@@ -92,9 +94,21 @@ export interface NoteHost {
92
94
  /**
93
95
  * Draws a file the notebook keeps where a note embeds it: an image, audio, video, or a card. The
94
96
  * dimensions are the ones the embed declared, in CSS pixels, or `null`.
97
+ *
98
+ * In edit mode the node may be drawn outside the host's React tree, in the root of a block the
99
+ * editor draws, so it must not depend on the host's context; a node that throws there leaves the
100
+ * block's Markdown in its place (#90).
95
101
  */
96
102
  readonly renderAttachment?: (file: AttachmentRequest) => ReactNode;
97
- /** Draws a link to an internal address, through the host's router. */
103
+ /**
104
+ * Draws a link to an internal address, through the host's router.
105
+ *
106
+ * In edit mode the node may be drawn outside the host's React tree, in the root of a block the
107
+ * editor draws — a callout, a table, an embed, the properties — so it must not depend on the host's
108
+ * context: a router's `<Link>`, which reads its router from context, throws there. Hand it what it
109
+ * needs (a `navigate` captured where the router is) or draw an anchor whose click the host follows.
110
+ * A node that throws leaves the block's Markdown in its place (#90).
111
+ */
98
112
  readonly renderLink?: (link: LinkRequest) => ReactNode;
99
113
  /** The words the page shows, in the reader's language; each one left out is MemorySmith's English. */
100
114
  readonly strings?: NoteStringsInput;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { NoteBody, type NoteBodyProps, type NoteMode } from './NoteBody.js';
2
+ export type { NoteButtons } from './NoteActions.js';
2
3
  export type { SaveAction, SaveNotice, SaveOutcome, SaveRefusal } from './edit/saves.js';
3
4
  export { isAttachRefusal } from './host.js';
4
5
  export type { AttachAttributes, AttachmentRequest, AttachProgress, AttachRefusal, EmbeddedNote, KeptFile, KeptFileEntry, LinkChoiceNote, LinkChoices, LinkRequest, NoteHost } from './host.js';
@@ -4,3 +4,4 @@ export declare const ICON_MARKDOWN: import("react").JSX.Element;
4
4
  export declare const ICON_COPY: import("react").JSX.Element;
5
5
  export declare const ICON_CHECK: import("react").JSX.Element;
6
6
  export declare const ICON_CLOSE: import("react").JSX.Element;
7
+ export declare const ICON_OWN_SIZE: import("react").JSX.Element;
@@ -9,3 +9,5 @@ export const ICON_MARKDOWN = svg('M10 12.5 8 15l2 2.5', 'm14 12.5 2 2.5-2 2.5',
9
9
  export const ICON_COPY = svg('M10 8h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H10a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2z', 'M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2');
10
10
  export const ICON_CHECK = svg('M20 6 9 17l-5-5');
11
11
  export const ICON_CLOSE = svg('M18 6 6 18', 'm6 6 12 12');
12
+ // The button that draws an image at its own size (#98): Lucide's scaling (ISC).
13
+ export const ICON_OWN_SIZE = svg('M21 3 9 15', 'M12 3H3v18h18v-9', 'M16 3h5v5', 'M14 15H9v-5');
package/dist/strings.d.ts CHANGED
@@ -109,6 +109,8 @@ export interface EditorWords {
109
109
  readonly attachFailed: string;
110
110
  /** Said after the host's step while more files wait their turn (#88). */
111
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;
112
114
  /** A code block's language, chosen at its top right (#84). */
113
115
  readonly language: string;
114
116
  readonly plainText: string;
@@ -156,6 +158,12 @@ export interface EditorWords {
156
158
  readonly addItem: string;
157
159
  readonly removeItem: string;
158
160
  readonly calloutType: string;
161
+ /** The field of an image's width in edit mode, in pixels (#98). */
162
+ readonly imageWidth: string;
163
+ /** The button that draws an image at its own size again (#98). */
164
+ readonly imageOwnSize: string;
165
+ /** The handle at an image's corner that drags its size (#98). */
166
+ readonly imageResize: string;
159
167
  readonly fold: string;
160
168
  readonly foldNone: string;
161
169
  readonly foldOpen: string;
package/dist/strings.js CHANGED
@@ -113,6 +113,7 @@ export const ENGLISH = {
113
113
  attaching: 'Attaching…',
114
114
  attachFailed: 'The file was not attached',
115
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',
116
117
  language: 'Language',
117
118
  plainText: 'Plain text',
118
119
  mermaidDiagram: 'Mermaid diagram',
@@ -155,6 +156,9 @@ export const ENGLISH = {
155
156
  addItem: 'Add',
156
157
  removeItem: 'Remove',
157
158
  calloutType: 'Type',
159
+ imageWidth: 'Width, in pixels',
160
+ imageOwnSize: 'Its own size',
161
+ imageResize: 'Drag to resize',
158
162
  fold: 'Folding',
159
163
  foldNone: 'Not foldable',
160
164
  foldOpen: 'Folds, open',
package/dist/theme.d.ts CHANGED
@@ -23,6 +23,7 @@
23
23
  import type { ReactNode } from 'react';
24
24
  import type { FileShape } from './file-types.js';
25
25
  import type { KeptFile } from './host.js';
26
+ import type { NoteButtons } from './NoteActions.js';
26
27
  import type { PropertyEntry } from './properties.js';
27
28
  export type Scheme = 'light' | 'dark';
28
29
  /**
@@ -119,6 +120,7 @@ export interface ThemeProperties {
119
120
  readonly chipBg?: string;
120
121
  readonly chipColor?: string;
121
122
  readonly chipFont?: string;
123
+ readonly buttonWordsFrom?: string;
122
124
  }
123
125
  /** The names of `ThemeProperties`, in the order the documentation lists them. */
124
126
  export declare const THEME_PROPERTIES: (keyof ThemeProperties)[];
@@ -251,8 +253,11 @@ export interface ModeSwitchSlotProps {
251
253
  readonly label: string;
252
254
  /** The default theme's icon of that action: a pencil to edit, an eye to read. */
253
255
  readonly icon: ReactNode;
254
- /** Whether the host asked for the icon alone or the icon and its word. */
255
- readonly display: 'icon' | 'label';
256
+ /**
257
+ * How the host asked the buttons around the note to show their word (#96): the icon alone, the
258
+ * icon and its word, or the word only on a wide note (`responsive`).
259
+ */
260
+ readonly display: NoteButtons;
256
261
  /** Switches to the other mode. */
257
262
  readonly toggle: () => void;
258
263
  }
@@ -315,7 +320,10 @@ export interface Theme {
315
320
  };
316
321
  /** An icon per callout kind, drawn in the title in place of the stylesheet's. */
317
322
  readonly icons?: Readonly<Record<string, ReactNode>>;
318
- /** The slots the theme fills. */
323
+ /**
324
+ * The slots the theme fills. In edit mode a slot may be drawn outside the host's React tree, in
325
+ * the root of a block the editor draws, so it must not depend on the host's context (#90).
326
+ */
319
327
  readonly components?: Partial<ThemeComponents>;
320
328
  }
321
329
  /**
package/dist/theme.js CHANGED
@@ -42,6 +42,7 @@ export const THEME_PROPERTIES = Object.keys({
42
42
  propertiesRule: 1, propertiesLabelFont: 1, propertiesLabelColor: 1, propertiesKeyColor: 1,
43
43
  iconPropertyText: 1, iconPropertyList: 1, iconPropertyDate: 1, iconPropertyBoolean: 1,
44
44
  chipBg: 1, chipColor: 1, chipFont: 1,
45
+ buttonWordsFrom: 1,
45
46
  });
46
47
  /** `textSoft` → `--markdown-text-soft`, `h1Size` → `--markdown-h1-size`. */
47
48
  export function customPropertyOf(name) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memorysmithapp/markdown-renderer",
3
- "version": "0.5.0",
3
+ "version": "0.7.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.5.0",
82
+ "@memorysmithapp/markdown-spec": "0.7.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
  }
@@ -90,10 +97,14 @@
90
97
  and the text of a title flows around it. In edit mode it stays there, at
91
98
  the end of a bar that holds the toolbar to its left and the save's status
92
99
  under it, and the bar stays at the top of the note while it scrolls. */
93
- /* The group at the top right: Show Markdown, Copy Markdown and the switch (#76). */
100
+ /* The group at the top right: Show Markdown, Copy Markdown and the switch (#76).
101
+ It stays at the top of what is in view while the note is read and scrolled,
102
+ as the bar does in edit mode, and the text flows around the place it takes
103
+ at the top of the note (#97). */
94
104
  .markdown .note-corner {
95
105
  float: right;
96
- position: relative;
106
+ position: sticky;
107
+ top: 0;
97
108
  z-index: 2;
98
109
  display: inline-flex;
99
110
  align-items: stretch;
@@ -109,6 +120,30 @@
109
120
  .markdown .mode-switch-glyph {
110
121
  flex: none;
111
122
  }
123
+ /* The buttons around a note show their word as the host asked (#96). A
124
+ `responsive` one measures what it stands in — the note in reading, the bar
125
+ in edit mode, the dialog of Show Markdown — as a container, and shows its
126
+ word only from `--markdown-button-words-from` on. A container query cannot
127
+ read a custom property, so the threshold is a switch computed from the
128
+ container's width: 0px on a narrower one and 1px from it on, which the
129
+ word's width and the space before it are multiplied by. The editor's root
130
+ is never the container, because a container holds what is fixed, and the
131
+ menu of a right-click is fixed to the window. */
132
+ .markdown.has-responsive-buttons,
133
+ .markdown .has-responsive-buttons {
134
+ container-type: inline-size;
135
+ }
136
+ .markdown .note-button-word {
137
+ white-space: nowrap;
138
+ }
139
+ .markdown [data-buttons='responsive'] {
140
+ gap: 0;
141
+ }
142
+ .markdown [data-buttons='responsive'] .note-button-word {
143
+ overflow: hidden;
144
+ max-width: calc(clamp(0px, (100cqi - var(--markdown-button-words-from, 560px)) * 1000, 1px) * 10000);
145
+ margin-inline-start: min(calc(clamp(0px, (100cqi - var(--markdown-button-words-from, 560px)) * 1000, 1px) * 10000), 0.35em);
146
+ }
112
147
  .markdown .edit-bar {
113
148
  position: sticky;
114
149
  top: 0;
@@ -139,6 +174,7 @@
139
174
  }
140
175
  .markdown .edit-corner .note-corner {
141
176
  float: none;
177
+ position: relative;
142
178
  }
143
179
  /* On a phone the corner keeps the top right, and the tools take the whole
144
180
  width under it rather than a narrow column beside it. */
@@ -397,6 +433,12 @@
397
433
  .markdown .cm-ms-math-block {
398
434
  display: flow-root;
399
435
  }
436
+ /* A block that could not be drawn shows its lines as they were written (#90). */
437
+ .markdown .cm-ms-block-failed pre {
438
+ margin: 0;
439
+ white-space: pre-wrap;
440
+ overflow-wrap: anywhere;
441
+ }
400
442
  /* A table's cell is where a click edits it: it fills its column and keeps a
401
443
  size of its own when it is empty, as every cell of a new table is, or it
402
444
  would be nothing wide and nothing could be clicked (#68). */
@@ -417,3 +459,28 @@
417
459
  max-width: 100%;
418
460
  vertical-align: middle;
419
461
  }
462
+ /* An image in edit mode carries its size's controls over its top right and a
463
+ handle at its bottom right corner that drags it (#98). */
464
+ .markdown .edit-image {
465
+ position: relative;
466
+ display: inline-block;
467
+ max-width: 100%;
468
+ vertical-align: middle;
469
+ }
470
+ .markdown .edit-image-controls {
471
+ align-items: center;
472
+ white-space: nowrap;
473
+ }
474
+ .markdown .edit-image-width {
475
+ width: 5em;
476
+ }
477
+ .markdown .edit-image-handle {
478
+ position: absolute;
479
+ right: 0;
480
+ bottom: 0;
481
+ z-index: 1;
482
+ width: 0.9rem;
483
+ height: 0.9rem;
484
+ cursor: nwse-resize;
485
+ touch-action: none;
486
+ }
package/theme-default.css CHANGED
@@ -1193,9 +1193,16 @@
1193
1193
  cursor: pointer;
1194
1194
  }
1195
1195
  .markdown .mode-switch.mode-switch-icon,
1196
- .markdown .note-action {
1196
+ .markdown .note-action.note-action-icon {
1197
1197
  padding: 0.3rem 0.4rem;
1198
1198
  }
1199
+ /* A responsive button keeps an icon's padding on a narrow note and takes a
1200
+ word's from --markdown-button-words-from on (#96). */
1201
+ .markdown .mode-switch.mode-switch-responsive,
1202
+ .markdown .note-action.note-action-responsive {
1203
+ padding-block: 0.3rem;
1204
+ padding-inline: calc(0.4rem + min(calc(clamp(0px, (100cqi - var(--markdown-button-words-from, 560px)) * 1000, 1px) * 10000), 0.3rem));
1205
+ }
1199
1206
  .markdown .note-action {
1200
1207
  color: var(--ms-text-soft);
1201
1208
  }
@@ -1913,6 +1920,20 @@
1913
1920
  .markdown .cm-ms-block:hover {
1914
1921
  box-shadow: 0 0 0 1px var(--ms-border);
1915
1922
  }
1923
+ .markdown .cm-ms-block-failed {
1924
+ padding: 0.5rem 0.75rem;
1925
+ border: 1px dashed var(--ms-border);
1926
+ border-radius: var(--ms-radius);
1927
+ }
1928
+ .markdown .cm-ms-block-failed-notice {
1929
+ margin: 0 0 0.35rem;
1930
+ color: var(--ms-text-soft);
1931
+ font-size: 0.85em;
1932
+ }
1933
+ .markdown .cm-ms-block-failed pre {
1934
+ font-family: var(--ms-font-mono);
1935
+ font-size: 0.9em;
1936
+ }
1916
1937
  .markdown .edit-block-controls {
1917
1938
  opacity: 0;
1918
1939
  padding: 0.1rem;
@@ -1922,9 +1943,51 @@
1922
1943
  transition: opacity 0.12s;
1923
1944
  }
1924
1945
  .markdown .cm-ms-block:hover .edit-block-controls,
1925
- .markdown .edit-block:focus-within .edit-block-controls {
1946
+ .markdown .edit-block:focus-within .edit-block-controls,
1947
+ .markdown .edit-image:hover .edit-block-controls,
1948
+ .markdown .edit-image:focus-within .edit-block-controls,
1949
+ .markdown .edit-image[data-resizing] .edit-block-controls {
1950
+ opacity: 1;
1951
+ }
1952
+ .markdown .edit-image-controls {
1953
+ font-size: 0.8rem;
1954
+ line-height: 1.2;
1955
+ }
1956
+ .markdown .edit-image-width.edit-field {
1957
+ height: 1.9rem;
1958
+ border-color: var(--ms-border);
1959
+ background: var(--ms-surface);
1960
+ color: var(--ms-text);
1961
+ font-size: 0.8rem;
1962
+ }
1963
+ .markdown .edit-image-unit {
1964
+ padding-inline-end: 0.2rem;
1965
+ color: var(--ms-text-soft);
1966
+ font-size: 0.8rem;
1967
+ }
1968
+ /* The handle is a corner of the accent's colour, shown with the controls. */
1969
+ .markdown .edit-image-handle {
1970
+ opacity: 0;
1971
+ border-right: 3px solid var(--ms-accent);
1972
+ border-bottom: 3px solid var(--ms-accent);
1973
+ border-bottom-right-radius: 3px;
1974
+ transition: opacity 0.12s;
1975
+ }
1976
+ .markdown .edit-image:hover .edit-image-handle,
1977
+ .markdown .edit-image[data-resizing] .edit-image-handle {
1926
1978
  opacity: 1;
1927
1979
  }
1980
+ /* Where nothing hovers, as on a phone, an image's size is always at hand. */
1981
+ @media (hover: none) {
1982
+ .markdown .edit-image .edit-block-controls,
1983
+ .markdown .edit-image .edit-image-handle {
1984
+ opacity: 1;
1985
+ }
1986
+ }
1987
+ .markdown .edit-image[data-resizing] .cm-ms-image,
1988
+ .markdown .edit-image[data-resizing] img {
1989
+ outline: 1px dashed var(--ms-accent);
1990
+ }
1928
1991
  .markdown .edit-table table {
1929
1992
  width: 100%;
1930
1993
  }