feat(workspace): image blocks via /bild (M2)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m18s

- /bild (only in an empty bullet) turns the bullet into an inline picker:
  file, camera on phones, drag & drop, paste, or an https link the server
  copies. Esc/✕ restores the empty bullet; nothing is synced before the
  upload succeeded.
- Photos are shrunk to 2048 px and stored as WebP (JPEG fallback); the
  re-encoding drops EXIF data such as the location.
- Image block: loaded with the login as blob URL, click opens full
  screen, optional caption kept in the block content
  (![caption](media:id)).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-29 23:14:08 +01:00
1 parent 177fa49363
commit e8fa14a4cb
8 files changed
+476 -4

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
import { describe, expect, it } from 'vitest';
import { mediaContent, parseMediaContent } from '../media';
describe('media content format', () => {
it('round-trips captions, also with brackets and backslashes', () => {
for (const caption of ['', 'Urlaub 2026', 'Plan [Entwurf]', 'C:\\Fotos\\x', 'zwei\nZeilen']) {
const content = mediaContent('image', 'med_0123abcd', caption);
expect(content.startsWith('![')).toBe(true);
expect(parseMediaContent(content)).toEqual({ id: 'med_0123abcd', caption: caption.replace(/\n/g, ' ') });
}
expect(mediaContent('audio', 'med_ff', 'Sprachnotiz')).toBe('[Sprachnotiz](media:med_ff)');
});
it('ignores anything that is not a media reference', () => {
expect(parseMediaContent('![x](https://example.com/a.png)')).toBeNull();
expect(parseMediaContent('Text ![x](media:med_aa)')).toBeNull();
expect(parseMediaContent('![x](media:../etc/passwd)')).toBeNull();
});
});