Added loading 5et from zip release, or cloning git repo. Improvements to CSS and some missing functionality
This commit is contained in:
1 parent
e241442302
commit
8be4c8d571
12 files changed
+1383
-104
No files matched your search
+389
-35
@@ -7,10 +7,7 @@ import { app } from "electron";
|
||||
const SHOULD_INJECT_RESKIN = process.env.FIVEETOOLS_DESKTOP_RESKIN !== "0";
|
||||
const SHOULD_FORCE_NIGHT_THEME = process.env.FIVEETOOLS_DESKTOP_FORCE_NIGHT !== "0";
|
||||
const SHOULD_USE_EXTERNAL_IMAGE_FALLBACK = process.env.FIVEETOOLS_DESKTOP_EXTERNAL_IMAGES !== "0";
|
||||
const DEFAULT_EXTERNAL_IMAGE_ROOT = "https://5e.tools/img/";
|
||||
const EXTERNAL_IMAGE_ROOT = SHOULD_USE_EXTERNAL_IMAGE_FALLBACK
|
||||
? normalizeExternalBaseUrl(process.env.FIVEETOOLS_DESKTOP_IMG_BASE_URL || DEFAULT_EXTERNAL_IMAGE_ROOT)
|
||||
: null;
|
||||
export const DEFAULT_EXTERNAL_IMAGE_ROOT = "https://5e.tools/img/";
|
||||
|
||||
const MIME_TYPES: Record<string, string> = {
|
||||
".avif": "image/avif",
|
||||
@@ -42,9 +39,10 @@ const MIME_TYPES: Record<string, string> = {
|
||||
".xml": "application/xml; charset=utf-8"
|
||||
};
|
||||
|
||||
const DESKTOP_RESKIN_BOOTSTRAP = `
|
||||
function getDesktopReskinBootstrap(externalImageRoot: string | null): string {
|
||||
return `
|
||||
(() => {
|
||||
${EXTERNAL_IMAGE_ROOT ? `globalThis.DEPLOYED_IMG_ROOT = ${JSON.stringify(EXTERNAL_IMAGE_ROOT)};` : ""}
|
||||
${externalImageRoot ? `globalThis.DEPLOYED_IMG_ROOT = ${JSON.stringify(externalImageRoot)};` : ""}
|
||||
|
||||
const root = document.documentElement;
|
||||
root.classList.add("desktop-reskin");
|
||||
@@ -71,6 +69,40 @@ const DESKTOP_RESKIN_BOOTSTRAP = `
|
||||
console.info("[5ET-D]", content);
|
||||
};
|
||||
|
||||
const requestOnboardingReset = () => {
|
||||
const message = [
|
||||
"Reset onboarding and remove local 5etools data?",
|
||||
"",
|
||||
"This will clear downloaded source/images and return to first-run setup."
|
||||
].join("\\n");
|
||||
|
||||
if (!window.confirm(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.parent && window.parent !== window) {
|
||||
window.parent.postMessage({ type: "5etd:reset-onboarding" }, "*");
|
||||
doToastInfo("Resetting onboarding...");
|
||||
return;
|
||||
}
|
||||
|
||||
doToastInfo("Reset onboarding is unavailable in this context.");
|
||||
};
|
||||
|
||||
window.addEventListener("message", (event) => {
|
||||
const data = event?.data;
|
||||
if (!data || typeof data !== "object") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.type !== "5etd:reset-onboarding-result" || data.ok !== false) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reason = typeof data.error === "string" && data.error ? data.error : "unknown error";
|
||||
doToastInfo(\`Reset onboarding failed: \${reason}\`);
|
||||
});
|
||||
|
||||
const closeDropdown = (li, toggle) => {
|
||||
if (typeof NavBar !== "undefined" && typeof NavBar._closeDropdownElement === "function") {
|
||||
NavBar._closeDropdownElement(li);
|
||||
@@ -172,17 +204,150 @@ const DESKTOP_RESKIN_BOOTSTRAP = `
|
||||
navbar.append(li);
|
||||
};
|
||||
|
||||
window.addEventListener("DOMContentLoaded", addCharactersMenu);
|
||||
window.addEventListener("load", addCharactersMenu);
|
||||
const addResetOnboardingMenu = () => {
|
||||
const navbar = document.getElementById("navbar");
|
||||
if (!navbar) return;
|
||||
|
||||
const settingsToggle = [...navbar.querySelectorAll(":scope > li.dropdown--navbar > a.ve-dropdown-toggle")]
|
||||
.find((it) => it.textContent?.trim().startsWith("Settings"));
|
||||
const settingsLi = settingsToggle?.parentElement;
|
||||
if (!settingsLi) return;
|
||||
|
||||
const menu =
|
||||
settingsLi.querySelector(":scope > ul.ve-dropdown-menu") ||
|
||||
settingsLi.querySelector(":scope > ul.dropdown-menu");
|
||||
if (!menu || menu.querySelector(".desktop-nav-reset-onboarding")) return;
|
||||
|
||||
const divider = document.createElement("li");
|
||||
divider.className = "divider desktop-nav-reset-onboarding";
|
||||
divider.setAttribute("role", "presentation");
|
||||
|
||||
const item = document.createElement("li");
|
||||
item.className = "desktop-nav-reset-onboarding";
|
||||
item.setAttribute("role", "presentation");
|
||||
|
||||
const link = document.createElement("a");
|
||||
link.href = "#";
|
||||
link.textContent = "Reset Onboarding";
|
||||
link.onclick = (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
requestOnboardingReset();
|
||||
|
||||
if (typeof NavBar !== "undefined" && typeof NavBar._closeDropdownElement === "function") {
|
||||
NavBar._closeDropdownElement(settingsLi);
|
||||
return;
|
||||
}
|
||||
|
||||
settingsLi.classList.remove("open");
|
||||
settingsToggle?.setAttribute("aria-expanded", "false");
|
||||
};
|
||||
|
||||
item.append(link);
|
||||
menu.append(divider, item);
|
||||
};
|
||||
|
||||
let isAutoFocusScheduled = false;
|
||||
let hasAutoFocusedSearch = false;
|
||||
|
||||
const tryAutoFocusSearch = () => {
|
||||
if (hasAutoFocusedSearch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const active = document.activeElement;
|
||||
if (active && active !== document.body && active !== document.documentElement) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const input = document.querySelector("#navbar .omni__input, .omni__input");
|
||||
if (!(input instanceof HTMLInputElement)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
input.focus({ preventScroll: true });
|
||||
input.select();
|
||||
|
||||
if (document.activeElement === input) {
|
||||
hasAutoFocusedSearch = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const scheduleAutoFocusSearch = () => {
|
||||
if (isAutoFocusScheduled || hasAutoFocusedSearch) {
|
||||
return;
|
||||
}
|
||||
|
||||
isAutoFocusScheduled = true;
|
||||
let attempts = 0;
|
||||
const maxAttempts = 80;
|
||||
const intervalMs = 75;
|
||||
|
||||
const runAttempt = () => {
|
||||
if (tryAutoFocusSearch()) {
|
||||
isAutoFocusScheduled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
attempts += 1;
|
||||
if (attempts >= maxAttempts) {
|
||||
isAutoFocusScheduled = false;
|
||||
}
|
||||
};
|
||||
|
||||
runAttempt();
|
||||
const interval = window.setInterval(() => {
|
||||
if (!isAutoFocusScheduled) {
|
||||
window.clearInterval(interval);
|
||||
return;
|
||||
}
|
||||
|
||||
runAttempt();
|
||||
}, intervalMs);
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
if (tryAutoFocusSearch()) {
|
||||
isAutoFocusScheduled = false;
|
||||
observer.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
const obsTarget = document.body || document.documentElement;
|
||||
if (obsTarget) {
|
||||
observer.observe(obsTarget, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
window.setTimeout(() => {
|
||||
observer.disconnect();
|
||||
isAutoFocusScheduled = false;
|
||||
}, maxAttempts * intervalMs + 250);
|
||||
};
|
||||
|
||||
const applyDesktopMenuExtensions = () => {
|
||||
addCharactersMenu();
|
||||
addResetOnboardingMenu();
|
||||
scheduleAutoFocusSearch();
|
||||
};
|
||||
|
||||
window.addEventListener("DOMContentLoaded", applyDesktopMenuExtensions);
|
||||
window.addEventListener("load", applyDesktopMenuExtensions);
|
||||
})();
|
||||
`;
|
||||
}
|
||||
|
||||
const DESKTOP_RESKIN_STYLE = `
|
||||
html.desktop-reskin {
|
||||
--desktop-shell-gutter: 14px;
|
||||
--desktop-topbar-pad-x: 18px;
|
||||
--desktop-nav-top-offset: 10px;
|
||||
--desktop-nav-reserved-height: 92px;
|
||||
}
|
||||
|
||||
html.desktop-reskin body {
|
||||
padding-top: calc(var(--safe-area-inset-top) + var(--desktop-nav-reserved-height)) !important;
|
||||
background:
|
||||
radial-gradient(circle at 13% -2%, rgba(77, 140, 222, 0.3), transparent 40%),
|
||||
radial-gradient(circle at 86% -4%, rgba(241, 176, 92, 0.24), transparent 38%),
|
||||
@@ -190,12 +355,17 @@ html.desktop-reskin body {
|
||||
color: #dce7f6;
|
||||
}
|
||||
|
||||
html.desktop-reskin .viewport-wrapper {
|
||||
inset: calc(var(--safe-area-inset-top) + var(--desktop-nav-reserved-height)) 0 0 0 !important;
|
||||
height: auto !important;
|
||||
min-height: calc(100vh - (var(--safe-area-inset-top) + var(--desktop-nav-reserved-height)));
|
||||
}
|
||||
|
||||
html.desktop-reskin .page__header {
|
||||
margin: 10px var(--desktop-shell-gutter) 0 !important;
|
||||
margin: 8px var(--desktop-shell-gutter) 0 !important;
|
||||
padding: 0 !important;
|
||||
border: 1px solid rgba(109, 164, 223, 0.32) !important;
|
||||
border-bottom: 0 !important;
|
||||
border-radius: 18px 18px 0 0;
|
||||
border-radius: 18px;
|
||||
background: linear-gradient(115deg, rgba(17, 29, 47, 0.98), rgba(24, 38, 58, 0.96) 64%, rgba(87, 58, 32, 0.88));
|
||||
backdrop-filter: blur(14px);
|
||||
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.24);
|
||||
@@ -226,17 +396,19 @@ html.desktop-reskin .page__subtitle {
|
||||
}
|
||||
|
||||
html.desktop-reskin #navigation.page__nav {
|
||||
margin: 0 var(--desktop-shell-gutter) 12px !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
max-width: none !important;
|
||||
position: fixed;
|
||||
top: calc(var(--safe-area-inset-top) + var(--desktop-nav-top-offset));
|
||||
left: var(--desktop-shell-gutter);
|
||||
right: var(--desktop-shell-gutter);
|
||||
width: auto !important;
|
||||
max-width: none !important;
|
||||
border: 1px solid rgba(109, 164, 223, 0.32) !important;
|
||||
border-top: 0 !important;
|
||||
border-radius: 0 0 18px 18px;
|
||||
border-radius: 18px;
|
||||
background: linear-gradient(180deg, rgba(12, 21, 35, 0.96), rgba(8, 15, 27, 0.98));
|
||||
box-shadow: 0 12px 26px rgba(0, 0, 0, 0.28);
|
||||
position: relative;
|
||||
z-index: 3000 !important;
|
||||
z-index: 3600 !important;
|
||||
}
|
||||
|
||||
html.desktop-reskin #navigation.page__nav .page__nav-inner {
|
||||
@@ -339,6 +511,68 @@ html.desktop-reskin #navbar > li.dropdown--navbar > a.ve-dropdown-toggle > .care
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
html.desktop-reskin #navigation.page__nav .page__btn-toggle-nav {
|
||||
margin: 0 !important;
|
||||
width: 48px !important;
|
||||
min-width: 48px !important;
|
||||
height: 44px !important;
|
||||
padding: 0 !important;
|
||||
border: 1px solid rgba(116, 174, 233, 0.52) !important;
|
||||
border-radius: 12px !important;
|
||||
background: linear-gradient(180deg, rgba(22, 36, 56, 0.95), rgba(13, 23, 38, 0.98)) !important;
|
||||
box-shadow: inset 0 1px 0 rgba(172, 220, 255, 0.16) !important;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
text-indent: -9999px;
|
||||
color: transparent !important;
|
||||
}
|
||||
|
||||
html.desktop-reskin #navigation.page__nav .page__btn-toggle-nav::before,
|
||||
html.desktop-reskin #navigation.page__nav .page__btn-toggle-nav::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 13px;
|
||||
right: 13px;
|
||||
height: 2px;
|
||||
border-radius: 2px;
|
||||
background: #ddebff;
|
||||
transition: transform 150ms ease, top 150ms ease, box-shadow 150ms ease;
|
||||
}
|
||||
|
||||
html.desktop-reskin #navigation.page__nav .page__btn-toggle-nav::before {
|
||||
top: 50%;
|
||||
transform: translateY(-8px);
|
||||
box-shadow: 0 7px 0 #ddebff;
|
||||
}
|
||||
|
||||
html.desktop-reskin #navigation.page__nav .page__btn-toggle-nav::after {
|
||||
top: 50%;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
|
||||
html.desktop-reskin #navigation.page__nav .page__btn-toggle-nav:hover,
|
||||
html.desktop-reskin #navigation.page__nav .page__btn-toggle-nav:focus {
|
||||
border-color: rgba(137, 204, 255, 0.88) !important;
|
||||
background: linear-gradient(145deg, rgba(51, 101, 160, 0.82), rgba(31, 84, 139, 0.86)) !important;
|
||||
}
|
||||
|
||||
html.desktop-reskin #navigation.page__nav .page__btn-toggle-nav.active {
|
||||
border-color: rgba(139, 207, 255, 0.92) !important;
|
||||
background: linear-gradient(140deg, rgba(34, 127, 210, 0.92), rgba(25, 95, 167, 0.96)) !important;
|
||||
box-shadow: inset 0 1px 0 rgba(229, 245, 255, 0.22) !important;
|
||||
}
|
||||
|
||||
html.desktop-reskin #navigation.page__nav .page__btn-toggle-nav.active::before {
|
||||
top: 50%;
|
||||
transform: translateY(0) rotate(45deg);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
html.desktop-reskin #navigation.page__nav .page__btn-toggle-nav.active::after {
|
||||
top: 50%;
|
||||
transform: translateY(0) rotate(-45deg);
|
||||
}
|
||||
|
||||
html.desktop-reskin #navbar > .omni__wrp-input:not(.omni__wrp-input--scrolled) {
|
||||
margin-left: auto !important;
|
||||
flex: 1 1 320px;
|
||||
@@ -372,6 +606,7 @@ html.desktop-reskin #navbar > .omni__wrp-input:not(.omni__wrp-input--scrolled) .
|
||||
@media (max-width: 1180px) {
|
||||
html.desktop-reskin {
|
||||
--desktop-topbar-pad-x: 12px;
|
||||
--desktop-nav-reserved-height: 108px;
|
||||
}
|
||||
|
||||
html.desktop-reskin #navbar {
|
||||
@@ -385,11 +620,93 @@ html.desktop-reskin #navbar > .omni__wrp-input:not(.omni__wrp-input--scrolled) .
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
html.desktop-reskin {
|
||||
--desktop-nav-reserved-height: 84px;
|
||||
}
|
||||
|
||||
html.desktop-reskin #navigation.page__nav {
|
||||
display: grid !important;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
column-gap: 10px;
|
||||
row-gap: 8px;
|
||||
align-items: center;
|
||||
width: auto !important;
|
||||
padding: 10px 12px 0 !important;
|
||||
}
|
||||
|
||||
html.desktop-reskin #navigation.page__nav .page__btn-toggle-nav {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
html.desktop-reskin #navigation.page__nav .page__nav-inner {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
html.desktop-reskin #navbar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
flex-wrap: nowrap;
|
||||
gap: 8px;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
html.desktop-reskin #navbar > li {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
html.desktop-reskin #navbar > li > a {
|
||||
width: 100%;
|
||||
min-height: 46px;
|
||||
padding: 10px 16px !important;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
html.desktop-reskin #navbar > .omni__wrp-input,
|
||||
html.desktop-reskin #navbar > .omni__wrp-input:not(.omni__wrp-input--scrolled) {
|
||||
order: -1;
|
||||
margin: 0 !important;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
html.desktop-reskin #navbar > .omni__wrp-input .omni__input,
|
||||
html.desktop-reskin #navbar > .omni__wrp-input:not(.omni__wrp-input--scrolled) .omni__input {
|
||||
width: 100%;
|
||||
min-width: 0 !important;
|
||||
}
|
||||
|
||||
html.desktop-reskin #navbar > li.page__nav-hidden-mobile {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
html.desktop-reskin #navbar > li.dropdown--navbar.open > .dropdown-menu,
|
||||
html.desktop-reskin #navbar > li.dropdown--navbar.open > .ve-dropdown-menu {
|
||||
position: static !important;
|
||||
float: none !important;
|
||||
width: 100% !important;
|
||||
margin: 6px 0 0 !important;
|
||||
}
|
||||
|
||||
html.desktop-reskin #navbar .ve-dropdown-menu--side {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
html.desktop-reskin #navbar .form-control,
|
||||
html.desktop-reskin #navbar input[type="search"] {
|
||||
background: rgba(8, 14, 24, 0.96) !important;
|
||||
border: 1px solid rgba(102, 155, 210, 0.42) !important;
|
||||
color: #e4effb !important;
|
||||
height: 44px !important;
|
||||
border-radius: 12px !important;
|
||||
}
|
||||
|
||||
@@ -692,10 +1009,16 @@ export interface WikiServer {
|
||||
close: () => Promise<void>;
|
||||
}
|
||||
|
||||
function getWikiRootCandidates(): string[] {
|
||||
export interface StartWikiServerOptions {
|
||||
rootDir?: string;
|
||||
externalImageRoot?: string | null;
|
||||
}
|
||||
|
||||
function getWikiRootCandidates(explicitRootDir?: string): string[] {
|
||||
const envDir = process.env.FIVEETOOLS_SRC_DIR;
|
||||
|
||||
return [
|
||||
explicitRootDir,
|
||||
envDir,
|
||||
path.resolve(app.getAppPath(), "..", "5etools-src"),
|
||||
path.resolve(app.getAppPath(), "5etools-src"),
|
||||
@@ -705,14 +1028,17 @@ function getWikiRootCandidates(): string[] {
|
||||
].filter((it): it is string => Boolean(it));
|
||||
}
|
||||
|
||||
function resolveWikiRootDir(): string {
|
||||
const candidates = getWikiRootCandidates();
|
||||
function isWikiRootDir(candidate: string): boolean {
|
||||
const indexPath = path.join(candidate, "5etools.html");
|
||||
const dataPath = path.join(candidate, "data");
|
||||
return fs.existsSync(indexPath) && fs.existsSync(dataPath);
|
||||
}
|
||||
|
||||
function resolveWikiRootDir(explicitRootDir?: string): string {
|
||||
const candidates = getWikiRootCandidates(explicitRootDir);
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const indexPath = path.join(candidate, "5etools.html");
|
||||
const dataPath = path.join(candidate, "data");
|
||||
|
||||
if (fs.existsSync(indexPath) && fs.existsSync(dataPath)) {
|
||||
if (isWikiRootDir(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
@@ -721,6 +1047,14 @@ function resolveWikiRootDir(): string {
|
||||
throw new Error(`Unable to locate 5etools-src directory. Checked:\n${formatted}`);
|
||||
}
|
||||
|
||||
export function findWikiRootDir(explicitRootDir?: string): string | null {
|
||||
try {
|
||||
return resolveWikiRootDir(explicitRootDir);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getMimeType(filePath: string): string {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
return MIME_TYPES[ext] ?? "application/octet-stream";
|
||||
@@ -731,13 +1065,27 @@ function normalizeExternalBaseUrl(url: string): string {
|
||||
return `${trimmed.replace(/\/+$/, "")}/`;
|
||||
}
|
||||
|
||||
function resolveExternalImageRoot(externalImageRoot?: string | null): string | null {
|
||||
if (externalImageRoot != null) {
|
||||
const trimmed = externalImageRoot.trim();
|
||||
return trimmed ? normalizeExternalBaseUrl(trimmed) : null;
|
||||
}
|
||||
|
||||
if (!SHOULD_USE_EXTERNAL_IMAGE_FALLBACK) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const envValue = process.env.FIVEETOOLS_DESKTOP_IMG_BASE_URL || DEFAULT_EXTERNAL_IMAGE_ROOT;
|
||||
return normalizeExternalBaseUrl(envValue);
|
||||
}
|
||||
|
||||
function getRequestPath(requestUrl: string): string {
|
||||
const parsed = new URL(requestUrl, "http://127.0.0.1");
|
||||
return decodeURIComponent(parsed.pathname);
|
||||
}
|
||||
|
||||
function getExternalImageUrl(requestUrl: string): URL | null {
|
||||
if (!EXTERNAL_IMAGE_ROOT) {
|
||||
function getExternalImageUrl(requestUrl: string, externalImageRoot: string | null): URL | null {
|
||||
if (!externalImageRoot) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -757,11 +1105,15 @@ function getExternalImageUrl(requestUrl: string): URL | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new URL(relativePath, EXTERNAL_IMAGE_ROOT);
|
||||
return new URL(relativePath, externalImageRoot);
|
||||
}
|
||||
|
||||
async function pTryServeExternalImage(requestUrl: string, res: http.ServerResponse): Promise<boolean> {
|
||||
const externalImageUrl = getExternalImageUrl(requestUrl);
|
||||
async function pTryServeExternalImage(
|
||||
requestUrl: string,
|
||||
res: http.ServerResponse,
|
||||
externalImageRoot: string | null
|
||||
): Promise<boolean> {
|
||||
const externalImageUrl = getExternalImageUrl(requestUrl, externalImageRoot);
|
||||
if (!externalImageUrl) {
|
||||
return false;
|
||||
}
|
||||
@@ -786,13 +1138,13 @@ async function pTryServeExternalImage(requestUrl: string, res: http.ServerRespon
|
||||
}
|
||||
}
|
||||
|
||||
function injectDesktopReskin(html: string): string {
|
||||
function injectDesktopReskin(html: string, bootstrapScript: string): string {
|
||||
if (html.includes("desktop-reskin-style")) {
|
||||
return html;
|
||||
}
|
||||
|
||||
const injection = [
|
||||
`<script id="desktop-reskin-boot">${DESKTOP_RESKIN_BOOTSTRAP}</script>`,
|
||||
`<script id="desktop-reskin-boot">${bootstrapScript}</script>`,
|
||||
`<style id="desktop-reskin-style">${DESKTOP_RESKIN_STYLE}</style>`
|
||||
].join("");
|
||||
|
||||
@@ -825,8 +1177,10 @@ function resolveRequestPath(rootDir: string, requestUrl: string): string {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
export async function startWikiServer(): Promise<WikiServer> {
|
||||
const rootDir = resolveWikiRootDir();
|
||||
export async function startWikiServer(options: StartWikiServerOptions = {}): Promise<WikiServer> {
|
||||
const rootDir = resolveWikiRootDir(options.rootDir);
|
||||
const externalImageRoot = resolveExternalImageRoot(options.externalImageRoot);
|
||||
const reskinBootstrap = getDesktopReskinBootstrap(externalImageRoot);
|
||||
|
||||
return await new Promise<WikiServer>((resolve, reject) => {
|
||||
const server = http.createServer((req, res) => {
|
||||
@@ -844,7 +1198,7 @@ export async function startWikiServer(): Promise<WikiServer> {
|
||||
fs.readFile(filePath, (err, data) => {
|
||||
if (err) {
|
||||
if (err.code === "ENOENT") {
|
||||
void pTryServeExternalImage(url, res).then((isServed) => {
|
||||
void pTryServeExternalImage(url, res, externalImageRoot).then((isServed) => {
|
||||
if (isServed) {
|
||||
return;
|
||||
}
|
||||
@@ -862,7 +1216,7 @@ export async function startWikiServer(): Promise<WikiServer> {
|
||||
|
||||
const extension = path.extname(filePath).toLowerCase();
|
||||
const transformedBody =
|
||||
extension === ".html" && SHOULD_INJECT_RESKIN ? injectDesktopReskin(data.toString("utf8")) : data;
|
||||
extension === ".html" && SHOULD_INJECT_RESKIN ? injectDesktopReskin(data.toString("utf8"), reskinBootstrap) : data;
|
||||
|
||||
res.writeHead(200, {
|
||||
"Content-Type": getMimeType(filePath),
|
||||
|
||||
Reference in new issue
Block a user