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 += `
${item.hCreated}
`;
if (item.items.length > 0) {
logsHTML += ``;
item.items.forEach((docItem) => {
logsHTML += `-
${escapeHtml(docItem.title)}
${window.siyuan.languages.rollback}
`;
});
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}
- ${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;
}
});
};