@aicayzer/inkkit 0.0.13 → 0.0.15

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/style.css CHANGED
@@ -201,22 +201,81 @@
201
201
  max-width: 100%;
202
202
  vertical-align: bottom;
203
203
  }
204
+ /* A standalone image is its own block; the row is full width so a click beside the card lands on it. */
205
+ .inkkit-root .ProseMirror .inkkit-image-block {
206
+ display: block;
207
+ margin: 0 0
208
+ var(--inkkit-paragraph-spacing, calc(var(--inkkit-font-size, 15px) * 0.667));
209
+ line-height: 0;
210
+ }
211
+ .inkkit-root .ProseMirror .inkkit-image-block > .image {
212
+ line-height: normal;
213
+ }
214
+ /* A card that shares its paragraph with text sits on its own line within it. */
215
+ .inkkit-root .ProseMirror :not(.inkkit-image-block) > .image.inkkit-file {
216
+ display: block;
217
+ width: fit-content;
218
+ }
219
+ .inkkit-root .ProseMirror .inkkit-image-block.ProseMirror-selectednode {
220
+ outline: none;
221
+ }
222
+ .inkkit-root
223
+ .ProseMirror
224
+ .inkkit-image-block.ProseMirror-selectednode
225
+ > .image {
226
+ outline: 2px solid color-mix(in srgb, var(--_inkkit-accent) 60%, transparent);
227
+ outline-offset: 2px;
228
+ border-radius: 6px;
229
+ }
230
+ .inkkit-root .ProseMirror-gapcursor {
231
+ display: none;
232
+ pointer-events: none;
233
+ position: absolute;
234
+ }
235
+ .inkkit-root .ProseMirror-gapcursor::after {
236
+ content: '';
237
+ display: block;
238
+ position: absolute;
239
+ top: -2px;
240
+ width: 20px;
241
+ border-top: 2px solid var(--_inkkit-accent);
242
+ animation: inkkit-gapcursor-blink 1.1s steps(2, start) infinite;
243
+ }
244
+ @keyframes inkkit-gapcursor-blink {
245
+ to {
246
+ visibility: hidden;
247
+ }
248
+ }
249
+ .inkkit-root .ProseMirror-focused .ProseMirror-gapcursor {
250
+ display: block;
251
+ }
204
252
  .inkkit-root .ProseMirror .image img {
205
253
  display: block;
206
254
  max-width: 100%;
207
255
  height: auto;
208
256
  border-radius: 6px;
209
257
  }
258
+ .inkkit-root .ProseMirror .inkkit-image-preview {
259
+ position: relative;
260
+ display: block;
261
+ width: fit-content;
262
+ max-width: 100%;
263
+ line-height: 0;
264
+ }
210
265
  .inkkit-root .ProseMirror .image-absent {
211
266
  color: var(--_inkkit-muted);
212
267
  font-style: italic;
213
268
  }
214
269
  .inkkit-root .ProseMirror .image-handle {
215
270
  position: absolute;
216
- right: -3px;
217
- top: 0;
218
- width: 10px;
219
- height: 100%;
271
+ right: 0;
272
+ top: 50%;
273
+ z-index: 1;
274
+ transform: translateY(-50%);
275
+ width: 20px;
276
+ height: 32px;
277
+ max-height: 100%;
278
+ touch-action: none;
220
279
  cursor: col-resize;
221
280
  opacity: 0;
222
281
  }
@@ -227,22 +286,58 @@
227
286
  .inkkit-root .ProseMirror .image-handle::after {
228
287
  content: '';
229
288
  position: absolute;
230
- right: 3px;
231
- top: 25%;
232
- height: 50%;
289
+ right: 8px;
290
+ top: 50%;
291
+ transform: translateY(-50%);
292
+ height: 20px;
293
+ max-height: 100%;
233
294
  width: 4px;
234
295
  border-radius: 2px;
235
296
  background: var(--_inkkit-accent);
236
297
  }
298
+ .inkkit-root .ProseMirror[contenteditable='false'] .image-handle {
299
+ display: none;
300
+ }
237
301
 
238
302
  .inkkit-root .ProseMirror .inkkit-file {
303
+ box-sizing: border-box;
239
304
  vertical-align: middle;
240
305
  border: 1px solid var(--_inkkit-rule);
241
- border-radius: 4px;
242
- padding: 4px;
306
+ border-radius: 6px;
307
+ padding: 6px;
243
308
  white-space: normal;
244
309
  }
245
310
 
311
+ .inkkit-root .ProseMirror .inkkit-file-footer {
312
+ display: flex;
313
+ flex-wrap: wrap;
314
+ align-items: center;
315
+ gap: 0.25em 0.5em;
316
+ font-size: 0.9em;
317
+ line-height: 1.4;
318
+ color: var(--_inkkit-muted);
319
+ }
320
+ .inkkit-root .ProseMirror .inkkit-file-footer:not(:first-child) {
321
+ margin-block-start: 0.25em;
322
+ }
323
+ /* Below a picture, the name takes the picture's width rather than widening the card, so the grip
324
+ stays at the picture's edge; a short picture still leaves room for a few characters. */
325
+ .inkkit-root .ProseMirror .inkkit-image-preview ~ .inkkit-file-footer {
326
+ width: 0;
327
+ min-width: 100%;
328
+ }
329
+ .inkkit-root .ProseMirror .image.inkkit-file:has(> .inkkit-image-preview) {
330
+ min-width: min(100%, 8em);
331
+ }
332
+ /* The name truncates; the open arrow stays visible right after it. */
333
+ .inkkit-root .ProseMirror .inkkit-file-name {
334
+ display: flex;
335
+ flex: 1 1 auto;
336
+ align-items: center;
337
+ gap: 0.25em;
338
+ min-width: 0;
339
+ }
340
+
246
341
  .inkkit-root .ProseMirror .inkkit-file audio,
247
342
  .inkkit-root .ProseMirror .inkkit-file video,
248
343
  .inkkit-root .ProseMirror .inkkit-file iframe {
@@ -261,18 +356,57 @@
261
356
  }
262
357
 
263
358
  .inkkit-root .ProseMirror .inkkit-file-label {
359
+ flex: 0 1 auto;
360
+ min-width: 0;
361
+ overflow: hidden;
362
+ text-overflow: ellipsis;
363
+ white-space: nowrap;
364
+ }
365
+ .inkkit-root .ProseMirror .inkkit-file-status {
366
+ flex-basis: 100%;
264
367
  overflow-wrap: anywhere;
265
368
  }
266
369
 
267
370
  .inkkit-root .ProseMirror .inkkit-file-control {
268
- margin-inline-start: 0.5em;
371
+ flex: none;
372
+ padding: 0.25em 0.5em;
269
373
  font: inherit;
374
+ line-height: 1.4;
375
+ white-space: nowrap;
270
376
  color: var(--_inkkit-text);
271
377
  background: var(--_inkkit-code-bg);
272
378
  border: 1px solid var(--_inkkit-rule);
273
379
  border-radius: 3px;
274
380
  cursor: pointer;
275
381
  }
382
+ .inkkit-root .ProseMirror .inkkit-file-open {
383
+ display: inline-flex;
384
+ align-items: center;
385
+ justify-content: center;
386
+ min-width: 1.5em;
387
+ min-height: 1.5em;
388
+ padding: 0;
389
+ color: var(--_inkkit-muted);
390
+ background: transparent;
391
+ border-color: transparent;
392
+ }
393
+ .inkkit-root .ProseMirror .inkkit-file-open:hover {
394
+ color: var(--_inkkit-text);
395
+ background: var(--_inkkit-code-bg);
396
+ }
397
+ .inkkit-root .ProseMirror .inkkit-file-open:focus-visible {
398
+ outline: 2px solid var(--_inkkit-accent);
399
+ outline-offset: 1px;
400
+ }
401
+ .inkkit-root .ProseMirror .inkkit-file-open svg {
402
+ width: 0.85em;
403
+ height: 0.85em;
404
+ fill: none;
405
+ stroke: currentColor;
406
+ stroke-width: 1.5;
407
+ stroke-linecap: round;
408
+ stroke-linejoin: round;
409
+ }
276
410
 
277
411
  .inkkit-root .ProseMirror {
278
412
  /* The viewport, not the wrapper Milkdown adds, so a click below the text still places the caret. */
@@ -18,7 +18,10 @@ export declare function projectionOffset(projection: TextProjection, position: n
18
18
  export declare function validateOffsets(text: string, from: number, to: number): void;
19
19
  export declare function textRect(rect: Pick<TextRect, 'left' | 'top' | 'right' | 'bottom'>): TextRect;
20
20
  export declare function formattedRects(view: EditorView, projection: TextProjection, from: number, to: number): TextRect[];
21
- export declare function literalRects(plain: HTMLTextAreaElement, from: number, to: number): TextRect[];
21
+ export declare function literalRangesRects(plain: HTMLTextAreaElement, ranges: readonly {
22
+ from: number;
23
+ to: number;
24
+ }[]): TextRect[][];
22
25
  export declare function intersectsViewport(rect: Pick<TextRect, 'top' | 'right' | 'bottom' | 'left'>, viewport: TextRect): boolean;
23
26
  export declare function literalVisibleRanges(plain: HTMLTextAreaElement, viewport: TextRect): {
24
27
  from: number;
@@ -1,5 +1,6 @@
1
1
  import { InkKitError } from './types.js';
2
2
  import { fileReference } from './linked-syntax.js';
3
+ import { imageBlockName } from './image-blocks.js';
3
4
  export function readableProjection(doc) {
4
5
  const result = { text: '', spans: [] };
5
6
  let blocks = 0;
@@ -24,6 +25,11 @@ export function readableProjection(doc) {
24
25
  String(node.attrs.kind).replace(/^./, (value) => value.toUpperCase()), position, position + 1, 'embed');
25
26
  return true;
26
27
  }
28
+ if (name === imageBlockName) {
29
+ block(position);
30
+ append(fileReference(node).label || 'Image', position, position + node.nodeSize, 'embed');
31
+ return false;
32
+ }
27
33
  if (node.isTextblock) {
28
34
  block(position + 1);
29
35
  if (!node.content.size)
@@ -110,15 +116,30 @@ export function formattedRects(view, projection, from, to) {
110
116
  return [textRect(view.coordsAtPos(position))];
111
117
  }
112
118
  const rects = [];
113
- for (const span of projection.spans) {
114
- if (span.to <= from || span.from >= to || span.kind === 'separator')
119
+ let low = 0;
120
+ let high = projection.spans.length;
121
+ while (low < high) {
122
+ const middle = (low + high) >>> 1;
123
+ if (projection.spans[middle].to <= from)
124
+ low = middle + 1;
125
+ else
126
+ high = middle;
127
+ }
128
+ for (let index = low; index < projection.spans.length; index++) {
129
+ const span = projection.spans[index];
130
+ if (span.from >= to)
131
+ break;
132
+ if (span.kind === 'separator')
115
133
  continue;
116
134
  if (span.kind === 'embed' || span.kind === 'break') {
117
135
  const node = view.nodeDOM(span.start);
118
136
  if (node instanceof Element) {
137
+ // A standalone image's node is its full-width row; the card is what shows.
119
138
  const label = node.matches('.inkkit-callout')
120
139
  ? (node.querySelector(':scope > .inkkit-callout-header > .inkkit-callout-title') ?? node)
121
- : node;
140
+ : node.matches('.inkkit-image-block')
141
+ ? (node.firstElementChild ?? node)
142
+ : node;
122
143
  rects.push(...Array.from(label.getClientRects(), textRect));
123
144
  }
124
145
  continue;
@@ -180,18 +201,20 @@ function withLiteralLayout(plain, measure) {
180
201
  mirror.remove();
181
202
  }
182
203
  }
183
- export function literalRects(plain, from, to) {
204
+ export function literalRangesRects(plain, ranges) {
184
205
  return (withLiteralLayout(plain, (text) => {
185
206
  const range = plain.ownerDocument.createRange();
186
- range.setStart(text, from);
187
- range.setEnd(text, from === to ? Math.min(to + 1, text.length) : to);
188
- return Array.from(range.getClientRects(), (rect) => textRect({
189
- left: rect.left,
190
- right: from === to ? rect.left : rect.right,
191
- top: rect.top,
192
- bottom: rect.bottom,
193
- }));
194
- }) ?? []);
207
+ return ranges.map(({ from, to }) => {
208
+ range.setStart(text, from);
209
+ range.setEnd(text, from === to ? Math.min(to + 1, text.length) : to);
210
+ return Array.from(range.getClientRects(), (rect) => textRect({
211
+ left: rect.left,
212
+ right: from === to ? rect.left : rect.right,
213
+ top: rect.top,
214
+ bottom: rect.bottom,
215
+ }));
216
+ });
217
+ }) ?? ranges.map(() => []));
195
218
  }
196
219
  export function intersectsViewport(rect, viewport) {
197
220
  return (viewport.width > 0 &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aicayzer/inkkit",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "description": "A preserving Markdown editor for offline web views and the web",
5
5
  "license": "MIT",
6
6
  "type": "module",