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

View File

@ -220,6 +220,12 @@ const STATIC_FALLBACK = {
appIconAlt: "Иконка NODE.DC", appIconAlt: "Иконка NODE.DC",
appWordmarkSrc: "./assets/webflow/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc", appWordmarkSrc: "./assets/webflow/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc",
appWordmarkAlt: "Логотип 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: [ editableFields: [
@ -289,6 +295,12 @@ const STATIC_FALLBACK = {
field("content.assets.appIconAlt", "Иконка приложения: alt", "text", "text", "Модель и логотипы"), field("content.assets.appIconAlt", "Иконка приложения: alt", "text", "text", "Модель и логотипы"),
field("content.assets.appWordmarkSrc", "Wordmark приложения", "media", "attr", "Модель и логотипы"), field("content.assets.appWordmarkSrc", "Wordmark приложения", "media", "attr", "Модель и логотипы"),
field("content.assets.appWordmarkAlt", "Wordmark приложения: alt", "text", "text", "Модель и логотипы"), field("content.assets.appWordmarkAlt", "Wordmark приложения: alt", "text", "text", "Модель и логотипы"),
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", "Модель и логотипы"),
], ],
}; };
@ -297,7 +309,7 @@ const GROUP_COPY = {
"Шапка: ссылки": "Пункты навигации верхней mac-панели. Каждый пункт можно добавить, удалить и переставить.", "Шапка: ссылки": "Пункты навигации верхней mac-панели. Каждый пункт можно добавить, удалить и переставить.",
Уведомления: "Карточки уведомлений справа сверху. Порядок в админке соответствует порядку на сайте.", Уведомления: "Карточки уведомлений справа сверху. Порядок в админке соответствует порядку на сайте.",
Dock: "Нижняя системная панель: приложения, корзина, ссылки и подписи файлов.", Dock: "Нижняя системная панель: приложения, корзина, ссылки и подписи файлов.",
"Модель и логотипы": "Базовые ассеты статичного слоя: GLB-модель, PNG/SVG-логотипы и alt-тексты.", "Модель и логотипы": "Базовые ассеты статичного слоя: GLB-модель, PNG/SVG-логотипы, favicon-комплект и alt-тексты.",
Заголовок: "Главный заголовок и вводный текст выбранной секции.", Заголовок: "Главный заголовок и вводный текст выбранной секции.",
"Верхний информационный блок": "Верхняя пара: длинный текст и крупный заголовок над медиаслайдером.", "Верхний информационный блок": "Верхняя пара: длинный текст и крупный заголовок над медиаслайдером.",
"Нижний информационный блок": "Нижняя зеркальная пара: крупный заголовок и длинный текст под медиаслайдером.", "Нижний информационный блок": "Нижняя зеркальная пара: крупный заголовок и длинный текст под медиаслайдером.",
@ -328,7 +340,8 @@ const GROUP_COPY = {
Контент: "Остальные typed-поля выбранного блока.", Контент: "Остальные typed-поля выбранного блока.",
}; };
const MEDIA_ACCEPT = "image/*,video/*,.gif,.webm,.mov,.mp4,.m4v,.avi,.mkv,.glb,.gltf,.svg,.avif,.webp,.woff,.woff2"; const MEDIA_ACCEPT =
"image/*,video/*,.gif,.webm,.mov,.mp4,.m4v,.avi,.mkv,.glb,.gltf,.svg,.ico,.avif,.webp,.webmanifest,.json,.woff,.woff2";
const MEDIA_DRAG_MIME = "application/x-nodedc-media-entry"; const MEDIA_DRAG_MIME = "application/x-nodedc-media-entry";
const MEDIA_KIND_FILTERS = [ const MEDIA_KIND_FILTERS = [
["all", "Все"], ["all", "Все"],
@ -598,6 +611,11 @@ function ensureStaticElements() {
notification.eyebrowClass ||= STATIC_FALLBACK.content.notifications[index]?.eyebrowClass || "red-txt gray"; notification.eyebrowClass ||= STATIC_FALLBACK.content.notifications[index]?.eyebrowClass || "red-txt gray";
}); });
state.page.staticElements.content.assets ||= clone(STATIC_FALLBACK.content.assets); state.page.staticElements.content.assets ||= clone(STATIC_FALLBACK.content.assets);
for (const [key, value] of Object.entries(STATIC_FALLBACK.content.assets)) {
if (!Object.prototype.hasOwnProperty.call(state.page.staticElements.content.assets, key)) {
state.page.staticElements.content.assets[key] = value;
}
}
state.page.staticElements.content.dock ||= clone(STATIC_FALLBACK.content.dock); state.page.staticElements.content.dock ||= clone(STATIC_FALLBACK.content.dock);
normalizeDock(state.page.staticElements.content.dock, state.page.staticElements.content.assets); normalizeDock(state.page.staticElements.content.dock, state.page.staticElements.content.assets);
state.page.staticElements.editableFields = clone(STATIC_FALLBACK.editableFields); state.page.staticElements.editableFields = clone(STATIC_FALLBACK.editableFields);
@ -715,7 +733,7 @@ function adminPreviewUrl(value) {
function mediaKindFromValue(value) { function mediaKindFromValue(value) {
if (!value) return null; if (!value) return null;
if (/\.(mp4|webm|mov|m4v|avi|mkv)(\?.*)?$/i.test(value)) return "video"; if (/\.(mp4|webm|mov|m4v|avi|mkv)(\?.*)?$/i.test(value)) return "video";
if (/\.(png|jpe?g|gif|svg|avif|webp)(\?.*)?$/i.test(value)) return "image"; if (/\.(png|jpe?g|gif|svg|ico|avif|webp)(\?.*)?$/i.test(value)) return "image";
if (/\.(glb|gltf)(\?.*)?$/i.test(value)) return "model"; if (/\.(glb|gltf)(\?.*)?$/i.test(value)) return "model";
if (/\.(woff2?|ttf|otf)(\?.*)?$/i.test(value)) return "font"; if (/\.(woff2?|ttf|otf)(\?.*)?$/i.test(value)) return "font";
return null; return null;
@ -781,6 +799,7 @@ function uploadBucketForField(block, editableField) {
if (path.includes("webgl") || currentValue.endsWith(".glb") || currentValue.endsWith(".gltf")) root = "models"; if (path.includes("webgl") || currentValue.endsWith(".glb") || currentValue.endsWith(".gltf")) root = "models";
if (path.includes("video") || path.includes("media") || path.includes("sliders") || path.includes("slideritems")) root = "media"; if (path.includes("video") || path.includes("media") || path.includes("sliders") || path.includes("slideritems")) root = "media";
if (path.includes("font") || /\.(woff2?|ttf|otf)(\?.*)?$/i.test(currentValue)) root = "fonts"; if (path.includes("font") || /\.(woff2?|ttf|otf)(\?.*)?$/i.test(currentValue)) root = "fonts";
if (path.includes("favicon")) return "favicon";
return `${root}/${blockSegment}`; return `${root}/${blockSegment}`;
} }

View File

@ -5,3 +5,4 @@
.pricing-table-section .pricing-table-shell{padding-top:12svh;padding-bottom:12svh}@media screen and (max-width:767px){.pricing-table-section .pricing-table-shell{padding-top:8svh;padding-bottom:8svh}} .pricing-table-section .pricing-table-shell{padding-top:12svh;padding-bottom:12svh}@media screen and (max-width:767px){.pricing-table-section .pricing-table-shell{padding-top:8svh;padding-bottom:8svh}}
[data-module=notification-w][data-notification-stack-ready]{height:auto;max-height:calc(100svh - 3.2rem);overflow:visible;scrollbar-width:none;overscroll-behavior:contain}[data-module=notification-w][data-notification-stack-ready]::-webkit-scrollbar{display:none}[data-module=notification-w][data-notification-stack-ready]:not(:hover):not([data-notification-expanded=true]){height:var(--notification-collapsed-height,6.6rem);max-height:var(--notification-collapsed-height,6.6rem)}[data-module=notification-w][data-notification-stack-ready]:is(:hover,[data-notification-expanded=true]){overflow-x:visible;overflow-y:auto;padding-bottom:.2rem}[data-module=notification-w][data-notification-stack-ready]>[data-module=notification]{position:relative;z-index:var(--notification-z,1);transform:translateY(var(--notification-stack-y,0));transition:transform .3s cubic-bezier(.22,1,.36,1)}[data-module=notification-w][data-notification-stack-ready]:is(:hover,[data-notification-expanded=true])>[data-module=notification]{transform:translateY(0)} [data-module=notification-w][data-notification-stack-ready]{height:auto;max-height:calc(100svh - 3.2rem);overflow:visible;scrollbar-width:none;overscroll-behavior:contain}[data-module=notification-w][data-notification-stack-ready]::-webkit-scrollbar{display:none}[data-module=notification-w][data-notification-stack-ready]:not(:hover):not([data-notification-expanded=true]){height:var(--notification-collapsed-height,6.6rem);max-height:var(--notification-collapsed-height,6.6rem)}[data-module=notification-w][data-notification-stack-ready]:is(:hover,[data-notification-expanded=true]){overflow-x:visible;overflow-y:auto;padding-bottom:.2rem}[data-module=notification-w][data-notification-stack-ready]>[data-module=notification]{position:relative;z-index:var(--notification-z,1);transform:translateY(var(--notification-stack-y,0));transition:transform .3s cubic-bezier(.22,1,.36,1)}[data-module=notification-w][data-notification-stack-ready]:is(:hover,[data-notification-expanded=true])>[data-module=notification]{transform:translateY(0)}
.dock-icon.ssscript{overflow:hidden;background-color:transparent;background-image:none}.dock-icon.ssscript>img{display:block;width:100%;height:100%;max-width:none;object-fit:cover;border-radius:inherit} .dock-icon.ssscript{overflow:hidden;background-color:transparent;background-image:none}.dock-icon.ssscript>img{display:block;width:100%;height:100%;max-width:none;object-fit:cover;border-radius:inherit}
[data-module=dock]{overflow:visible}[data-module=dock] .dock-icon{position:relative;overflow:visible}[data-module=dock] .dock-icon.ssscript{overflow:visible}[data-module=dock] .dock-icon::after{content:attr(data-dock-title);position:absolute;z-index:30;left:50%;bottom:calc(100% + .64rem);max-width:14rem;transform:translate(-50%,.28rem) scale(.96);transform-origin:50% 100%;opacity:0;pointer-events:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border:1px solid rgba(255,255,255,.12);border-radius:.72rem;background:linear-gradient(180deg,rgba(255,255,255,.07),rgba(255,255,255,.018)),rgba(34,34,38,.86);box-shadow:inset 0 1px 0 rgba(255,255,255,.08),0 20px 60px rgba(0,0,0,.42);-webkit-backdrop-filter:blur(22px) saturate(1.15);backdrop-filter:blur(22px) saturate(1.15);color:rgba(255,255,255,.92);padding:.42rem .68rem;font-size:.68rem;font-weight:760;line-height:1;letter-spacing:0;transition:opacity .16s ease,transform .16s ease}[data-module=dock] .dock-icon:is(:hover,:focus-visible){z-index:20}[data-module=dock] .dock-icon:is(:hover,:focus-visible)::after{opacity:1;transform:translate(-50%,0) scale(1)}[data-module=dock] .dock-icon[data-dock-title=""]::after{display:none}

View File

@ -5,3 +5,4 @@
.pricing-table-section .pricing-table-shell{padding-top:12svh;padding-bottom:12svh}@media screen and (max-width:767px){.pricing-table-section .pricing-table-shell{padding-top:8svh;padding-bottom:8svh}} .pricing-table-section .pricing-table-shell{padding-top:12svh;padding-bottom:12svh}@media screen and (max-width:767px){.pricing-table-section .pricing-table-shell{padding-top:8svh;padding-bottom:8svh}}
[data-module=notification-w][data-notification-stack-ready]{height:auto;max-height:calc(100svh - 3.2rem);overflow:visible;scrollbar-width:none;overscroll-behavior:contain}[data-module=notification-w][data-notification-stack-ready]::-webkit-scrollbar{display:none}[data-module=notification-w][data-notification-stack-ready]:not(:hover):not([data-notification-expanded=true]){height:var(--notification-collapsed-height,6.6rem);max-height:var(--notification-collapsed-height,6.6rem)}[data-module=notification-w][data-notification-stack-ready]:is(:hover,[data-notification-expanded=true]){overflow-x:visible;overflow-y:auto;padding-bottom:.2rem}[data-module=notification-w][data-notification-stack-ready]>[data-module=notification]{position:relative;z-index:var(--notification-z,1);transform:translateY(var(--notification-stack-y,0));transition:transform .3s cubic-bezier(.22,1,.36,1)}[data-module=notification-w][data-notification-stack-ready]:is(:hover,[data-notification-expanded=true])>[data-module=notification]{transform:translateY(0)} [data-module=notification-w][data-notification-stack-ready]{height:auto;max-height:calc(100svh - 3.2rem);overflow:visible;scrollbar-width:none;overscroll-behavior:contain}[data-module=notification-w][data-notification-stack-ready]::-webkit-scrollbar{display:none}[data-module=notification-w][data-notification-stack-ready]:not(:hover):not([data-notification-expanded=true]){height:var(--notification-collapsed-height,6.6rem);max-height:var(--notification-collapsed-height,6.6rem)}[data-module=notification-w][data-notification-stack-ready]:is(:hover,[data-notification-expanded=true]){overflow-x:visible;overflow-y:auto;padding-bottom:.2rem}[data-module=notification-w][data-notification-stack-ready]>[data-module=notification]{position:relative;z-index:var(--notification-z,1);transform:translateY(var(--notification-stack-y,0));transition:transform .3s cubic-bezier(.22,1,.36,1)}[data-module=notification-w][data-notification-stack-ready]:is(:hover,[data-notification-expanded=true])>[data-module=notification]{transform:translateY(0)}
.dock-icon.ssscript{overflow:hidden;background-color:transparent;background-image:none}.dock-icon.ssscript>img{display:block;width:100%;height:100%;max-width:none;object-fit:cover;border-radius:inherit} .dock-icon.ssscript{overflow:hidden;background-color:transparent;background-image:none}.dock-icon.ssscript>img{display:block;width:100%;height:100%;max-width:none;object-fit:cover;border-radius:inherit}
[data-module=dock]{overflow:visible}[data-module=dock] .dock-icon{position:relative;overflow:visible}[data-module=dock] .dock-icon.ssscript{overflow:visible}[data-module=dock] .dock-icon::after{content:attr(data-dock-title);position:absolute;z-index:30;left:50%;bottom:calc(100% + .64rem);max-width:14rem;transform:translate(-50%,.28rem) scale(.96);transform-origin:50% 100%;opacity:0;pointer-events:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border:1px solid rgba(255,255,255,.12);border-radius:.72rem;background:linear-gradient(180deg,rgba(255,255,255,.07),rgba(255,255,255,.018)),rgba(34,34,38,.86);box-shadow:inset 0 1px 0 rgba(255,255,255,.08),0 20px 60px rgba(0,0,0,.42);-webkit-backdrop-filter:blur(22px) saturate(1.15);backdrop-filter:blur(22px) saturate(1.15);color:rgba(255,255,255,.92);padding:.42rem .68rem;font-size:.68rem;font-weight:760;line-height:1;letter-spacing:0;transition:opacity .16s ease,transform .16s ease}[data-module=dock] .dock-icon:is(:hover,:focus-visible){z-index:20}[data-module=dock] .dock-icon:is(:hover,:focus-visible)::after{opacity:1;transform:translate(-50%,0) scale(1)}[data-module=dock] .dock-icon[data-dock-title=""]::after{display:none}

View File

@ -372,7 +372,15 @@
const hasAppTarget = Boolean(targetId && document.getElementById(targetId)); const hasAppTarget = Boolean(targetId && document.getElementById(targetId));
const isInertHref = !href || href === "#" || href === "#hero" || href.toLowerCase().startsWith("javascript:"); const isInertHref = !href || href === "#" || href === "#hero" || href.toLowerCase().startsWith("javascript:");
if (!hasAppTarget && !isInertHref) return; if (!isInertHref) {
event.preventDefault();
event.stopImmediatePropagation();
link.setAttribute("target", "_blank");
link.setAttribute("rel", "noopener noreferrer");
window.open(href, "_blank", "noopener,noreferrer");
window.requestAnimationFrame(() => link.blur());
return;
}
event.preventDefault(); event.preventDefault();
window.requestAnimationFrame(() => link.blur()); window.requestAnimationFrame(() => link.blur());

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,13 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>
.icon-fav-01 { fill: #333334; } /* Тёмный цвет для светлого таб-бара */
@media (prefers-color-scheme: dark) {
.icon-fav-01 { fill: #FEFEFE; } /* Светлый цвет для тёмного таб-бара */
}
@media (prefers-color-scheme: light) {
.icon-fav-01 { fill: #000000; } /* Тёмный цвет для светлого таб-бара (для явности) */
}
</style>
<path class="icon-fav-01" d="M17.8738 15.9159L16.0002 18.9718L14.1267 15.9159H17.8738ZM23.6307 12.7864H8.36914L15.9999 25.2308L23.6307 12.7864Z"/>
<path class="icon-fav-01" d="M10.9793 19.4872L6.67269 11.5918H25.5344L21.2281 19.4872H25.0581L31.2614 8H0.738281L7.16244 19.4872H10.9793Z"/>
</svg>

After

Width:  |  Height:  |  Size: 852 B

View File

@ -0,0 +1,29 @@
{
"theme_color": "#eeeff4",
"background_color": "#eeeff4",
"display": "browser",
"scope": "/",
"start_url": "/",
"icons": [
{
"src": "icon-adaptive.svg",
"type": "image/svg+xml",
"sizes": "any"
},
{
"src": "icon-192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "icon-512.png",
"type": "image/png",
"sizes": "512x512"
},
{
"src": "apple-touch-icon.png",
"type": "image/png",
"sizes": "180x180"
}
]
}

View File

@ -150,7 +150,13 @@
"appIconSrc": "./assets/uploads/images/Logo/dclogo_white_alpha-mqus5if2.png", "appIconSrc": "./assets/uploads/images/Logo/dclogo_white_alpha-mqus5if2.png",
"appIconAlt": "Иконка NODE.DC", "appIconAlt": "Иконка NODE.DC",
"appWordmarkSrc": "./assets/webflow/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc", "appWordmarkSrc": "./assets/webflow/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc",
"appWordmarkAlt": "Логотип 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": [ "editableFields": [
@ -382,6 +388,48 @@
"kind": "text", "kind": "text",
"renderAs": "text", "renderAs": "text",
"group": "Модель и логотипы" "group": "Модель и логотипы"
},
{
"path": "content.assets.faviconSvg",
"label": "Favicon SVG",
"kind": "media",
"renderAs": "attr",
"group": "Модель и логотипы"
},
{
"path": "content.assets.faviconIco",
"label": "Favicon ICO",
"kind": "media",
"renderAs": "attr",
"group": "Модель и логотипы"
},
{
"path": "content.assets.faviconAppleTouchIcon",
"label": "Favicon Apple 180",
"kind": "media",
"renderAs": "attr",
"group": "Модель и логотипы"
},
{
"path": "content.assets.faviconPng192",
"label": "Favicon PNG 192",
"kind": "media",
"renderAs": "attr",
"group": "Модель и логотипы"
},
{
"path": "content.assets.faviconPng512",
"label": "Favicon PNG 512",
"kind": "media",
"renderAs": "attr",
"group": "Модель и логотипы"
},
{
"path": "content.assets.faviconManifest",
"label": "Favicon manifest",
"kind": "media",
"renderAs": "attr",
"group": "Модель и логотипы"
} }
] ]
}, },

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -66,6 +66,8 @@ const MEDIA_EXTENSIONS = new Set([
".gif", ".gif",
".glb", ".glb",
".gltf", ".gltf",
".ico",
".json",
".jpeg", ".jpeg",
".jpg", ".jpg",
".m4v", ".m4v",
@ -73,6 +75,7 @@ const MEDIA_EXTENSIONS = new Set([
".mp4", ".mp4",
".png", ".png",
".svg", ".svg",
".webmanifest",
".webm", ".webm",
".webp", ".webp",
".woff", ".woff",
@ -91,6 +94,7 @@ const REFERENCE_TEXT_EXTENSIONS = new Set([
".ts", ".ts",
".tsx", ".tsx",
".txt", ".txt",
".webmanifest",
".xml", ".xml",
]); ]);
@ -100,6 +104,8 @@ const MIME = {
".js": "text/javascript; charset=utf-8", ".js": "text/javascript; charset=utf-8",
".json": "application/json; charset=utf-8", ".json": "application/json; charset=utf-8",
".svg": "image/svg+xml", ".svg": "image/svg+xml",
".ico": "image/x-icon",
".webmanifest": "application/manifest+json; charset=utf-8",
".png": "image/png", ".png": "image/png",
".jpg": "image/jpeg", ".jpg": "image/jpeg",
".jpeg": "image/jpeg", ".jpeg": "image/jpeg",
@ -150,6 +156,15 @@ function sendStream(req, res, status, stream, headers) {
stream.pipe(res); 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) { function sendJson(res, status, payload) {
send(res, status, `${JSON.stringify(payload, null, 2)}\n`, "application/json; charset=utf-8"); send(res, status, `${JSON.stringify(payload, null, 2)}\n`, "application/json; charset=utf-8");
} }
@ -225,6 +240,8 @@ function sanitizeUploadBucket(value) {
function extensionFromMimeType(mimeType) { function extensionFromMimeType(mimeType) {
const map = { const map = {
"image/svg+xml": ".svg", "image/svg+xml": ".svg",
"image/x-icon": ".ico",
"image/vnd.microsoft.icon": ".ico",
"image/png": ".png", "image/png": ".png",
"image/jpeg": ".jpg", "image/jpeg": ".jpg",
"image/gif": ".gif", "image/gif": ".gif",
@ -233,6 +250,8 @@ function extensionFromMimeType(mimeType) {
"video/mp4": ".mp4", "video/mp4": ".mp4",
"video/webm": ".webm", "video/webm": ".webm",
"video/quicktime": ".mov", "video/quicktime": ".mov",
"application/manifest+json": ".webmanifest",
"application/json": ".json",
"model/gltf-binary": ".glb", "model/gltf-binary": ".glb",
"model/gltf+json": ".gltf", "model/gltf+json": ".gltf",
"font/woff": ".woff", "font/woff": ".woff",
@ -503,7 +522,7 @@ function serializedAssetReplacements(replacements) {
} }
function mediaKindFromExtension(extension) { 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 ([".m4v", ".mov", ".mp4", ".webm"].includes(extension)) return "video";
if ([".glb", ".gltf"].includes(extension)) return "model"; if ([".glb", ".gltf"].includes(extension)) return "model";
if ([".woff", ".woff2"].includes(extension)) return "font"; if ([".woff", ".woff2"].includes(extension)) return "font";
@ -1383,8 +1402,7 @@ async function serveStatic(req, res) {
return; return;
} }
const ext = extname(filePath); const contentType = contentTypeForPath(filePath);
const contentType = MIME[ext] || "application/octet-stream";
const range = req.headers.range; const range = req.headers.range;
if (range) { if (range) {

View File

@ -720,6 +720,12 @@ const staticElements = {
appIconAlt: "Иконка NODE.DC", appIconAlt: "Иконка NODE.DC",
appWordmarkSrc: "./assets/webflow/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc", appWordmarkSrc: "./assets/webflow/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc",
appWordmarkAlt: "Логотип 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: [ editableFields: [
@ -753,6 +759,12 @@ const staticElements = {
field("content.assets.appIconAlt", "Иконка приложения: alt"), field("content.assets.appIconAlt", "Иконка приложения: alt"),
field("content.assets.appWordmarkSrc", "Wordmark приложения", "media", "attr"), field("content.assets.appWordmarkSrc", "Wordmark приложения", "media", "attr"),
field("content.assets.appWordmarkAlt", "Wordmark приложения: alt"), 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; const { navigation, notifications, dock } = definition.content;
let tokenized = source; 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 = replaceShellLiteral(
tokenized, tokenized,

View File

@ -78,6 +78,11 @@ function isExternalHref(value) {
return /^(https?:)?\/\//i.test(String(value || "")) || /^(mailto:|tel:)/i.test(String(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) { function normalizeLinkForRender(item) {
if (!item || typeof item !== "object") return; if (!item || typeof item !== "object") return;
@ -103,12 +108,28 @@ function normalizeLinkForRender(item) {
item.resolvedTarget = mode === "href" ? linkTarget : ""; 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) { function applyRenderableLinks(model) {
const staticLinks = model.staticElements?.content?.navigation?.links; const staticLinks = model.staticElements?.content?.navigation?.links;
if (Array.isArray(staticLinks)) { if (Array.isArray(staticLinks)) {
staticLinks.forEach(normalizeLinkForRender); staticLinks.forEach(normalizeLinkForRender);
} }
const dockItems = model.staticElements?.content?.dock?.items;
if (Array.isArray(dockItems)) {
dockItems.forEach(normalizeDockItemForRender);
}
for (const block of allBlocks(model)) { for (const block of allBlocks(model)) {
const footer = block.content?.footer; const footer = block.content?.footer;
if (!footer) continue; if (!footer) continue;