Milestone admin and frontend stage

This commit is contained in:
dcconstructions
2026-06-27 17:47:46 +03:00
parent 66d59eba0a
commit e9c0dd56c2
16 changed files with 215 additions and 12 deletions
+21 -3
View File
@@ -66,6 +66,8 @@ const MEDIA_EXTENSIONS = new Set([
".gif",
".glb",
".gltf",
".ico",
".json",
".jpeg",
".jpg",
".m4v",
@@ -73,6 +75,7 @@ const MEDIA_EXTENSIONS = new Set([
".mp4",
".png",
".svg",
".webmanifest",
".webm",
".webp",
".woff",
@@ -91,6 +94,7 @@ const REFERENCE_TEXT_EXTENSIONS = new Set([
".ts",
".tsx",
".txt",
".webmanifest",
".xml",
]);
@@ -100,6 +104,8 @@ const MIME = {
".js": "text/javascript; charset=utf-8",
".json": "application/json; charset=utf-8",
".svg": "image/svg+xml",
".ico": "image/x-icon",
".webmanifest": "application/manifest+json; charset=utf-8",
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
@@ -150,6 +156,15 @@ function sendStream(req, res, status, stream, headers) {
stream.pipe(res);
}
function contentTypeForPath(filePath) {
const normalizedPath = String(filePath || "").toLowerCase();
if (normalizedPath.endsWith(".webmanifest") || normalizedPath.endsWith(".webmanifest.json")) {
return "application/manifest+json; charset=utf-8";
}
return MIME[extname(filePath).toLowerCase()] || "application/octet-stream";
}
function sendJson(res, status, payload) {
send(res, status, `${JSON.stringify(payload, null, 2)}\n`, "application/json; charset=utf-8");
}
@@ -225,6 +240,8 @@ function sanitizeUploadBucket(value) {
function extensionFromMimeType(mimeType) {
const map = {
"image/svg+xml": ".svg",
"image/x-icon": ".ico",
"image/vnd.microsoft.icon": ".ico",
"image/png": ".png",
"image/jpeg": ".jpg",
"image/gif": ".gif",
@@ -233,6 +250,8 @@ function extensionFromMimeType(mimeType) {
"video/mp4": ".mp4",
"video/webm": ".webm",
"video/quicktime": ".mov",
"application/manifest+json": ".webmanifest",
"application/json": ".json",
"model/gltf-binary": ".glb",
"model/gltf+json": ".gltf",
"font/woff": ".woff",
@@ -503,7 +522,7 @@ function serializedAssetReplacements(replacements) {
}
function mediaKindFromExtension(extension) {
if ([".avif", ".gif", ".jpeg", ".jpg", ".png", ".svg", ".webp"].includes(extension)) return "image";
if ([".avif", ".gif", ".ico", ".jpeg", ".jpg", ".png", ".svg", ".webp"].includes(extension)) return "image";
if ([".m4v", ".mov", ".mp4", ".webm"].includes(extension)) return "video";
if ([".glb", ".gltf"].includes(extension)) return "model";
if ([".woff", ".woff2"].includes(extension)) return "font";
@@ -1383,8 +1402,7 @@ async function serveStatic(req, res) {
return;
}
const ext = extname(filePath);
const contentType = MIME[ext] || "application/octet-stream";
const contentType = contentTypeForPath(filePath);
const range = req.headers.range;
if (range) {
+45
View File
@@ -720,6 +720,12 @@ const staticElements = {
appIconAlt: "Иконка NODE.DC",
appWordmarkSrc: "./assets/webflow/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc",
appWordmarkAlt: "Логотип NODE.DC",
faviconSvg: "./assets/uploads/favicon/icon-adaptive.svg",
faviconIco: "./assets/uploads/favicon/favicon.ico",
faviconAppleTouchIcon: "./assets/uploads/favicon/apple-touch-icon.png",
faviconPng192: "./assets/uploads/favicon/icon-192.png",
faviconPng512: "./assets/uploads/favicon/icon-512.png",
faviconManifest: "./assets/uploads/favicon/manifest.webmanifest.json",
},
},
editableFields: [
@@ -753,6 +759,12 @@ const staticElements = {
field("content.assets.appIconAlt", "Иконка приложения: alt"),
field("content.assets.appWordmarkSrc", "Wordmark приложения", "media", "attr"),
field("content.assets.appWordmarkAlt", "Wordmark приложения: alt"),
field("content.assets.faviconSvg", "Favicon SVG", "media", "attr"),
field("content.assets.faviconIco", "Favicon ICO", "media", "attr"),
field("content.assets.faviconAppleTouchIcon", "Favicon Apple 180", "media", "attr"),
field("content.assets.faviconPng192", "Favicon PNG 192", "media", "attr"),
field("content.assets.faviconPng512", "Favicon PNG 512", "media", "attr"),
field("content.assets.faviconManifest", "Favicon manifest", "media", "attr"),
],
};
@@ -772,6 +784,39 @@ function tokenizeStaticShell(source, definition) {
const { navigation, notifications, dock } = definition.content;
let tokenized = source;
const faviconHead = [
`{{#if staticElements.content.assets.faviconSvg}}<link rel="icon" type="image/svg+xml" sizes="any" href="${shellToken(
"attr",
"content.assets.faviconSvg",
)}"/>{{/if}}`,
`{{#if staticElements.content.assets.faviconIco}}<link rel="icon" type="image/x-icon" href="${shellToken(
"attr",
"content.assets.faviconIco",
)}"/>{{/if}}`,
`{{#if staticElements.content.assets.faviconAppleTouchIcon}}<link rel="apple-touch-icon" sizes="180x180" href="${shellToken(
"attr",
"content.assets.faviconAppleTouchIcon",
)}"/>{{/if}}`,
`{{#if staticElements.content.assets.faviconPng192}}<link rel="icon" type="image/png" sizes="192x192" href="${shellToken(
"attr",
"content.assets.faviconPng192",
)}"/>{{/if}}`,
`{{#if staticElements.content.assets.faviconPng512}}<link rel="icon" type="image/png" sizes="512x512" href="${shellToken(
"attr",
"content.assets.faviconPng512",
)}"/>{{/if}}`,
`{{#if staticElements.content.assets.faviconManifest}}<link rel="manifest" href="${shellToken(
"attr",
"content.assets.faviconManifest",
)}"/>{{/if}}`,
].join("");
tokenized = replaceShellLiteral(
tokenized,
`<link href="./assets/webflow/images/69b83bc24411350dcdb86781_favicon.png" rel="shortcut icon" type="image/x-icon"/><link href="./assets/webflow/images/69b83bc6ad9e108637556ec5_webclip.png" rel="apple-touch-icon"/>`,
faviconHead,
"favicon links",
);
tokenized = replaceShellLiteral(
tokenized,
+21
View File
@@ -78,6 +78,11 @@ function isExternalHref(value) {
return /^(https?:)?\/\//i.test(String(value || "")) || /^(mailto:|tel:)/i.test(String(value || ""));
}
function isInertHref(value) {
const href = String(value || "").trim();
return !href || href === "#" || href === "#hero" || href.toLowerCase().startsWith("javascript:");
}
function normalizeLinkForRender(item) {
if (!item || typeof item !== "object") return;
@@ -103,12 +108,28 @@ function normalizeLinkForRender(item) {
item.resolvedTarget = mode === "href" ? linkTarget : "";
}
function normalizeDockItemForRender(item) {
if (!item || typeof item !== "object") return;
const href = String(item.href || "").trim();
const opensWindow = !isInertHref(href);
item.resolvedDockHref = href || "#";
item.resolvedDockTarget = opensWindow ? "_blank" : "";
item.resolvedDockRel = opensWindow ? "noopener noreferrer" : "";
}
function applyRenderableLinks(model) {
const staticLinks = model.staticElements?.content?.navigation?.links;
if (Array.isArray(staticLinks)) {
staticLinks.forEach(normalizeLinkForRender);
}
const dockItems = model.staticElements?.content?.dock?.items;
if (Array.isArray(dockItems)) {
dockItems.forEach(normalizeDockItemForRender);
}
for (const block of allBlocks(model)) {
const footer = block.content?.footer;
if (!footer) continue;