2aa482f62d
Add a visible caption (<figcaption>) under images, editable from the image bubble-menu and persisted across all formats: native Yjs/JSON, HTML export, and Markdown. - image node: new plain-text `caption` attribute (parse/render `data-caption` on <img>, emitted only when set) + `setImageCaption` command. The node stays an atom; the schema shape is unchanged, so the server's generateHTML/generateJSON path round-trips it for free. - resize node-view: re-parent the resizable wrapper into a <figure> and render the caption in a <figcaption> BELOW it, outside nodeView.wrapper (so onCommit's offsetHeight measurement and the left/right resize handles still cover the image only). This path also drives read-only / share rendering. React placeholder view renders the caption too. - bubble-menu: new useCaptionControl panel modeled on useAltTextControl (own icon, Caption strings, softer sanitizer, ~500 char limit). - markdown lossless round-trip: a captioned image is emitted as a raw <img data-caption> wrapped in a block <div> (same trick as <video>) in both the editor-ext turndown rule and the MCP converter; caption-less images stay clean . Import restores the caption via the shared markdownToHtml + parseHTML. - styles + i18n keys; tests for the schema attr round-trip, markdown round-trip (editor-ext) and the MCP converter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
74 lines
1.6 KiB
CSS
74 lines
1.6 KiB
CSS
.ProseMirror {
|
|
img {
|
|
max-width: 100%;
|
|
height: auto;
|
|
|
|
@media print {
|
|
break-inside: avoid;
|
|
}
|
|
}
|
|
|
|
.node-image, .node-video, .node-pdf, .node-excalidraw, .node-drawio {
|
|
&.ProseMirror-selectednode {
|
|
outline: none;
|
|
}
|
|
}
|
|
|
|
.attachment-placeholder {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background-color: var(--mantine-color-body);
|
|
border-radius: var(--mantine-radius-default);
|
|
cursor: pointer;
|
|
padding: 15px;
|
|
height: 25px;
|
|
|
|
@mixin light {
|
|
border: 1px solid var(--mantine-color-gray-3);
|
|
}
|
|
|
|
@mixin dark {
|
|
border: 1px solid var(--mantine-color-dark-4);
|
|
}
|
|
}
|
|
|
|
.image-caption {
|
|
text-align: center;
|
|
font-size: 0.875em;
|
|
color: var(--mantine-color-dimmed);
|
|
margin-top: 0.4em;
|
|
line-height: 1.35;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.uploading-text {
|
|
font-size: var(--mantine-font-size-md);
|
|
line-height: var(--mantine-line-height-md);
|
|
}
|
|
|
|
.media-pulse {
|
|
animation: media-pulse 1.2s ease-in-out infinite;
|
|
|
|
@mixin light {
|
|
background: linear-gradient(-90deg, var(--mantine-color-gray-3) 0%, var(--mantine-color-gray-1) 50%, var(--mantine-color-gray-3) 100%);
|
|
background-size: 400% 400%;
|
|
}
|
|
|
|
@mixin dark {
|
|
background: linear-gradient(-90deg, var(--mantine-color-dark-6) 0%, var(--mantine-color-dark-5) 50%, var(--mantine-color-dark-6) 100%);
|
|
background-size: 400% 400%;
|
|
}
|
|
|
|
@keyframes media-pulse {
|
|
0% {
|
|
background-position: 0% 0%;
|
|
}
|
|
100% {
|
|
background-position: -135% 0%;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|