点中或悬停图片四角出把手,按原比例缩放。宽写回 style、去掉高(学生端高一律 auto): 老题面的图片尺寸都在 style 里,Tiptap 自带的 resize 写的是 width / height 属性, 被 style 盖住,拖完存下来还是原尺寸,所以自己接了 ResizableNodeView。 解析和输出没动,不拖的图片打开再保存和原来一样。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9e2217e09d
commit
4000b3a9f0
2 files changed
+93
-1
No files matched your search
@@ -692,6 +692,43 @@ const inTable = computed(() => isActive("table"))
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 图片外面那层可缩放的容器(richText.ts 的 StyledImage):选中或悬停时四个角出把手 */
|
||||||
|
.content :deep([data-resize-container]) {
|
||||||
|
max-width: 100%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content :deep([data-resize-wrapper]) {
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content :deep([data-resize-handle]) {
|
||||||
|
display: none;
|
||||||
|
width: 9px;
|
||||||
|
height: 9px;
|
||||||
|
margin: -4px;
|
||||||
|
border: 1px solid #fff;
|
||||||
|
background: v-bind("theme.primaryColor");
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content :deep([data-resize-container]:hover [data-resize-handle]),
|
||||||
|
.content :deep(.ProseMirror-selectednode [data-resize-handle]),
|
||||||
|
.content :deep([data-resize-state="true"] [data-resize-handle]) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content :deep([data-resize-handle="top-left"]),
|
||||||
|
.content :deep([data-resize-handle="bottom-right"]) {
|
||||||
|
cursor: nwse-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content :deep([data-resize-handle="top-right"]),
|
||||||
|
.content :deep([data-resize-handle="bottom-left"]) {
|
||||||
|
cursor: nesw-resize;
|
||||||
|
}
|
||||||
|
|
||||||
.content :deep(.ProseMirror-selectednode) {
|
.content :deep(.ProseMirror-selectednode) {
|
||||||
outline: 2px solid v-bind("theme.primaryColor");
|
outline: 2px solid v-bind("theme.primaryColor");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Extension } from "@tiptap/core"
|
import { Extension, getRenderedAttributes, ResizableNodeView } from "@tiptap/core"
|
||||||
import { Image } from "@tiptap/extension-image"
|
import { Image } from "@tiptap/extension-image"
|
||||||
import { TableKit } from "@tiptap/extension-table"
|
import { TableKit } from "@tiptap/extension-table"
|
||||||
import { TextAlign } from "@tiptap/extension-text-align"
|
import { TextAlign } from "@tiptap/extension-text-align"
|
||||||
@@ -27,6 +27,61 @@ const StyledImage = Image.extend({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 拖四个角缩放。Tiptap 自带的 resize 把尺寸写进 width / height 属性,可老图的尺寸在 style 里、
|
||||||
|
// 盖过属性,拖完存下来还是原来的大小 —— 所以自己接:宽写回 style,高去掉(学生端高一律 auto)
|
||||||
|
addNodeView() {
|
||||||
|
return ({ node, getPos, editor }) => {
|
||||||
|
const img = document.createElement("img")
|
||||||
|
const extensionAttributes = editor.extensionManager.attributes
|
||||||
|
|
||||||
|
/** 节点属性原样落到 <img> 上,编辑区和存下来的 HTML 是同一个样子 */
|
||||||
|
const render = (node: Parameters<typeof getRenderedAttributes>[0]) => {
|
||||||
|
const attributes = getRenderedAttributes(node, extensionAttributes)
|
||||||
|
for (const name of img.getAttributeNames()) {
|
||||||
|
if (attributes[name] == null) img.removeAttribute(name)
|
||||||
|
}
|
||||||
|
for (const [name, value] of Object.entries(attributes)) {
|
||||||
|
if (value != null) img.setAttribute(name, value)
|
||||||
|
}
|
||||||
|
// 拖动的是外面那层容器,图片自己不能被浏览器当成原生拖拽
|
||||||
|
img.draggable = false
|
||||||
|
}
|
||||||
|
|
||||||
|
render(node)
|
||||||
|
const view = new ResizableNodeView({
|
||||||
|
element: img,
|
||||||
|
editor,
|
||||||
|
node,
|
||||||
|
getPos,
|
||||||
|
onCommit: (width) => {
|
||||||
|
const pos = getPos()
|
||||||
|
if (pos === undefined) return
|
||||||
|
const { style } = document.createElement("img")
|
||||||
|
style.cssText = view.node.attrs.style ?? ""
|
||||||
|
style.removeProperty("height")
|
||||||
|
style.width = `${Math.round(width)}px`
|
||||||
|
editor
|
||||||
|
.chain()
|
||||||
|
.setNodeSelection(pos)
|
||||||
|
.updateAttributes(this.name, { style: style.cssText, width: null, height: null })
|
||||||
|
.run()
|
||||||
|
},
|
||||||
|
onUpdate: (node) => {
|
||||||
|
render(node)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
directions: ["top-left", "top-right", "bottom-left", "bottom-right"],
|
||||||
|
min: { width: 24, height: 24 },
|
||||||
|
preserveAspectRatio: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
// 它会拿 width 属性往 style 里写一份,和 style 里原有的宽打架;以节点属性为准
|
||||||
|
render(node)
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 段落的首行缩进、左边距。老题面里有 `text-indent: 28pt`、`padding-left: 40px` 这种 */
|
/** 段落的首行缩进、左边距。老题面里有 `text-indent: 28pt`、`padding-left: 40px` 这种 */
|
||||||
|
|||||||
Reference in new issue
Block a user