mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-12-20 00:20:12 +01:00
This commit is contained in:
parent
3f00813034
commit
4bd2760b2b
4 changed files with 97 additions and 57 deletions
|
|
@ -200,6 +200,7 @@ export const initFileMenu = (notebookId: string, pathString: string, liElement:
|
||||||
icon: "iconCopy",
|
icon: "iconCopy",
|
||||||
submenu: (copySubMenu(id, false) as IMenu[]).concat([{
|
submenu: (copySubMenu(id, false) as IMenu[]).concat([{
|
||||||
label: window.siyuan.languages.duplicate,
|
label: window.siyuan.languages.duplicate,
|
||||||
|
accelerator: window.siyuan.config.keymap.editor.general.duplicate.custom,
|
||||||
click() {
|
click() {
|
||||||
fetchPost("/api/filetree/duplicateDoc", {
|
fetchPost("/api/filetree/duplicateDoc", {
|
||||||
id
|
id
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import {getContenteditableElement, getTopAloneElement, isNotEditBlock} from "../
|
||||||
import * as dayjs from "dayjs";
|
import * as dayjs from "dayjs";
|
||||||
import {fetchPost, fetchSyncPost} from "../../util/fetch";
|
import {fetchPost, fetchSyncPost} from "../../util/fetch";
|
||||||
import {cancelSB, insertEmptyBlock, jumpToParentNext} from "../../block/util";
|
import {cancelSB, insertEmptyBlock, jumpToParentNext} from "../../block/util";
|
||||||
import {scrollCenter} from "../../util/highlightById";
|
|
||||||
import {countBlockWord} from "../../layout/status";
|
import {countBlockWord} from "../../layout/status";
|
||||||
/// #if !MOBILE
|
/// #if !MOBILE
|
||||||
import {openFileById} from "../../editor/util";
|
import {openFileById} from "../../editor/util";
|
||||||
|
|
@ -29,6 +28,7 @@ import {openFileById} from "../../editor/util";
|
||||||
import {Constants} from "../../constants";
|
import {Constants} from "../../constants";
|
||||||
import {openMobileFileById} from "../../mobile/editor";
|
import {openMobileFileById} from "../../mobile/editor";
|
||||||
import {mathRender} from "../markdown/mathRender";
|
import {mathRender} from "../markdown/mathRender";
|
||||||
|
import {duplicateBlock} from "../wysiwyg/commonHotkey";
|
||||||
|
|
||||||
export class Gutter {
|
export class Gutter {
|
||||||
public element: HTMLElement;
|
public element: HTMLElement;
|
||||||
|
|
@ -543,46 +543,55 @@ export class Gutter {
|
||||||
window.siyuan.menus.menu.append(new MenuItem({
|
window.siyuan.menus.menu.append(new MenuItem({
|
||||||
label: window.siyuan.languages.copy,
|
label: window.siyuan.languages.copy,
|
||||||
icon: "iconCopy",
|
icon: "iconCopy",
|
||||||
accelerator: "⌘C",
|
type: "submenu",
|
||||||
click() {
|
submenu: [{
|
||||||
if (isNotEditBlock(selectsElement[0])) {
|
label: window.siyuan.languages.copy,
|
||||||
|
accelerator: "⌘C",
|
||||||
|
click() {
|
||||||
|
if (isNotEditBlock(selectsElement[0])) {
|
||||||
|
let html = "";
|
||||||
|
selectsElement.forEach(item => {
|
||||||
|
html += removeEmbed(item);
|
||||||
|
});
|
||||||
|
writeText(protyle.lute.BlockDOM2StdMd(html).trimEnd());
|
||||||
|
} else {
|
||||||
|
focusByRange(getEditorRange(selectsElement[0]));
|
||||||
|
document.execCommand("copy");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
label: window.siyuan.languages.copyPlainText,
|
||||||
|
accelerator: window.siyuan.config.keymap.editor.general.copyPlainText.custom,
|
||||||
|
click() {
|
||||||
let html = "";
|
let html = "";
|
||||||
selectsElement.forEach(item => {
|
selectsElement.forEach(item => {
|
||||||
html += removeEmbed(item);
|
item.querySelectorAll('[contenteditable="true"]').forEach(editItem => {
|
||||||
});
|
const cloneNode = editItem.cloneNode(true) as HTMLElement;
|
||||||
writeText(protyle.lute.BlockDOM2StdMd(html).trimEnd());
|
cloneNode.querySelectorAll('[data-type="backslash"]').forEach(slashItem => {
|
||||||
} else {
|
slashItem.firstElementChild.remove();
|
||||||
focusByRange(getEditorRange(selectsElement[0]));
|
});
|
||||||
document.execCommand("copy");
|
html += cloneNode.textContent + "\n";
|
||||||
}
|
|
||||||
}
|
|
||||||
}).element);
|
|
||||||
window.siyuan.menus.menu.append(new MenuItem({
|
|
||||||
label: window.siyuan.languages.copyPlainText,
|
|
||||||
accelerator: window.siyuan.config.keymap.editor.general.copyPlainText.custom,
|
|
||||||
click() {
|
|
||||||
let html = "";
|
|
||||||
selectsElement.forEach(item => {
|
|
||||||
item.querySelectorAll('[contenteditable="true"]').forEach(editItem => {
|
|
||||||
const cloneNode = editItem.cloneNode(true) as HTMLElement;
|
|
||||||
cloneNode.querySelectorAll('[data-type="backslash"]').forEach(slashItem => {
|
|
||||||
slashItem.firstElementChild.remove();
|
|
||||||
});
|
});
|
||||||
html += cloneNode.textContent + "\n";
|
|
||||||
});
|
});
|
||||||
});
|
writeText(html.trimEnd());
|
||||||
writeText(html.trimEnd());
|
}
|
||||||
}
|
}, {
|
||||||
}).element);
|
label: window.siyuan.languages.copy + " HTML",
|
||||||
window.siyuan.menus.menu.append(new MenuItem({
|
click() {
|
||||||
label: window.siyuan.languages.copy + " HTML",
|
let html = "";
|
||||||
click() {
|
selectsElement.forEach(item => {
|
||||||
let html = "";
|
html += item.outerHTML;
|
||||||
selectsElement.forEach(item => {
|
});
|
||||||
html += item.outerHTML;
|
writeText(protyle.lute.BlockDOM2HTML(html));
|
||||||
});
|
}
|
||||||
writeText(protyle.lute.BlockDOM2HTML(html));
|
}, {
|
||||||
}
|
label: window.siyuan.languages.duplicate,
|
||||||
|
accelerator: window.siyuan.config.keymap.editor.general.duplicate.custom,
|
||||||
|
disabled: protyle.disabled,
|
||||||
|
click() {
|
||||||
|
duplicateBlock(selectsElement, protyle);
|
||||||
|
}
|
||||||
|
}]
|
||||||
}).element);
|
}).element);
|
||||||
if (protyle.disabled) {
|
if (protyle.disabled) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -948,26 +957,10 @@ export class Gutter {
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
label: window.siyuan.languages.duplicate,
|
label: window.siyuan.languages.duplicate,
|
||||||
|
accelerator: window.siyuan.config.keymap.editor.general.duplicate.custom,
|
||||||
disabled: protyle.disabled,
|
disabled: protyle.disabled,
|
||||||
click() {
|
click() {
|
||||||
const tempElement = nodeElement.cloneNode(true) as HTMLElement;
|
duplicateBlock([nodeElement], protyle);
|
||||||
const newId = Lute.NewNodeID();
|
|
||||||
tempElement.setAttribute("data-node-id", newId);
|
|
||||||
tempElement.querySelectorAll("[data-node-id]").forEach(item => {
|
|
||||||
item.setAttribute("data-node-id", Lute.NewNodeID());
|
|
||||||
});
|
|
||||||
nodeElement.after(tempElement);
|
|
||||||
scrollCenter(protyle);
|
|
||||||
transaction(protyle, [{
|
|
||||||
action: "insert",
|
|
||||||
data: nodeElement.nextElementSibling.outerHTML,
|
|
||||||
id: newId,
|
|
||||||
previousID: id,
|
|
||||||
}], [{
|
|
||||||
action: "delete",
|
|
||||||
id: newId,
|
|
||||||
}]);
|
|
||||||
focusBlock(tempElement);
|
|
||||||
}
|
}
|
||||||
}])
|
}])
|
||||||
}).element);
|
}).element);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import {matchHotKey} from "../util/hotKey";
|
||||||
import {fetchPost} from "../../util/fetch";
|
import {fetchPost} from "../../util/fetch";
|
||||||
import {writeText} from "../util/compatibility";
|
import {writeText} from "../util/compatibility";
|
||||||
import {
|
import {
|
||||||
|
focusBlock,
|
||||||
focusByOffset,
|
focusByOffset,
|
||||||
getSelectionOffset,
|
getSelectionOffset,
|
||||||
setFirstNodeRange,
|
setFirstNodeRange,
|
||||||
|
|
@ -16,6 +17,8 @@ import {getContenteditableElement} from "./getBlock";
|
||||||
import {hasClosestByMatchTag} from "../util/hasClosest";
|
import {hasClosestByMatchTag} from "../util/hasClosest";
|
||||||
import {hideElements} from "../ui/hideElements";
|
import {hideElements} from "../ui/hideElements";
|
||||||
import {countBlockWord} from "../../layout/status";
|
import {countBlockWord} from "../../layout/status";
|
||||||
|
import {scrollCenter} from "../../util/highlightById";
|
||||||
|
import {transaction} from "./transaction";
|
||||||
|
|
||||||
export const commonHotkey = (protyle: IProtyle, event: KeyboardEvent) => {
|
export const commonHotkey = (protyle: IProtyle, event: KeyboardEvent) => {
|
||||||
const target = event.target as HTMLElement;
|
const target = event.target as HTMLElement;
|
||||||
|
|
@ -100,7 +103,7 @@ export const upSelect = (options: {
|
||||||
options.event.preventDefault();
|
options.event.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else{
|
} else {
|
||||||
// 选中上一个节点的处理在 toolbar/index.ts 中 `shift+方向键或三击选中`
|
// 选中上一个节点的处理在 toolbar/index.ts 中 `shift+方向键或三击选中`
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -180,3 +183,35 @@ export const getStartEndElement = (selectElements: NodeListOf<Element> | Element
|
||||||
endElement
|
endElement
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const duplicateBlock = (nodeElements: Element[], protyle: IProtyle) => {
|
||||||
|
let focusElement
|
||||||
|
const doOperations: IOperation[] = []
|
||||||
|
const undoOperations: IOperation[] = []
|
||||||
|
nodeElements.forEach((item, index) => {
|
||||||
|
const tempElement = item.cloneNode(true) as HTMLElement;
|
||||||
|
if (index === nodeElements.length - 1) {
|
||||||
|
focusElement = tempElement
|
||||||
|
}
|
||||||
|
const newId = Lute.NewNodeID();
|
||||||
|
tempElement.setAttribute("data-node-id", newId);
|
||||||
|
tempElement.querySelectorAll("[data-node-id]").forEach(childItem => {
|
||||||
|
childItem.setAttribute("data-node-id", Lute.NewNodeID());
|
||||||
|
});
|
||||||
|
item.classList.remove("protyle-wysiwyg--select");
|
||||||
|
item.after(tempElement);
|
||||||
|
doOperations.push({
|
||||||
|
action: "insert",
|
||||||
|
data: tempElement.outerHTML,
|
||||||
|
id: newId,
|
||||||
|
previousID: item.getAttribute("data-node-id"),
|
||||||
|
})
|
||||||
|
undoOperations.push({
|
||||||
|
action: "delete",
|
||||||
|
id: newId,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
transaction(protyle, doOperations, undoOperations);
|
||||||
|
focusBlock(focusElement);
|
||||||
|
scrollCenter(protyle);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ import {isLocalPath} from "../../util/pathName";
|
||||||
/// #if !MOBILE
|
/// #if !MOBILE
|
||||||
import {openBy, openFileById} from "../../editor/util";
|
import {openBy, openFileById} from "../../editor/util";
|
||||||
/// #endif
|
/// #endif
|
||||||
import {commonHotkey, downSelect, getStartEndElement, upSelect} from "./commonHotkey";
|
import {commonHotkey, downSelect, duplicateBlock, getStartEndElement, upSelect} from "./commonHotkey";
|
||||||
import {linkMenu, refMenu, setFold, zoomOut} from "../../menus/protyle";
|
import {linkMenu, refMenu, setFold, zoomOut} from "../../menus/protyle";
|
||||||
import {removeEmbed} from "./removeEmbed";
|
import {removeEmbed} from "./removeEmbed";
|
||||||
import {openAttr} from "../../menus/commonMenuItem";
|
import {openAttr} from "../../menus/commonMenuItem";
|
||||||
|
|
@ -1446,6 +1446,17 @@ export const keydown = (protyle: IProtyle, editorElement: HTMLElement) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (matchHotKey(window.siyuan.config.keymap.editor.general.duplicate.custom, event)) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
let selectsElement: HTMLElement[] = Array.from(protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select"));
|
||||||
|
if (selectsElement.length === 0) {
|
||||||
|
selectsElement = [nodeElement];
|
||||||
|
}
|
||||||
|
duplicateBlock(selectsElement, protyle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// tab 需等待 list 和 table 处理完成
|
// tab 需等待 list 和 table 处理完成
|
||||||
if (event.key === "Tab" && !event.ctrlKey && !isCtrl(event) && !event.altKey) {
|
if (event.key === "Tab" && !event.ctrlKey && !isCtrl(event) && !event.altKey) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue