import {Dialog} from "../dialog"; import {confirmDialog} from "../dialog/confirmDialog"; import {fetchPost} from "./fetch"; import {Constants} from "../constants"; import {escapeHtml} from "./escape"; import {isMobile} from "./functions"; 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"; let historyEditor: Protyle; const renderDoc = (element: HTMLElement, currentPage: number) => { 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 inputElement = element.querySelector(".b3-text-field") as HTMLInputElement; const opElement = element.querySelector('.b3-select[data-type="opselect"]') as HTMLSelectElement; const typeElement = element.querySelector('.b3-select[data-type="typeselect"]') as HTMLSelectElement; const notebookElement = element.querySelector('.b3-select[data-type="notebookselect"]') as HTMLSelectElement; localStorage.setItem(Constants.LOCAL_HISTORYNOTEID, notebookElement.value); 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"); if (typeElement.value === "0" || typeElement.value === "1") { opElement.removeAttribute("disabled"); notebookElement.removeAttribute("disabled"); assetElement.classList.add("fn__none"); } else { opElement.setAttribute("disabled", "disabled"); notebookElement.setAttribute("disabled", "disabled"); assetElement.classList.remove("fn__none"); } fetchPost("/api/history/searchHistory", { notebook: notebookElement.value, query: inputElement.value, page: currentPage, op: opElement.value, type: parseInt(typeElement.value) }, (response) => { if (currentPage < response.data.pageCount) { nextElement.removeAttribute("disabled"); } else { nextElement.setAttribute("disabled", "disabled"); } 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; }); }; const renderRepoItem = (response: IWebSocketData, element: Element, type: string) => { if (response.data.snapshots.length === 0) { element.lastElementChild.innerHTML = `
  • ${window.siyuan.languages.emptyContent}
  • `; return; } let actionHTML = ""; if (type === "cloudTag") { actionHTML = ` `; } else if (type === "localTag") { actionHTML = ` `; } else if (type === "local") { actionHTML = ` `; } let repoHTML = ""; response.data.snapshots.forEach((item: { memo: string, id: string, hCreated: string, count: number, hSize: string, tag: string }) => { if (isMobile()) { repoHTML += `
  • ${escapeHtml(item.memo)} ${item.tag}
    ${item.hCreated} ${window.siyuan.languages.fileSize} ${item.hSize} ${window.siyuan.languages.fileCount} ${item.count}
    ${actionHTML}
  • `; } else { repoHTML += `
  • ${escapeHtml(item.memo)} ${item.tag}
    ${item.hCreated} ${window.siyuan.languages.fileSize} ${item.hSize} ${window.siyuan.languages.fileCount} ${item.count}
    ${actionHTML}
  • `; } }); element.lastElementChild.innerHTML = `${repoHTML}`; }; const renderRepo = (element: Element, currentPage: number) => { element.lastElementChild.innerHTML = '
  • '; const previousElement = element.querySelector('[data-type="previous"]'); const nextElement = element.querySelector('[data-type="next"]'); if (currentPage < 0) { if (currentPage === -1) { fetchPost("/api/repo/getRepoTagSnapshots", {}, (response) => { renderRepoItem(response, element, "localTag"); }); } if (currentPage === -2) { fetchPost("/api/repo/getCloudRepoTagSnapshots", {}, (response) => { renderRepoItem(response, element, "cloudTag"); }); } previousElement.classList.add("fn__none"); nextElement.classList.add("fn__none"); } else { previousElement.classList.remove("fn__none"); nextElement.classList.remove("fn__none"); element.setAttribute("data-init", "true"); element.setAttribute("data-page", currentPage.toString()); if (currentPage > 1) { previousElement.removeAttribute("disabled"); } else { previousElement.setAttribute("disabled", "disabled"); } fetchPost("/api/repo/getRepoSnapshots", {page: currentPage}, (response) => { if (currentPage < response.data.pageCount) { nextElement.removeAttribute("disabled"); } else { nextElement.setAttribute("disabled", "disabled"); } renderRepoItem(response, element, "local"); }); } }; const renderRmNotebook = (element: HTMLElement) => { element.setAttribute("data-init", "true"); fetchPost("/api/history/getNotebookHistory", {}, (response) => { if (response.data.histories.length === 0) { element.innerHTML = `
  • ${window.siyuan.languages.emptyContent}
  • `; return; } let logsHTML = ""; response.data.histories.forEach((item: { items: { path: string, title: string }[], hCreated: string }, index: number) => { logsHTML += `
  • 0 ? "" : " fn__hidden"}"> ${item.hCreated}
  • `; if (item.items.length > 0) { logsHTML += `"; } }); element.innerHTML = logsHTML; }); }; export const openHistory = () => { const exitDialog = window.siyuan.dialogs.find((item) => { if (item.element.querySelector("#historyContainer")) { item.destroy(); return true; } }); if (exitDialog) { return; } const currentNotebookId = localStorage.getItem(Constants.LOCAL_HISTORYNOTEID); let notebookSelectHTML = ""; window.siyuan.notebooks.forEach((item) => { if (!item.closed) { notebookSelectHTML += ` `; } }); const dialog = new Dialog({ content: `
    ${window.siyuan.languages.fileHistory}
    ${window.siyuan.languages.removedNotebook}
    ${window.siyuan.languages.dataSnapshot}
    • ${window.siyuan.languages.emptyContent}
    • ${window.siyuan.languages.emptyContent}
    `, width: "80vw", height: "80vh", }); const firstPanelElement = dialog.element.querySelector("#historyContainer [data-type=doc]") as HTMLElement; firstPanelElement.querySelectorAll(".b3-select").forEach((itemElement) => { itemElement.addEventListener("change", () => { renderDoc(firstPanelElement, 1); }); }); firstPanelElement.querySelector(".b3-text-field").addEventListener("input", (event: KeyboardEvent) => { if (event.isComposing) { return; } renderDoc(firstPanelElement, 1); }); firstPanelElement.querySelector(".b3-text-field").addEventListener("compositionend", () => { renderDoc(firstPanelElement, 1); }); const docElement = firstPanelElement.querySelector('.history__text[data-type="docPanel"]') as HTMLElement; const assetElement = firstPanelElement.querySelector('.history__text[data-type="assetPanel"]'); const mdElement = firstPanelElement.querySelector('.history__text[data-type="mdPanel"]') as HTMLTextAreaElement; renderDoc(firstPanelElement, 1); historyEditor = new Protyle(docElement, { blockId: "", action: [Constants.CB_GET_HISTORY], render: { background: false, title: false, gutter: false, breadcrumb: false, breadcrumbDocName: false, breadcrumbContext: false, }, typewriterMode: false, after(editor) { disabledProtyle(editor.protyle); } }); const repoElement = dialog.element.querySelector('#historyContainer [data-type="repo"]'); const selectElement = repoElement.querySelector(".b3-select") as HTMLSelectElement; selectElement.addEventListener("change", () => { const value = selectElement.value; if (value === "0") { renderRepo(repoElement, 1); } else if (value === "1") { renderRepo(repoElement, -1); } else if (value === "2") { renderRepo(repoElement, -2); } }); dialog.element.addEventListener("click", (event) => { let target = event.target as HTMLElement; while (target && !target.isEqualNode(dialog.element)) { const type = target.getAttribute("data-type"); if (target.classList.contains("item")) { target.parentElement.querySelector(".item--focus").classList.remove("item--focus"); Array.from(dialog.element.querySelector("#historyContainer").children).forEach((item: HTMLElement) => { if (item.getAttribute("data-type") === type) { item.classList.remove("fn__none"); item.classList.add("fn__block"); target.classList.add("item--focus"); if (item.getAttribute("data-init") !== "true") { if (type === "notebook") { renderRmNotebook(item); } else if (type === "repo") { renderRepo(item, 1); } } } else { item.classList.add("fn__none"); item.classList.remove("fn__block"); } }); break; } else 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") }); } else if (dataType === "notebook") { fetchPost("/api/history/rollbackNotebookHistory", { historyPath: target.parentElement.getAttribute("data-path") }); } else { fetchPost("/api/repo/checkoutRepo", { id: target.parentElement.getAttribute("data-id") }); } }); break; } else if (type === "toggle") { const iconElement = target.firstElementChild.firstElementChild; if (iconElement.classList.contains("b3-list-item__arrow--open")) { target.nextElementSibling.classList.add("fn__none"); iconElement.classList.remove("b3-list-item__arrow--open"); } else { if (target.nextElementSibling && target.nextElementSibling.tagName === "UL") { target.nextElementSibling.classList.remove("fn__none"); iconElement.classList.add("b3-list-item__arrow--open"); } else { const inputElement = firstPanelElement.querySelector(".b3-text-field") as HTMLInputElement; const opElement = firstPanelElement.querySelector('.b3-select[data-type="opselect"]') as HTMLSelectElement; const typeElement = firstPanelElement.querySelector('.b3-select[data-type="typeselect"]') as HTMLSelectElement; const notebookElement = firstPanelElement.querySelector('.b3-select[data-type="notebookselect"]') as HTMLSelectElement; fetchPost("/api/history/getHistoryItems", { notebook: notebookElement.value, query: inputElement.value, op: opElement.value, type: parseInt(typeElement.value), created: target.getAttribute("data-created") }, (response) => { iconElement.classList.add("b3-list-item__arrow--open"); let html = ""; response.data.items.forEach((docItem: { title: string, path: string }) => { html += `
  • ${escapeHtml(docItem.title)}
  • `; }); target.insertAdjacentHTML("afterend", ``); }); } } break; } else if (type === "rmtoggle") { target.nextElementSibling.classList.toggle("fn__none"); target.firstElementChild.firstElementChild.classList.toggle("b3-list-item__arrow--open"); break; } else if (target.classList.contains("b3-list-item") && (type === "assets" || type === "doc")) { 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(response, historyEditor.protyle, [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"); break; } else if (type === "genRepo") { const genRepoDialog = new Dialog({ title: window.siyuan.languages.snapshotMemo, content: `
    `, width: isMobile() ? "80vw" : "520px", }); const textareaElement = genRepoDialog.element.querySelector("textarea"); textareaElement.focus(); const btnsElement = genRepoDialog.element.querySelectorAll(".b3-button"); genRepoDialog.bindInput(textareaElement, () => { (btnsElement[1] as HTMLButtonElement).click(); }); btnsElement[0].addEventListener("click", () => { genRepoDialog.destroy(); }); btnsElement[1].addEventListener("click", () => { fetchPost("/api/repo/createSnapshot", {memo: textareaElement.value}, () => { renderRepo(repoElement, 1); }); genRepoDialog.destroy(); }); break; } else if (type === "removeRepoTagSnapshot" || type === "removeCloudRepoTagSnapshot") { const tag = target.parentElement.getAttribute("data-tag"); confirmDialog(window.siyuan.languages.deleteOpConfirm, `${window.siyuan.languages.confirmDelete} ${tag}?`, () => { fetchPost("/api/repo/" + type, {tag}, () => { renderRepo(repoElement, type === "removeRepoTagSnapshot" ? -1 : -2); }); }); break; } else if (type === "uploadSnapshot") { fetchPost("/api/repo/uploadCloudSnapshot", { tag: target.parentElement.getAttribute("data-tag"), id: target.parentElement.getAttribute("data-id") }); break; } else if (type === "downloadSnapshot") { fetchPost("/api/repo/downloadCloudSnapshot", { tag: target.parentElement.getAttribute("data-tag"), id: target.parentElement.getAttribute("data-id") }); break; } else if (type === "genTag") { const genTagDialog = new Dialog({ title: window.siyuan.languages.tagSnapshot, content: `
    `, width: isMobile() ? "80vw" : "520px", }); const inputElement = genTagDialog.element.querySelector(".b3-text-field") as HTMLInputElement; inputElement.select(); const btnsElement = genTagDialog.element.querySelectorAll(".b3-button"); btnsElement[0].addEventListener("click", () => { genTagDialog.destroy(); }); btnsElement[2].addEventListener("click", () => { fetchPost("/api/repo/tagSnapshot", { id: target.parentElement.getAttribute("data-id"), name: inputElement.value }, () => { fetchPost("/api/repo/uploadCloudSnapshot", { tag: inputElement.value, id: target.parentElement.getAttribute("data-id") }, () => { renderRepo(repoElement, 1); }); }); genTagDialog.destroy(); }); btnsElement[1].addEventListener("click", () => { fetchPost("/api/repo/tagSnapshot", { id: target.parentElement.getAttribute("data-id"), name: inputElement.value }, () => { renderRepo(repoElement, 1); }); genTagDialog.destroy(); }); break; } else if ((type === "previous" || type === "next") && target.getAttribute("disabled") !== "disabled") { const currentPage = parseInt(repoElement.getAttribute("data-page")); renderRepo(repoElement, type === "previous" ? currentPage - 1 : currentPage + 1); break; } else if ((type === "docprevious" || type === "docnext") && target.getAttribute("disabled") !== "disabled") { const currentPage = parseInt(firstPanelElement.getAttribute("data-page")); renderDoc(firstPanelElement, type === "docprevious" ? currentPage - 1 : currentPage + 1); break; } else if (type === "rebuildIndex") { fetchPost("/api/history/reindexHistory", {}, () => { renderDoc(firstPanelElement, 1); }); break; } target = target.parentElement; } }); };