jvs-ppt-ui 0.0.16 → 0.0.18
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/lib/jvs-ppt-ui.es.js +24043 -21814
- package/dist/lib/jvs-ppt-ui.umd.js +100 -100
- package/dist/lib/style.css +1 -1
- package/package.json +1 -1
- package/src/components/CanvasArea.vue +9739 -9566
- package/src/components/SidebarRight.vue +10 -6
- package/src/components/TemplateGallery.vue +437 -9
- package/src/components/canvas/elements/CanvasGroupElement.vue +214 -213
- package/src/components/canvas/elements/CanvasShapeElement.vue +894 -882
- package/src/components/material/CreateMaterialPanel.vue +2229 -0
- package/src/components/material/MyAssetsPanel.vue +1548 -0
- package/src/components/material/useDamAuth.ts +239 -0
- package/src/components/sidebar/panels/SidebarTextFormatPanel.vue +1 -1
- package/src/composables/canvas/useCanvasText.ts +44 -18
- package/src/composables/sidebar/useSidebarTextFormat.ts +72 -1
- package/src/composables/useSlides.ts +9804 -9627
- package/src/utils/canvas/elementStyles.ts +256 -0
|
@@ -964,3 +964,259 @@ export function updateTextAlignInHtml(html: string, align: string): string {
|
|
|
964
964
|
return `style="${newStyle}"`
|
|
965
965
|
})
|
|
966
966
|
}
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Resolve the effective font size (in px) for a text/shape element:
|
|
971
|
+
* - When the cursor/selection is active inside the element's editable container:
|
|
972
|
+
* - For a collapsed cursor (caret), returns the font size of the character immediately BEFORE the cursor
|
|
973
|
+
* on the current line/block (falling back to the character after the cursor if at the very start of the line).
|
|
974
|
+
* - For a non-collapsed range selection, returns the font size of the first selected character.
|
|
975
|
+
* - When there is no cursor in the element, returns the font size of the first line (first non-empty character).
|
|
976
|
+
*/
|
|
977
|
+
export function resolveEffectiveElementFontSize(
|
|
978
|
+
el: any,
|
|
979
|
+
options?: {
|
|
980
|
+
isEditing?: boolean
|
|
981
|
+
savedRange?: Range | null
|
|
982
|
+
fallback?: string
|
|
983
|
+
}
|
|
984
|
+
): string {
|
|
985
|
+
const fallback = options?.fallback || '18px'
|
|
986
|
+
if (!el) return fallback
|
|
987
|
+
|
|
988
|
+
const parseSizeToPx = (raw: any): string | null => {
|
|
989
|
+
if (raw === undefined || raw === null || raw === '' || raw === 'auto') return null
|
|
990
|
+
const match = String(raw).trim().match(/(\d+(?:\.\d+)?)\s*(px|pt)?/i)
|
|
991
|
+
if (match && match[1]) {
|
|
992
|
+
let size = parseFloat(match[1])
|
|
993
|
+
const unit = (match[2] || 'px').toLowerCase()
|
|
994
|
+
if (unit === 'pt') {
|
|
995
|
+
size = size * 1.33333
|
|
996
|
+
}
|
|
997
|
+
if (!isNaN(size) && size > 0) {
|
|
998
|
+
return `${Math.round(size)}px`
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
return null
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
const resolveFontSizeFromDomNode = (targetNode: Node | null, container: HTMLElement): string | null => {
|
|
1005
|
+
if (!targetNode) return null
|
|
1006
|
+
let cur: HTMLElement | null = targetNode.nodeType === Node.ELEMENT_NODE
|
|
1007
|
+
? (targetNode as HTMLElement)
|
|
1008
|
+
: targetNode.parentElement
|
|
1009
|
+
|
|
1010
|
+
// 1. Walk up from targetNode to container (exclusive) checking inline font-size
|
|
1011
|
+
while (cur && cur !== container && container.contains(cur)) {
|
|
1012
|
+
const styleAttr = cur.getAttribute('style') || ''
|
|
1013
|
+
const match = styleAttr.match(/font-size\s*:\s*(\d+(?:\.\d+)?)\s*(px|pt)?/i)
|
|
1014
|
+
if (match && match[1]) {
|
|
1015
|
+
const parsed = parseSizeToPx(`${match[1]}${match[2] || 'px'}`)
|
|
1016
|
+
if (parsed) return parsed
|
|
1017
|
+
}
|
|
1018
|
+
if (cur.style && cur.style.fontSize) {
|
|
1019
|
+
const parsed = parseSizeToPx(cur.style.fontSize)
|
|
1020
|
+
if (parsed) return parsed
|
|
1021
|
+
}
|
|
1022
|
+
cur = cur.parentElement
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// 2. Fallback to element's style.fontSize or container's inline font-size
|
|
1026
|
+
if (el.style && el.style.fontSize && el.style.fontSize !== 'auto') {
|
|
1027
|
+
const parsed = parseSizeToPx(el.style.fontSize)
|
|
1028
|
+
if (parsed) return parsed
|
|
1029
|
+
}
|
|
1030
|
+
if (el.fontSize && el.fontSize !== 'auto') {
|
|
1031
|
+
const parsed = parseSizeToPx(el.fontSize)
|
|
1032
|
+
if (parsed) return parsed
|
|
1033
|
+
}
|
|
1034
|
+
const containerStyleAttr = container.getAttribute('style') || ''
|
|
1035
|
+
const containerMatch = containerStyleAttr.match(/font-size\s*:\s*(\d+(?:\.\d+)?)\s*(px|pt)?/i)
|
|
1036
|
+
if (containerMatch && containerMatch[1]) {
|
|
1037
|
+
const parsed = parseSizeToPx(`${containerMatch[1]}${containerMatch[2] || 'px'}`)
|
|
1038
|
+
if (parsed) return parsed
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// 3. Fallback to computed style on the element
|
|
1042
|
+
const elem = targetNode.nodeType === Node.ELEMENT_NODE
|
|
1043
|
+
? (targetNode as HTMLElement)
|
|
1044
|
+
: targetNode.parentElement
|
|
1045
|
+
if (elem && typeof window !== 'undefined') {
|
|
1046
|
+
const computedFs = window.getComputedStyle(elem).fontSize
|
|
1047
|
+
const parsed = parseSizeToPx(computedFs)
|
|
1048
|
+
if (parsed) return parsed
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
return null
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
const container = typeof document !== 'undefined' && el.id
|
|
1055
|
+
? (document.getElementById(`editable-${el.id}`) || document.getElementById(`editable-element-${el.id}`) || document.getElementById(`screen-editable-${el.id}`))
|
|
1056
|
+
: null
|
|
1057
|
+
|
|
1058
|
+
const isEditing = options?.isEditing !== undefined
|
|
1059
|
+
? options.isEditing
|
|
1060
|
+
: Boolean(container && (container.getAttribute('contenteditable') === 'true' || container === document.activeElement || container.contains(document.activeElement)))
|
|
1061
|
+
|
|
1062
|
+
// 1. Cursor / selection is active inside the element
|
|
1063
|
+
if (container && isEditing && typeof window !== 'undefined') {
|
|
1064
|
+
const selectedTd = container.querySelector('td.selected-cell') as HTMLElement | null
|
|
1065
|
+
if (selectedTd) {
|
|
1066
|
+
const tdWalker = document.createTreeWalker(selectedTd, NodeFilter.SHOW_TEXT, null)
|
|
1067
|
+
let firstTdText: Text | null = null
|
|
1068
|
+
let tdNode: Node | null = tdWalker.nextNode()
|
|
1069
|
+
while (tdNode) {
|
|
1070
|
+
if ((tdNode.nodeValue || '').replace(/[\u200B\r\n\s]/g, '').length > 0) {
|
|
1071
|
+
firstTdText = tdNode as Text
|
|
1072
|
+
break
|
|
1073
|
+
}
|
|
1074
|
+
tdNode = tdWalker.nextNode()
|
|
1075
|
+
}
|
|
1076
|
+
const tdFs = resolveFontSizeFromDomNode(firstTdText || selectedTd, container)
|
|
1077
|
+
if (tdFs) return tdFs
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
const sel = window.getSelection()
|
|
1081
|
+
let activeRange: Range | null = null
|
|
1082
|
+
if (sel && sel.rangeCount > 0) {
|
|
1083
|
+
const r = sel.getRangeAt(0)
|
|
1084
|
+
if (container.contains(r.commonAncestorContainer)) {
|
|
1085
|
+
activeRange = r
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
if (!activeRange && options?.savedRange && container.contains(options.savedRange.commonAncestorContainer)) {
|
|
1089
|
+
activeRange = options.savedRange
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
if (activeRange) {
|
|
1093
|
+
if (!activeRange.collapsed) {
|
|
1094
|
+
const startRange = activeRange.cloneRange()
|
|
1095
|
+
startRange.collapse(true)
|
|
1096
|
+
const endRange = activeRange.cloneRange()
|
|
1097
|
+
endRange.collapse(false)
|
|
1098
|
+
|
|
1099
|
+
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null)
|
|
1100
|
+
let firstSelectedText: Text | null = null
|
|
1101
|
+
let node: Node | null = walker.nextNode()
|
|
1102
|
+
while (node) {
|
|
1103
|
+
const rawVal = node.nodeValue || ''
|
|
1104
|
+
if (rawVal.replace(/[\u200B\r\n]/g, '').length > 0) {
|
|
1105
|
+
try {
|
|
1106
|
+
if (startRange.comparePoint(node, rawVal.length) > 0 && endRange.comparePoint(node, 0) < 0) {
|
|
1107
|
+
firstSelectedText = node as Text
|
|
1108
|
+
break
|
|
1109
|
+
}
|
|
1110
|
+
} catch (_) {}
|
|
1111
|
+
}
|
|
1112
|
+
node = walker.nextNode()
|
|
1113
|
+
}
|
|
1114
|
+
const targetNode = firstSelectedText || activeRange.startContainer
|
|
1115
|
+
const fs = resolveFontSizeFromDomNode(targetNode, container)
|
|
1116
|
+
if (fs) return fs
|
|
1117
|
+
} else {
|
|
1118
|
+
let blockRoot: HTMLElement = container
|
|
1119
|
+
let temp: Node | null = activeRange.startContainer
|
|
1120
|
+
while (temp && temp !== container) {
|
|
1121
|
+
if (temp.nodeType === Node.ELEMENT_NODE && /^(P|DIV|LI|H[1-6]|BLOCKQUOTE|TD|TH)$/i.test((temp as HTMLElement).tagName)) {
|
|
1122
|
+
blockRoot = temp as HTMLElement
|
|
1123
|
+
break
|
|
1124
|
+
}
|
|
1125
|
+
temp = temp.parentNode
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
let prevTextNode: Text | null = null
|
|
1129
|
+
let nextTextNode: Text | null = null
|
|
1130
|
+
const walker = document.createTreeWalker(blockRoot, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, null)
|
|
1131
|
+
let node: Node | null = walker.nextNode()
|
|
1132
|
+
while (node) {
|
|
1133
|
+
if (node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).tagName === 'BR') {
|
|
1134
|
+
try {
|
|
1135
|
+
if (activeRange.comparePoint(node, 0) < 0) {
|
|
1136
|
+
prevTextNode = null
|
|
1137
|
+
} else if (nextTextNode) {
|
|
1138
|
+
break
|
|
1139
|
+
}
|
|
1140
|
+
} catch (_) {}
|
|
1141
|
+
} else if (node.nodeType === Node.TEXT_NODE) {
|
|
1142
|
+
const rawVal = node.nodeValue || ''
|
|
1143
|
+
const firstCharIdx = rawVal.search(/[^\u200B\r\n]/)
|
|
1144
|
+
if (firstCharIdx >= 0) {
|
|
1145
|
+
try {
|
|
1146
|
+
// 若该文本节点首个可见字符结束位置 (firstCharIdx + 1) 在光标位置之前或恰好重合,说明光标前存在该节点的可见字符
|
|
1147
|
+
const cmp = activeRange.comparePoint(node, firstCharIdx + 1)
|
|
1148
|
+
if (cmp <= 0) {
|
|
1149
|
+
prevTextNode = node as Text
|
|
1150
|
+
} else if (!nextTextNode) {
|
|
1151
|
+
nextTextNode = node as Text
|
|
1152
|
+
}
|
|
1153
|
+
} catch (_) {}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
node = walker.nextNode()
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
const targetNode = prevTextNode || nextTextNode || activeRange.startContainer
|
|
1160
|
+
const fs = resolveFontSizeFromDomNode(targetNode, container)
|
|
1161
|
+
if (fs) return fs
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// 2. No active cursor: use the font size of the first line (first non-empty text character)
|
|
1167
|
+
if (container) {
|
|
1168
|
+
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null)
|
|
1169
|
+
let firstTextNode: Text | null = null
|
|
1170
|
+
let node: Node | null = walker.nextNode()
|
|
1171
|
+
while (node) {
|
|
1172
|
+
if ((node.nodeValue || '').replace(/[\u200B\r\n\s]/g, '').length > 0) {
|
|
1173
|
+
firstTextNode = node as Text
|
|
1174
|
+
break
|
|
1175
|
+
}
|
|
1176
|
+
node = walker.nextNode()
|
|
1177
|
+
}
|
|
1178
|
+
if (firstTextNode) {
|
|
1179
|
+
const fs = resolveFontSizeFromDomNode(firstTextNode, container)
|
|
1180
|
+
if (fs) return fs
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// 3. Fallback when DOM container is not mounted or has no text nodes
|
|
1185
|
+
const htmlContent = el.content || (typeof el.text === 'string' ? el.text : el.text?.content) || ''
|
|
1186
|
+
if (htmlContent) {
|
|
1187
|
+
if (typeof document !== 'undefined' && htmlContent.includes('<')) {
|
|
1188
|
+
const tempDiv = document.createElement('div')
|
|
1189
|
+
tempDiv.innerHTML = htmlContent
|
|
1190
|
+
const walker = document.createTreeWalker(tempDiv, NodeFilter.SHOW_TEXT, null)
|
|
1191
|
+
let firstTextNode: Text | null = null
|
|
1192
|
+
let node: Node | null = walker.nextNode()
|
|
1193
|
+
while (node) {
|
|
1194
|
+
if ((node.nodeValue || '').replace(/[\u200B\r\n\s]/g, '').length > 0) {
|
|
1195
|
+
firstTextNode = node as Text
|
|
1196
|
+
break
|
|
1197
|
+
}
|
|
1198
|
+
node = walker.nextNode()
|
|
1199
|
+
}
|
|
1200
|
+
if (firstTextNode) {
|
|
1201
|
+
const fs = resolveFontSizeFromDomNode(firstTextNode, tempDiv)
|
|
1202
|
+
if (fs) return fs
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
const match = htmlContent.match(/font-size\s*:\s*(\d+(?:\.\d+)?)\s*(px|pt)?/i)
|
|
1206
|
+
if (match && match[1]) {
|
|
1207
|
+
const parsed = parseSizeToPx(`${match[1]}${match[2] || 'px'}`)
|
|
1208
|
+
if (parsed) return parsed
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
if (el.style && el.style.fontSize && el.style.fontSize !== 'auto') {
|
|
1213
|
+
const parsed = parseSizeToPx(el.style.fontSize)
|
|
1214
|
+
if (parsed) return parsed
|
|
1215
|
+
}
|
|
1216
|
+
if (el.fontSize && el.fontSize !== 'auto') {
|
|
1217
|
+
const parsed = parseSizeToPx(el.fontSize)
|
|
1218
|
+
if (parsed) return parsed
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
return fallback
|
|
1222
|
+
}
|