批量选择
This commit is contained in:
@@ -1345,6 +1345,11 @@ export default {
|
|||||||
hasChange: false,
|
hasChange: false,
|
||||||
hasInit: false,
|
hasInit: false,
|
||||||
selectedIds: [],
|
selectedIds: [],
|
||||||
|
isMouseSelecting: false,
|
||||||
|
_selectionSyncToCheckboxesTimer: null,
|
||||||
|
_onDocumentSelectionChange: null,
|
||||||
|
_onDocumentMouseUp: null,
|
||||||
|
_onManuscriptMouseDown: null,
|
||||||
|
|
||||||
displayList: [],
|
displayList: [],
|
||||||
currentTypeText: '',
|
currentTypeText: '',
|
||||||
@@ -1505,51 +1510,14 @@ export default {
|
|||||||
});
|
});
|
||||||
this.$refs.scrollDiv.addEventListener('scroll', this.divOnScroll, { passive: true });
|
this.$refs.scrollDiv.addEventListener('scroll', this.divOnScroll, { passive: true });
|
||||||
|
|
||||||
document.addEventListener('selectionchange', () => {
|
this._onDocumentSelectionChange = this.handleDocumentSelectionChange.bind(this);
|
||||||
if(this.isPreview)return;
|
this._onDocumentMouseUp = this.handleDocumentMouseUp.bind(this);
|
||||||
const selection = window.getSelection();
|
this._onManuscriptMouseDown = this.handleManuscriptMouseDown.bind(this);
|
||||||
if (selection.rangeCount === 0) return;
|
document.addEventListener('selectionchange', this._onDocumentSelectionChange);
|
||||||
|
document.addEventListener('mouseup', this._onDocumentMouseUp);
|
||||||
const range = selection.getRangeAt(0);
|
if (this.$refs.scroll) {
|
||||||
// 依然保留 trim() 后的文本判断,用来决定是否显示气泡
|
this.$refs.scroll.addEventListener('mousedown', this._onManuscriptMouseDown);
|
||||||
const plainText = selection.toString().trim();
|
}
|
||||||
|
|
||||||
if (plainText !== '' && selection.rangeCount > 0) {
|
|
||||||
// --- 1. 获取包含标签的 HTML 内容 ---
|
|
||||||
const fragment = range.cloneContents();
|
|
||||||
const tempDiv = document.createElement('div');
|
|
||||||
tempDiv.appendChild(fragment);
|
|
||||||
// 关键点:这个 label 变量现在包含了完整的 HTML 结构(如 <myfigure>)
|
|
||||||
const htmlLabel = tempDiv.innerHTML;
|
|
||||||
const allPMainElements = this.getInvolvedPMain(range);
|
|
||||||
const allIds = [...new Set(allPMainElements.map((el) => el.getAttribute('data-id')))];
|
|
||||||
if (allIds.length > 0) {
|
|
||||||
this.updateBubblePosition(range);
|
|
||||||
const rootItem = this.wordList.find((item) => item.am_id == allIds[0]);
|
|
||||||
|
|
||||||
this.currentSelection = {
|
|
||||||
// 将 label 设置为包含标签的 HTML 字符串
|
|
||||||
label: htmlLabel,
|
|
||||||
mainId: allIds[0],
|
|
||||||
index: this.wordList.indexOf(rootItem),
|
|
||||||
|
|
||||||
content: rootItem ? rootItem.content : ''
|
|
||||||
};
|
|
||||||
|
|
||||||
this.currentId = allIds[0];
|
|
||||||
this.currentData = rootItem;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.currentTag = '';
|
|
||||||
this.currentTagData = {};
|
|
||||||
this.currentSelection = {
|
|
||||||
label: '',
|
|
||||||
mainId: '',
|
|
||||||
index: 0,
|
|
||||||
content: {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
activated() {
|
activated() {
|
||||||
// 主动触发 MathJax 渲染
|
// 主动触发 MathJax 渲染
|
||||||
@@ -1580,6 +1548,16 @@ export default {
|
|||||||
if (this.resizeObs) this.resizeObs.disconnect();
|
if (this.resizeObs) this.resizeObs.disconnect();
|
||||||
if (this.mutObs) this.mutObs.disconnect();
|
if (this.mutObs) this.mutObs.disconnect();
|
||||||
if (this._syncRefOrderTimer) clearTimeout(this._syncRefOrderTimer);
|
if (this._syncRefOrderTimer) clearTimeout(this._syncRefOrderTimer);
|
||||||
|
if (this._selectionSyncToCheckboxesTimer) clearTimeout(this._selectionSyncToCheckboxesTimer);
|
||||||
|
if (this._onDocumentSelectionChange) {
|
||||||
|
document.removeEventListener('selectionchange', this._onDocumentSelectionChange);
|
||||||
|
}
|
||||||
|
if (this._onDocumentMouseUp) {
|
||||||
|
document.removeEventListener('mouseup', this._onDocumentMouseUp);
|
||||||
|
}
|
||||||
|
if (this._onManuscriptMouseDown && this.$refs.scroll) {
|
||||||
|
this.$refs.scroll.removeEventListener('mousedown', this._onManuscriptMouseDown);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
destroy() {
|
destroy() {
|
||||||
this.destroyTinymce();
|
this.destroyTinymce();
|
||||||
@@ -1702,6 +1680,115 @@ renderCiteLabels(html) {
|
|||||||
|
|
||||||
return rangePs;
|
return rangePs;
|
||||||
},
|
},
|
||||||
|
handleManuscriptMouseDown(event) {
|
||||||
|
if (this.isPreview) return;
|
||||||
|
const root = this.$refs && this.$refs.scroll;
|
||||||
|
if (!root) return;
|
||||||
|
if (root.contains(event.target)) {
|
||||||
|
this.isMouseSelecting = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleDocumentMouseUp() {
|
||||||
|
this.isMouseSelecting = false;
|
||||||
|
},
|
||||||
|
handleDocumentSelectionChange() {
|
||||||
|
if (this.isPreview) return;
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (!selection || selection.rangeCount === 0) return;
|
||||||
|
|
||||||
|
const range = selection.getRangeAt(0);
|
||||||
|
const plainText = selection.toString().trim();
|
||||||
|
|
||||||
|
if (plainText !== '' && selection.rangeCount > 0) {
|
||||||
|
const fragment = range.cloneContents();
|
||||||
|
const tempDiv = document.createElement('div');
|
||||||
|
tempDiv.appendChild(fragment);
|
||||||
|
const htmlLabel = tempDiv.innerHTML;
|
||||||
|
const allPMainElements = this.getInvolvedPMain(range);
|
||||||
|
const allIds = [...new Set(allPMainElements.map((el) => el.getAttribute('data-id')))];
|
||||||
|
if (allIds.length > 0) {
|
||||||
|
this.updateBubblePosition(range);
|
||||||
|
const rootItem = this.wordList.find((item) => item.am_id == allIds[0]);
|
||||||
|
|
||||||
|
this.currentSelection = {
|
||||||
|
label: htmlLabel,
|
||||||
|
mainId: allIds[0],
|
||||||
|
index: this.wordList.indexOf(rootItem),
|
||||||
|
content: rootItem ? rootItem.content : ''
|
||||||
|
};
|
||||||
|
|
||||||
|
this.currentId = allIds[0];
|
||||||
|
this.currentData = rootItem;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.currentTag = '';
|
||||||
|
this.currentTagData = {};
|
||||||
|
this.currentSelection = {
|
||||||
|
label: '',
|
||||||
|
mainId: '',
|
||||||
|
index: 0,
|
||||||
|
content: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.scheduleSyncSelectedIdsFromRange();
|
||||||
|
},
|
||||||
|
scheduleSyncSelectedIdsFromRange() {
|
||||||
|
if (this._selectionSyncToCheckboxesTimer) {
|
||||||
|
clearTimeout(this._selectionSyncToCheckboxesTimer);
|
||||||
|
}
|
||||||
|
this._selectionSyncToCheckboxesTimer = setTimeout(() => {
|
||||||
|
this.syncSelectedIdsFromRangeInternal();
|
||||||
|
}, 80);
|
||||||
|
},
|
||||||
|
syncSelectedIdsFromRangeInternal() {
|
||||||
|
if (this.isPreview || this.isInternalAction) return;
|
||||||
|
const scrollRoot = this.$refs && this.$refs.scroll;
|
||||||
|
if (!scrollRoot) return;
|
||||||
|
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) return;
|
||||||
|
const range = selection.getRangeAt(0);
|
||||||
|
const common = range.commonAncestorContainer;
|
||||||
|
const commonElement = common && common.nodeType === 1 ? common : common && common.parentElement;
|
||||||
|
if (!commonElement || !scrollRoot.contains(commonElement)) return;
|
||||||
|
|
||||||
|
const nodes = Array.from(scrollRoot.querySelectorAll('.drop-target[main-id]'));
|
||||||
|
const touchedMainIds = [];
|
||||||
|
nodes.forEach((node) => {
|
||||||
|
const id = node.getAttribute('main-id');
|
||||||
|
if (!id || id === 'References') return;
|
||||||
|
try {
|
||||||
|
if (range.intersectsNode(node)) {
|
||||||
|
touchedMainIds.push(id);
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
});
|
||||||
|
if (!touchedMainIds.length) return;
|
||||||
|
|
||||||
|
const indexMap = {};
|
||||||
|
this.wordList.forEach((item, idx) => {
|
||||||
|
if (item && item.am_id != null) {
|
||||||
|
indexMap[String(item.am_id)] = idx;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const indices = [...new Set(touchedMainIds.map((id) => indexMap[String(id)]).filter((v) => Number.isInteger(v)))];
|
||||||
|
if (!indices.length) return;
|
||||||
|
const lo = Math.min(...indices);
|
||||||
|
const hi = Math.max(...indices);
|
||||||
|
const rangeIds = this.wordList
|
||||||
|
.slice(lo, hi + 1)
|
||||||
|
.map((item) => (item && item.am_id != null ? item.am_id : null))
|
||||||
|
.filter((id) => id != null);
|
||||||
|
if (!rangeIds.length) return;
|
||||||
|
|
||||||
|
const existingSet = new Set(this.selectedIds || []);
|
||||||
|
rangeIds.forEach((id) => existingSet.add(id));
|
||||||
|
const ordered = this.wordList
|
||||||
|
.map((item) => (item && item.am_id != null ? item.am_id : null))
|
||||||
|
.filter((id) => id != null && existingSet.has(id));
|
||||||
|
this.selectedIds = ordered;
|
||||||
|
this.$forceUpdate();
|
||||||
|
},
|
||||||
|
|
||||||
handleUnbindLink(type) {
|
handleUnbindLink(type) {
|
||||||
const rootItem = this.wordList.find((item) => item.am_id == this.currentTagData.main_id);
|
const rootItem = this.wordList.find((item) => item.am_id == this.currentTagData.main_id);
|
||||||
@@ -3837,10 +3924,18 @@ renderCiteLabels(html) {
|
|||||||
this.currentData = {};
|
this.currentData = {};
|
||||||
this.manuscriptAutociteContext = null;
|
this.manuscriptAutociteContext = null;
|
||||||
|
|
||||||
// 4. 清除浏览器原生的文字选中蓝色区域
|
// 4. 仅当“没有按住鼠标连续拖选”时,才清理浏览器原生选区
|
||||||
// 这样滚动时就不会有一大片蓝色的选区跟着走,视觉上更干净
|
// 按住拖选并滚动时保留选区,让用户可以持续扩展选中范围
|
||||||
const selection = window.getSelection();
|
const selection = window.getSelection();
|
||||||
if (selection) {
|
const hasActiveRange = !!(selection && selection.rangeCount > 0 && !selection.isCollapsed);
|
||||||
|
const range = hasActiveRange ? selection.getRangeAt(0) : null;
|
||||||
|
const common = range ? range.commonAncestorContainer : null;
|
||||||
|
const commonElement = common && common.nodeType === 1 ? common : common && common.parentElement;
|
||||||
|
const inManuscript = !!(commonElement && this.$refs.scroll && this.$refs.scroll.contains(commonElement));
|
||||||
|
const shouldKeepSelection = this.isMouseSelecting && hasActiveRange && inManuscript;
|
||||||
|
if (shouldKeepSelection) {
|
||||||
|
this.scheduleSyncSelectedIdsFromRange();
|
||||||
|
} else if (selection) {
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
}
|
}
|
||||||
// const scrollTop = scrollDiv.scrollTop; // 获取垂直滚动距离
|
// const scrollTop = scrollDiv.scrollTop; // 获取垂直滚动距离
|
||||||
|
|||||||
Reference in New Issue
Block a user