import {appearance} from "./appearance"; import {showMessage} from "../dialog/message"; import {fetchPost} from "../util/fetch"; import {confirmDialog} from "../dialog/confirmDialog"; import {highlightRender} from "../protyle/markdown/highlightRender"; import {exportLayout} from "../layout/util"; import {Constants} from "../constants"; /// #if !BROWSER import {shell} from "electron"; import * as path from "path"; /// #endif import {getFrontend, isBrowser} from "../util/functions"; import {setStorageVal} from "../protyle/util/compatibility"; import {hasClosestByAttribute, hasClosestByClassName} from "../protyle/util/hasClosest"; import {Plugin} from "../plugin"; import {App} from "../index"; import {escapeAttr} from "../util/escape"; import {uninstall} from "../plugin/uninstall"; import {loadPlugin} from "../plugin/loader"; export const bazaar = { element: undefined as Element, genHTML() { if (!window.siyuan.config.bazaar.trust) { return `
${window.siyuan.languages.bazaarTrust}
${window.siyuan.languages.bazaarTrust3}
${window.siyuan.languages.bazaarTrustCodeReview}
${window.siyuan.languages.bazaarTrustCodeReviewTip}
${window.siyuan.languages.bazaarTrustOpenSource}
${window.siyuan.languages.bazaarTrustOpenSourceTip}
${window.siyuan.languages.bazaarCommunityReview}
${window.siyuan.languages.bazaarPeerReviewTip}
${window.siyuan.languages.bazaarUserReport}
${window.siyuan.languages.bazaarUserReportTip}
${window.siyuan.languages.bazaarTrust1}
${window.siyuan.languages.bazaarTrust2}
`; } const localSort = window.siyuan.storage[Constants.LOCAL_BAZAAR]; const loadingHTML = `
`; return `
${window.siyuan.languages.downloaded}
${window.siyuan.languages.plugin}
${window.siyuan.languages.theme}
${window.siyuan.languages.icon}
${window.siyuan.languages.template}
${window.siyuan.languages.widget}
${loadingHTML}
${loadingHTML}
${loadingHTML}
${loadingHTML}
${loadingHTML}
${loadingHTML}
`; }, _genCardHTML(item: IBazaarItem, bazaarType: TBazaarType) { let hide = false; let themeMode = ""; if (bazaarType === "themes") { const themeValue = (bazaar.element.querySelector("#bazaarSelect") as HTMLSelectElement).value; if ((themeValue === "0" && item.modes.includes("dark")) || themeValue === "1" && item.modes.includes("light")) { hide = true; } themeMode = item.modes.toString(); } let showSwitch = false; if (["icons", "themes"].includes(bazaarType)) { showSwitch = true; } const dataObj = { bazaarType, themeMode: themeMode, updated: item.updated, name: item.name, repoURL: item.repoURL, repoHash: item.repoHash, downloads: item.downloads, downloaded: false, }; return `
${item.preferredName} ${item.name}
${item.preferredDesc || ""}
${item.downloads} ${item.preferredFunding ? `` : ""}
`; }, _genMyHTML(bazaarType: TBazaarType, app: App) { const contentElement = bazaar.element.querySelector("#configBazaarDownloaded"); if (contentElement.getAttribute("data-loading") === "true" || contentElement.previousElementSibling.querySelector(`[data-type="my${bazaarType.replace(bazaarType[0], bazaarType[0].toUpperCase()).substring(0, bazaarType.length - 1)}"]`).classList.contains("b3-button--outline")) { return; } contentElement.setAttribute("data-loading", "true"); let url = "/api/bazaar/getInstalledTheme"; if (bazaarType === "icons") { url = "/api/bazaar/getInstalledIcon"; } else if (bazaarType === "widgets") { url = "/api/bazaar/getInstalledWidget"; } else if (bazaarType === "templates") { url = "/api/bazaar/getInstalledTemplate"; } else if (bazaarType === "plugins") { url = "/api/bazaar/getInstalledPlugin"; } fetchPost(url, { frontend: getFrontend() }, response => { contentElement.removeAttribute("data-loading"); let html = ""; let showSwitch = false; if (["icons", "themes"].includes(bazaarType)) { showSwitch = true; } response.data.packages.forEach((item: IBazaarItem) => { const dataObj = { bazaarType, themeMode: item.modes?.toString(), updated: item.updated, name: item.name, repoURL: item.repoURL, repoHash: item.repoHash, downloaded: true }; let hasSetting = false; if (bazaarType === "plugins") { app.plugins.find((item: Plugin) => { if (item.name === dataObj.name) { // @ts-ignore hasSetting = item.setting || item.__proto__.hasOwnProperty("openSetting"); return true; } }); } html += `
${item.preferredName} ${item.name}
${item.preferredDesc || ""}
${item.incompatible ? `${window.siyuan.languages.incompatible}` : ""} ${item.preferredFunding ? `` : ""}
`; }); bazaar._data.downloaded = response.data.packages; contentElement.innerHTML = html ? html : `
`; }); }, _data: { themes: [] as IBazaarItem[], templates: [] as IBazaarItem[], icons: [] as IBazaarItem[], widgets: [] as IBazaarItem[], plugins: [] as IBazaarItem[], downloaded: [] as IBazaarItem[], }, _renderReadme(cardElement: HTMLElement, bazaarType: TBazaarType) { const dataObj = JSON.parse(cardElement.getAttribute("data-obj")); let data: IBazaarItem; (dataObj.downloaded ? bazaar._data.downloaded : bazaar._data[bazaarType]).find((item: IBazaarItem) => { if (item.repoURL === dataObj.repoURL) { data = item; return true; } }); const readmeElement = bazaar.element.querySelector("#configBazaarReadme") as HTMLElement; const urls = data.repoURL.split("/"); urls.pop(); let navTitle = window.siyuan.languages.icon; if (bazaarType === "themes") { navTitle = window.siyuan.languages.theme; } else if (bazaarType === "widgets") { navTitle = window.siyuan.languages.widget; } else if (bazaarType === "templates") { navTitle = window.siyuan.languages.template; } else if (bazaarType === "plugins") { navTitle = window.siyuan.languages.plugin; } const dataObj1 = { bazaarType, themeMode: data.modes?.toString(), name: data.name, repoURL: data.repoURL, repoHash: data.repoHash, downloaded: true }; readmeElement.innerHTML = `
${navTitle}
${data.preferredName}
${data.name}
${data.preferredFunding ? `` : `` } ${data.author}
${window.siyuan.languages.currentVer}
v${data.version}
${dataObj.downloaded ? window.siyuan.languages.installDate : window.siyuan.languages.releaseDate}
${dataObj.downloaded ? data.hInstallDate : data.hUpdated}
${dataObj.downloaded ? window.siyuan.languages.installSize : window.siyuan.languages.pkgSize}
${dataObj.downloaded ? data.hInstallSize : data.hSize}
Repo ${data.stars} ${data.openIssues} ${data.downloads}
${data.preferredDesc || ""}
`; if (dataObj.downloaded) { const mdElement = readmeElement.querySelector(".item__readme"); mdElement.innerHTML = data.preferredReadme; highlightRender(mdElement); } else { fetchPost("/api/bazaar/getBazaarPackageREAME", { repoURL: data.repoURL, repoHash: data.repoHash, packageType: bazaarType }, response => { const mdElement = readmeElement.querySelector(".item__readme"); mdElement.innerHTML = response.data.html; highlightRender(mdElement); }); } readmeElement.classList.add("config-bazaar__readme--show"); }, bindEvent(app: App) { if (!window.siyuan.config.bazaar.trust) { bazaar.element.querySelector("button").addEventListener("click", () => { fetchPost("/api/setting/setBazaar", {trust: true}, () => { window.siyuan.config.bazaar.trust = true; bazaar.element.innerHTML = bazaar.genHTML(); bazaar.bindEvent(app); }); }); return; } this._genMyHTML("plugins", app); bazaar.element.firstElementChild.addEventListener("click", (event) => { let target = event.target as HTMLElement; const dataElement = hasClosestByAttribute(target, "data-obj", null); let dataObj: IObject; if (dataElement) { dataObj = JSON.parse(dataElement.getAttribute("data-obj")); } while (target && !target.isEqualNode(bazaar.element)) { const type = target.getAttribute("data-type"); if (target.tagName === "A") { break; } else if (type === "open" && dataObj) { /// #if !BROWSER const dirName = dataObj.bazaarType; if (dirName === "icons" || dirName === "themes") { shell.openPath(path.join(window.siyuan.config.system.confDir, "appearance", dirName, dataObj.name)); } else { shell.openPath(path.join(window.siyuan.config.system.dataDir, dirName, dataObj.name)); } /// #endif event.preventDefault(); event.stopPropagation(); break; } else if (["myTheme", "myTemplate", "myIcon", "myWidget", "myPlugin"].includes(type)) { const contentElement = bazaar.element.querySelector("#configBazaarDownloaded"); if (target.classList.contains("b3-button--outline") && !contentElement.getAttribute("data-loading")) { target.parentElement.childNodes.forEach((item: HTMLElement) => { if (item.nodeType !== 3 && item.classList.contains("b3-button")) { item.classList.add("b3-button--outline"); } }); target.classList.remove("b3-button--outline"); this._genMyHTML(type.replace("my", "").toLowerCase() + "s" as TBazaarType, app); } event.preventDefault(); event.stopPropagation(); break; } else if (type === "goBack") { bazaar.element.querySelector("#configBazaarReadme").classList.remove("config-bazaar__readme--show"); event.preventDefault(); event.stopPropagation(); break; } else if (type === "install") { if (!target.classList.contains("b3-button--progress")) { const bazaarType = dataObj.bazaarType as TBazaarType; let url = "/api/bazaar/installBazaarTemplate"; if (bazaarType === "themes") { url = "/api/bazaar/installBazaarTheme"; } else if (bazaarType === "icons") { url = "/api/bazaar/installBazaarIcon"; } else if (bazaarType === "widgets") { url = "/api/bazaar/installBazaarWidget"; } else if (bazaarType === "plugins") { url = "/api/bazaar/installBazaarPlugin"; } fetchPost(url, { repoURL: dataObj.repoURL, packageName: dataObj.name, repoHash: dataObj.repoHash, mode: dataObj.themeMode === "dark" ? 1 : 0, frontend: getFrontend() }, response => { if (window.siyuan.config.appearance.themeJS && bazaarType === "themes") { exportLayout({ reload: true, onlyData: false, errorExit: false, }); return; } bazaar._onBazaar(response, bazaarType, ["themes", "icons"].includes(bazaarType)); bazaar._genMyHTML(bazaarType, app); if (bazaarType === "plugins") { confirmDialog(window.siyuan.languages.confirm, window.siyuan.languages.enablePluginTip, () => { fetchPost("/api/petal/setPetalEnabled", { packageName: dataObj.name, enabled: true, frontend: getFrontend() }, (response) => { loadPlugin(app, response.data); bazaar._genMyHTML(bazaarType, app); }); }); } }); } event.preventDefault(); event.stopPropagation(); break; } else if (type === "install-t") { if (!target.classList.contains("b3-button--progress")) { confirmDialog(window.siyuan.languages.update, window.siyuan.languages.exportTplTip, () => { const bazaarType = dataObj.bazaarType as TBazaarType; let url = "/api/bazaar/installBazaarTemplate"; if (bazaarType === "themes") { url = "/api/bazaar/installBazaarTheme"; } else if (bazaarType === "icons") { url = "/api/bazaar/installBazaarIcon"; } else if (bazaarType === "widgets") { url = "/api/bazaar/installBazaarWidget"; } else if (bazaarType === "plugins") { url = "/api/bazaar/installBazaarPlugin"; } if (!target.classList.contains("b3-button")) { target.parentElement.insertAdjacentHTML("afterend", ''); } fetchPost(url, { repoURL: dataObj.repoURL, packageName: dataObj.name, repoHash: dataObj.repoHash, mode: dataObj.themeMode === "dark" ? 1 : 0, update: true, frontend: getFrontend() }, response => { // 更新主题后不需要对该主题进行切换 https://github.com/siyuan-note/siyuan/issues/4966 this._genMyHTML(bazaarType, app); bazaar._onBazaar(response, bazaarType, ["icons"].includes(bazaarType)); // https://github.com/siyuan-note/siyuan/issues/5411 if (bazaarType === "themes" && ( (window.siyuan.config.appearance.mode === 0 && window.siyuan.config.appearance.themeLight === dataObj.name) || (window.siyuan.config.appearance.mode === 1 && window.siyuan.config.appearance.themeDark === dataObj.name) )) { if (window.siyuan.config.appearance.themeJS) { exportLayout({ reload: true, onlyData: false, errorExit: false, }); } else { const linkElement = (document.getElementById("themeDefaultStyle") as HTMLLinkElement); linkElement.href = linkElement.href + "1"; } } }); }); } event.preventDefault(); event.stopPropagation(); break; } else if (type === "uninstall") { const bazaarType = dataObj.bazaarType as TBazaarType; let url = "/api/bazaar/uninstallBazaarTemplate"; if (bazaarType === "themes") { url = "/api/bazaar/uninstallBazaarTheme"; } else if (bazaarType === "icons") { url = "/api/bazaar/uninstallBazaarIcon"; } else if (bazaarType === "widgets") { url = "/api/bazaar/uninstallBazaarWidget"; } else if (bazaarType === "plugins") { url = "/api/bazaar/uninstallBazaarPlugin"; } const packageName = dataObj.name; if (window.siyuan.config.appearance.themeDark === packageName || window.siyuan.config.appearance.themeLight === packageName || window.siyuan.config.appearance.icon === packageName) { showMessage(window.siyuan.languages.uninstallTip); } else { confirmDialog(window.siyuan.languages.uninstall, window.siyuan.languages.confirmUninstall.replace("${name}", packageName), () => { fetchPost(url, { packageName, frontend: getFrontend() }, response => { this._genMyHTML(bazaarType, app); bazaar._onBazaar(response, bazaarType, ["themes", "icons"].includes(bazaarType)); if (bazaarType === "plugins") { uninstall(app, packageName); } }); }); } event.preventDefault(); event.stopPropagation(); break; } else if (type === "switch") { const bazaarType = dataObj.bazaarType as TBazaarType; const packageName = dataObj.name; const mode = dataObj.themeMode === "dark" ? 1 : 0; if (bazaarType === "icons") { fetchPost("/api/setting/setAppearance", Object.assign({}, window.siyuan.config.appearance, { icon: packageName, }), (appearanceResponse) => { this._genMyHTML(bazaarType, app); fetchPost("/api/bazaar/getBazaarIcon", {}, response => { response.data.appearance = appearanceResponse.data; bazaar._onBazaar(response, "icons", true); bazaar._data.icons = response.data.packages; }); }); } else if (bazaarType === "themes") { fetchPost("/api/setting/setAppearance", Object.assign({}, window.siyuan.config.appearance, { mode, modeOS: false, themeDark: mode === 1 ? packageName : window.siyuan.config.appearance.themeDark, themeLight: mode === 0 ? packageName : window.siyuan.config.appearance.themeLight, }), (appearanceResponse) => { if ((mode !== window.siyuan.config.appearance.mode || (mode === 1 && window.siyuan.config.appearance.themeDark !== packageName) || (mode === 0 && window.siyuan.config.appearance.themeLight !== packageName)) && window.siyuan.config.appearance.themeJS) { exportLayout({ reload: true, onlyData: false, errorExit: false, }); } else { this._genMyHTML("themes", app); fetchPost("/api/bazaar/getBazaarTheme", {}, response => { response.data.appearance = appearanceResponse.data; bazaar._onBazaar(response, "themes", true); bazaar._data.themes = response.data.packages; }); } }); } event.preventDefault(); event.stopPropagation(); break; } else if (type === "setting") { app.plugins.find((item: Plugin) => { if (item.name === dataObj.name) { item.openSetting(); return true; } }); event.preventDefault(); event.stopPropagation(); break; } else if (type === "plugin-enable") { if (!target.getAttribute("disabled")) { target.setAttribute("disabled", "disabled"); const enabled = (target as HTMLInputElement).checked; fetchPost("/api/petal/setPetalEnabled", { packageName: dataObj.name, enabled, frontend: getFrontend() }, (response) => { target.removeAttribute("disabled"); if (enabled) { loadPlugin(app, response.data); } else { uninstall(app, dataObj.name); } }); } event.stopPropagation(); break; } else if (target.classList.contains("b3-card")) { if (!hasClosestByClassName(event.target as HTMLElement, "b3-card__actions--right")) { bazaar._renderReadme(target, (dataObj.bazaarType) as TBazaarType); } event.preventDefault(); event.stopPropagation(); break; } else if (target.classList.contains("item") && !target.classList.contains("item--focus")) { // switch tab bazaar.element.querySelector(".layout-tab-bar .item--focus").classList.remove("item--focus"); target.classList.add("item--focus"); bazaar.element.querySelectorAll(".bazaarPanel").forEach(item => { if (type === item.getAttribute("data-type")) { item.classList.remove("fn__none"); if (!item.getAttribute("data-init")) { if (type === "template") { fetchPost("/api/bazaar/getBazaarTemplate", {}, response => { bazaar._onBazaar(response, "templates", false); bazaar._data.templates = response.data.packages; }); } else if (type === "icon") { fetchPost("/api/bazaar/getBazaarIcon", {}, response => { bazaar._onBazaar(response, "icons", false); bazaar._data.icons = response.data.packages; }); } else if (type === "widget") { fetchPost("/api/bazaar/getBazaarWidget", {}, response => { bazaar._onBazaar(response, "widgets", false); bazaar._data.widgets = response.data.packages; }); } else if (type === "theme") { fetchPost("/api/bazaar/getBazaarTheme", {}, response => { bazaar._onBazaar(response, "themes", false); bazaar._data.themes = response.data.packages; }); } else if (type === "plugin") { fetchPost("/api/bazaar/getBazaarPlugin", { frontend: getFrontend() }, response => { bazaar._onBazaar(response, "plugins", false); bazaar._data.plugins = response.data.packages; }); } item.setAttribute("data-init", "true"); } } else { item.classList.add("fn__none"); } }); event.preventDefault(); event.stopPropagation(); break; } else if (target.classList.contains("item__preview")) { target.classList.toggle("item__preview--fullscreen"); event.preventDefault(); event.stopPropagation(); break; } target = target.parentElement; } }); bazaar.element.querySelectorAll(".b3-select").forEach((selectElement: HTMLSelectElement) => { selectElement.addEventListener("change", () => { if (selectElement.id === "bazaarSelect") { // theme select bazaar.element.querySelectorAll("#configBazaarTheme .b3-card").forEach((item) => { const dataObj = JSON.parse(item.getAttribute("data-obj")); if (selectElement.value === "0") { if (dataObj.themeMode.indexOf("light") > -1) { item.classList.remove("fn__none"); } else { item.classList.add("fn__none"); } } else if (selectElement.value === "1") { if (dataObj.themeMode.indexOf("dark") > -1) { item.classList.remove("fn__none"); } else { item.classList.add("fn__none"); } } else { item.classList.remove("fn__none"); } }); } else { // sort const localSort = window.siyuan.storage[Constants.LOCAL_BAZAAR]; const panelElement = selectElement.parentElement.parentElement; let html = ""; if (selectElement.value === "0") { // 更新时间降序 Array.from(panelElement.querySelectorAll(".b3-card")).sort((a, b) => { return JSON.parse(b.getAttribute("data-obj")).updated < JSON.parse(a.getAttribute("data-obj")).updated ? -1 : 1; }).forEach((item) => { html += item.outerHTML; }); } else if (selectElement.value === "1") { // 更新时间升序 Array.from(panelElement.querySelectorAll(".b3-card")).sort((a, b) => { return JSON.parse(b.getAttribute("data-obj")).updated < JSON.parse(a.getAttribute("data-obj")).updated ? 1 : -1; }).forEach((item) => { html += item.outerHTML; }); } else if (selectElement.value === "2") { // 下载次数降序 Array.from(panelElement.querySelectorAll(".b3-card")).sort((a, b) => { return JSON.parse(b.getAttribute("data-obj")).downloads < JSON.parse(a.getAttribute("data-obj")).downloads ? -1 : 1; }).forEach((item) => { html += item.outerHTML; }); } else if (selectElement.value === "3") { // 下载次数升序 Array.from(panelElement.querySelectorAll(".b3-card")).sort((a, b) => { return JSON.parse(b.getAttribute("data-obj")).downloads < JSON.parse(a.getAttribute("data-obj")).downloads ? 1 : -1; }).forEach((item) => { html += item.outerHTML; }); } localSort[selectElement.parentElement.parentElement.getAttribute("data-type")] = selectElement.value; setStorageVal(Constants.LOCAL_BAZAAR, window.siyuan.storage[Constants.LOCAL_BAZAAR]); panelElement.querySelector(".b3-cards").innerHTML = html; } }); }); }, _onBazaar(response: IWebSocketData, bazaarType: TBazaarType, reload: boolean) { let id = "#configBazaarTemplate"; if (bazaarType === "themes") { id = "#configBazaarTheme"; } else if (bazaarType === "icons") { id = "#configBazaarIcon"; } else if (bazaarType === "widgets") { id = "#configBazaarWidget"; } else if (bazaarType === "plugins") { id = "#configBazaarPlugin"; } const element = bazaar.element.querySelector(id); if (response.code === 1) { showMessage(response.msg); element.querySelectorAll("img[data-type='img-loading']").forEach((item) => { item.remove(); }); } let html = ""; response.data.packages.forEach((item: IBazaarItem) => { html += this._genCardHTML(item, bazaarType); }); bazaar._data[bazaarType] = response.data.packages; element.innerHTML = `
${html}
`; const localSort = window.siyuan.storage[Constants.LOCAL_BAZAAR]; if (localSort[bazaarType.replace("s", "")] === "1") { html = ""; Array.from(element.querySelectorAll(".b3-card")).sort((a, b) => { return JSON.parse(b.getAttribute("data-obj")).updated < JSON.parse(a.getAttribute("data-obj")).updated ? 1 : -1; }).forEach((item) => { html += item.outerHTML; }); } else if (localSort[bazaarType.replace("s", "")] === "2") { // 下载次数降序 html = ""; Array.from(element.querySelectorAll(".b3-card")).sort((a, b) => { return JSON.parse(b.getAttribute("data-obj")).downloads < JSON.parse(a.getAttribute("data-obj")).downloads ? -1 : 1; }).forEach((item) => { html += item.outerHTML; }); } else if (localSort[bazaarType.replace("s", "")] === "3") { // 下载次数升序 html = ""; Array.from(element.querySelectorAll(".b3-card")).sort((a, b) => { return JSON.parse(b.getAttribute("data-obj")).downloads < JSON.parse(a.getAttribute("data-obj")).downloads ? 1 : -1; }).forEach((item) => { html += item.outerHTML; }); } element.innerHTML = `
${html}
`; if (reload) { appearance.onSetappearance(response.data.appearance); } } };