diff --git a/app/src/history/doc.ts b/app/src/history/doc.ts new file mode 100644 index 000000000..4f84a6fa8 --- /dev/null +++ b/app/src/history/doc.ts @@ -0,0 +1,187 @@ +import {Dialog} from "../dialog"; +import {confirmDialog} from "../dialog/confirmDialog"; +import {Constants} from "../constants"; +import {hasClosestByClassName} from "../protyle/util/hasClosest"; +import {renderAssetsPreview} from "../asset/renderAssets"; +import {Protyle} from "../protyle"; +import {disabledProtyle, onGet} from "../protyle/util/onGet"; +import * as dayjs from "dayjs"; +import {fetchPost} from "../util/fetch"; +import {isMobile} from "../util/functions"; +import {App} from "../index"; + +let historyEditor: Protyle; + +const renderDoc = (element: HTMLElement, currentPage: number, id: string) => { + const previousElement = element.querySelector('[data-type="docprevious"]'); + const nextElement = element.querySelector('[data-type="docnext"]'); + element.setAttribute("data-page", currentPage.toString()); + if (currentPage > 1) { + previousElement.removeAttribute("disabled"); + } else { + previousElement.setAttribute("disabled", "disabled"); + } + const opElement = element.querySelector('.b3-select[data-type="opselect"]') as HTMLSelectElement; + const docElement = element.querySelector('.history__text[data-type="docPanel"]'); + const assetElement = element.querySelector('.history__text[data-type="assetPanel"]'); + const mdElement = element.querySelector('.history__text[data-type="mdPanel"]') as HTMLTextAreaElement; + docElement.classList.add("fn__none"); + mdElement.classList.add("fn__none"); + assetElement.classList.remove("fn__none"); + fetchPost("/api/history/searchHistory", { + query: id, + page: currentPage, + op: opElement.value, + type: 4 + }, (response) => { + if (currentPage < response.data.pageCount) { + nextElement.removeAttribute("disabled"); + } else { + nextElement.setAttribute("disabled", "disabled"); + } + nextElement.nextElementSibling.nextElementSibling.textContent = `${currentPage}/${response.data.pageCount || 1}`; + if (response.data.histories.length === 0) { + element.lastElementChild.lastElementChild.previousElementSibling.classList.add("fn__none"); + element.lastElementChild.lastElementChild.classList.add("fn__none"); + element.lastElementChild.firstElementChild.innerHTML = `
  • ${window.siyuan.languages.emptyContent}
  • `; + return; + } + let logsHTML = ""; + response.data.histories.forEach((item: string) => { + logsHTML += `
  • + + ${dayjs(parseInt(item) * 1000).format("YYYY-MM-DD HH:mm:ss")} +
  • `; + }); + element.lastElementChild.firstElementChild.innerHTML = logsHTML; + }); +}; + +export const openDocHistory = (app: App, id: string) => { + const contentHTML = `
    +
    + + + + + 1/1 + +
    + +
    +
    + +
    + +
    +
    +
    `; + const dialog = new Dialog({ + content: contentHTML, + width: isMobile() ? "92vw" : "768px", + height: isMobile() ? "80vh" : "70vh", + destroyCallback() { + historyEditor = undefined; + } + }); + bindEvent(app, dialog.element, id); +}; + +const bindEvent = (app: App, element: HTMLElement, id: string) => { + element.querySelector(".b3-select").addEventListener("change", () => { + renderDoc(element, 1, id); + }); + const docElement = element.querySelector('.history__text[data-type="docPanel"]') as HTMLElement; + const assetElement = element.querySelector('.history__text[data-type="assetPanel"]'); + const mdElement = element.querySelector('.history__text[data-type="mdPanel"]') as HTMLTextAreaElement; + renderDoc(element, 1, id); + historyEditor = new Protyle(app, docElement, { + blockId: "", + action: [Constants.CB_GET_HISTORY], + render: { + background: false, + title: false, + gutter: false, + breadcrumb: false, + breadcrumbDocName: false, + }, + typewriterMode: false, + }); + disabledProtyle(historyEditor.protyle); + element.addEventListener("click", (event) => { + let target = event.target as HTMLElement; + while (target && !target.isEqualNode(element)) { + const type = target.getAttribute("data-type"); + if (target.classList.contains("b3-list-item__action") && type === "rollback" && !window.siyuan.config.readonly) { + confirmDialog("⚠️ " + window.siyuan.languages.rollback, `${window.siyuan.languages.rollbackConfirm.replace("${date}", target.parentElement.textContent.trim())}`, () => { + const dataType = target.parentElement.getAttribute("data-type"); + if (dataType === "assets") { + fetchPost("/api/history/rollbackAssetsHistory", { + historyPath: target.parentElement.getAttribute("data-path") + }); + } else if (dataType === "doc") { + fetchPost("/api/history/rollbackDocHistory", { + // notebook: (firstPanelElement.querySelector('.b3-select[data-type="notebookselect"]') as HTMLSelectElement).value, + historyPath: target.parentElement.getAttribute("data-path") + }); + } + }); + event.stopPropagation(); + event.preventDefault(); + break; + } else if (target.classList.contains("b3-list-item")) { + const dataPath = target.getAttribute("data-path"); + if (type === "assets") { + assetElement.innerHTML = renderAssetsPreview(dataPath); + } else if (type === "doc") { + fetchPost("/api/history/getDocHistoryContent", { + historyPath: dataPath, + // k: (firstPanelElement.querySelector(".b3-text-field") as HTMLInputElement).value + }, (response) => { + if (response.data.isLargeDoc) { + mdElement.value = response.data.content; + mdElement.classList.remove("fn__none"); + docElement.classList.add("fn__none"); + } else { + mdElement.classList.add("fn__none"); + docElement.classList.remove("fn__none"); + onGet({ + data: response, + protyle: historyEditor.protyle, + action: [Constants.CB_GET_HISTORY, Constants.CB_GET_HTML], + }); + } + }); + } + let currentItem = hasClosestByClassName(target, "b3-list") as HTMLElement; + if (currentItem) { + currentItem = currentItem.querySelector(".b3-list-item--focus"); + if (currentItem) { + currentItem.classList.remove("b3-list-item--focus"); + } + } + target.classList.add("b3-list-item--focus"); + event.stopPropagation(); + event.preventDefault(); + break; + } else if ((type === "docprevious" || type === "docnext") && target.getAttribute("disabled") !== "disabled") { + const currentPage = parseInt(element.getAttribute("data-page")); + renderDoc(element, type === "docprevious" ? currentPage - 1 : currentPage + 1, id); + event.stopPropagation(); + event.preventDefault(); + break; + } + target = target.parentElement; + } + }); +}; diff --git a/app/src/menus/navigation.ts b/app/src/menus/navigation.ts index 38910d0c3..fecc182c3 100644 --- a/app/src/menus/navigation.ts +++ b/app/src/menus/navigation.ts @@ -32,6 +32,7 @@ import {openNewWindowById} from "../window/openNewWindow"; import {openCardByData} from "../card/openCard"; import {viewCards} from "../card/viewCards"; import {App} from "../index"; +import {openDocHistory} from "../history/doc"; const initMultiMenu = (selectItemElements: NodeListOf) => { const fileItemElement = Array.from(selectItemElements).find(item => { @@ -540,6 +541,15 @@ export const initFileMenu = (app: App, notebookId: string, pathString: string, l submenu: openSubmenus, }).element); /// #endif + if (!window.siyuan.config.readonly) { + window.siyuan.menus.menu.append(new MenuItem({ + label: window.siyuan.languages.dataHistory, + icon: "iconHistory", + click() { + openDocHistory(app, id); + } + }).element); + } genImportMenu(notebookId, pathString); window.siyuan.menus.menu.append(exportMd(id)); return window.siyuan.menus.menu;