5 次代码提交
修改 11 个文件,包含 307 行新增33 行删除
+2 -2
查看文件
@@ -78,14 +78,14 @@ spec:
position: up
label: 分类
icon: script-text
url: /categories
url: ""
external: false
- enabled: true
type: tags
position: up
label: 标签
icon: label
url: /tags
url: ""
external: false
- enabled: true
type: login
+2
查看文件
@@ -3,6 +3,7 @@ import type { Alpine } from "alpinejs";
import { headerMenu } from "./header-menu";
import { postLineNum } from "./post-line-num";
import { postToc } from "./post-toc";
import { initDevcmPromptLoading } from "./prompt-loading";
import { themeComment } from "./theme-comment";
import { themeMode } from "./theme-mode";
import { upvote } from "./upvote";
@@ -20,6 +21,7 @@ export const bootDevcmAlpine = (Alpine: Alpine) => {
Alpine.data("themeComment", themeComment);
initDevcmActivityActions();
initDevcmPromptLoading();
initTypedText();
Alpine.start();
+181
查看文件
@@ -0,0 +1,181 @@
const PROMPT_LINE_SELECTOR = ".devcm-prompt-stream__line.dividing";
const LOADING_CLASS = "devcm-prompt-stream__line--loading";
const FINISHING_CLASS = "devcm-prompt-stream__line--finishing";
const STORAGE_KEY = "devcm:prompt-loading";
const ROUTE_PENDING_VALUE = "route";
const FINISH_ANIMATION_NAMES = ["devcm-route-tail-shrink", "devcm-route-finish-state"];
let removeFinishListener: (() => void) | null = null;
const getPromptLine = () => document.querySelector<HTMLElement>(PROMPT_LINE_SELECTOR);
const clearFinishListener = () => {
removeFinishListener?.();
removeFinishListener = null;
};
const setPendingRoute = () => {
try {
window.sessionStorage.setItem(STORAGE_KEY, ROUTE_PENDING_VALUE);
} catch {
// Ignore storage failures in private browsing or restricted environments.
}
};
const consumePendingRoute = () => {
try {
const isPending = window.sessionStorage.getItem(STORAGE_KEY) === ROUTE_PENDING_VALUE;
window.sessionStorage.removeItem(STORAGE_KEY);
return isPending;
} catch {
return false;
}
};
const resetPromptLoading = () => {
const line = getPromptLine();
clearFinishListener();
if (!line) {
return;
}
line.classList.remove(LOADING_CLASS, FINISHING_CLASS);
delete line.dataset.devcmLoading;
};
const startPromptLoading = () => {
const line = getPromptLine();
clearFinishListener();
if (!line) {
return;
}
line.classList.remove(FINISHING_CLASS);
line.classList.add(LOADING_CLASS);
line.dataset.devcmLoading = "true";
};
const startPromptFinishing = () => {
const line = getPromptLine();
clearFinishListener();
if (!line) {
return;
}
const handleAnimationEnd = (event: AnimationEvent) => {
if (FINISH_ANIMATION_NAMES.includes(event.animationName)) {
resetPromptLoading();
}
};
line.classList.remove(LOADING_CLASS);
line.classList.add(FINISHING_CLASS);
line.dataset.devcmLoading = "done";
line.addEventListener("animationend", handleAnimationEnd);
removeFinishListener = () => line.removeEventListener("animationend", handleAnimationEnd);
};
const finishWhenReady = () => {
if (document.readyState === "complete") {
startPromptFinishing();
return;
}
window.addEventListener("load", startPromptFinishing, { once: true });
};
const isReloadNavigation = () => {
if (typeof performance === "undefined" || typeof performance.getEntriesByType !== "function") {
return false;
}
const [navigationEntry] = performance.getEntriesByType("navigation") as PerformanceNavigationTiming[];
return navigationEntry?.type === "reload";
};
const resumeInitialPromptLoading = () => {
if (consumePendingRoute()) {
startPromptFinishing();
return;
}
if (!isReloadNavigation()) {
return;
}
startPromptLoading();
finishWhenReady();
};
const hasModifiedClick = (event: MouseEvent) => event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
const isNavigableAnchor = (anchor: HTMLAnchorElement, event: MouseEvent) => {
if (event.defaultPrevented || event.button !== 0 || hasModifiedClick(event)) {
return false;
}
if (anchor.hasAttribute("download")) {
return false;
}
const target = anchor.getAttribute("target");
if (target && target.toLowerCase() !== "_self") {
return false;
}
const rawHref = anchor.getAttribute("href");
if (!rawHref || rawHref === "#" || rawHref.startsWith("#")) {
return false;
}
const url = new URL(anchor.href, window.location.href);
if (url.origin !== window.location.origin) {
return false;
}
if (url.protocol !== "http:" && url.protocol !== "https:") {
return false;
}
const current = new URL(window.location.href);
return url.pathname !== current.pathname || url.search !== current.search;
};
export const initDevcmPromptLoading = () => {
resumeInitialPromptLoading();
document.addEventListener("click", (event) => {
const target = event.target;
if (!(event instanceof MouseEvent) || !(target instanceof Element)) {
return;
}
const anchor = target.closest<HTMLAnchorElement>("a[href]");
if (!anchor || !isNavigableAnchor(anchor, event)) {
return;
}
setPendingRoute();
startPromptLoading();
});
window.addEventListener("beforeunload", startPromptLoading);
window.addEventListener("pagehide", (event) => {
if (!event.persisted) {
startPromptLoading();
}
});
window.addEventListener("pageshow", (event) => {
if (event.persisted) {
resetPromptLoading();
}
});
};
+11 -11
查看文件
@@ -10,7 +10,7 @@ body.devcm-body {
min-height: 0;
background: radial-gradient(circle at 46% 0%, rgba(104, 243, 127, 0.11), transparent 28%),
radial-gradient(circle at 85% 18%, rgba(255, 176, 0, 0.1), transparent 28%),
linear-gradient(180deg, #090b09 0%, #050706 100%);
linear-gradient(180deg, #0f130f 0%, #090c09 100%);
color: var(--devcm-text);
font-family: var(--devcm-mono);
overflow: hidden;
@@ -58,18 +58,18 @@ body.devcm-body::after {
}
body.devcm-body::before {
opacity: 0.16;
opacity: 0.24;
background: repeating-linear-gradient(
0deg,
rgba(255, 255, 255, 0.035) 0,
rgba(255, 255, 255, 0.035) 1px,
rgba(255, 255, 255, 0.055) 0,
rgba(255, 255, 255, 0.055) 1px,
transparent 1px,
transparent 3px
),
repeating-linear-gradient(
90deg,
rgba(255, 176, 0, 0.025) 0,
rgba(255, 176, 0, 0.025) 1px,
rgba(255, 176, 0, 0.045) 0,
rgba(255, 176, 0, 0.045) 1px,
transparent 1px,
transparent 28px
);
@@ -77,8 +77,8 @@ body.devcm-body::before {
}
body.devcm-body::after {
background: radial-gradient(ellipse at center, transparent 48%, rgba(0, 0, 0, 0.52) 100%);
opacity: 0.62;
background: radial-gradient(ellipse at center, transparent 55%, rgba(0, 0, 0, 0.38) 100%);
opacity: 0.46;
}
.devcm-shell {
@@ -105,9 +105,9 @@ html[data-color-scheme="light"] .devcm-shell {
min-height: 0;
grid-template-rows: auto minmax(0, 1fr) auto;
overflow: hidden;
background: linear-gradient(90deg, rgba(255, 176, 0, 0.04) 1px, transparent 1px) 0 0 / 28px 28px,
linear-gradient(180deg, rgba(255, 176, 0, 0.032) 1px, transparent 1px) 0 0 / 28px 28px,
linear-gradient(180deg, rgba(8, 10, 8, 0.98), rgba(6, 8, 6, 0.98));
background: linear-gradient(90deg, rgba(255, 176, 0, 0.064) 1px, transparent 1px) 0 0 / 28px 28px,
linear-gradient(180deg, rgba(104, 243, 127, 0.04) 1px, transparent 1px) 0 0 / 28px 28px,
linear-gradient(180deg, rgba(13, 17, 13, 0.96), rgba(10, 13, 10, 0.96));
}
html[data-color-scheme="light"] .devcm-app {
+9 -7
查看文件
@@ -1,11 +1,11 @@
:root,
html[data-color-scheme="dark"] {
color-scheme: dark;
--devcm-bg: #070908;
--devcm-bg-deep: #040504;
--devcm-panel: rgba(12, 14, 11, 0.94);
--devcm-panel-soft: rgba(18, 20, 14, 0.88);
--devcm-panel-warm: rgba(22, 18, 10, 0.74);
--devcm-bg: #0e120e;
--devcm-bg-deep: #090c09;
--devcm-panel: rgba(18, 22, 16, 0.94);
--devcm-panel-soft: rgba(24, 28, 19, 0.88);
--devcm-panel-warm: rgba(31, 25, 14, 0.76);
--devcm-line: rgba(255, 176, 0, 0.22);
--devcm-line-strong: rgba(255, 176, 0, 0.48);
--devcm-text: #ddd6b9;
@@ -31,6 +31,7 @@ html[data-color-scheme="dark"] {
--background: var(--devcm-bg);
--foreground: var(--devcm-text);
--selectionBackground: rgba(104, 243, 127, 0.24);
--selectionText: #fff6d4;
--blue: var(--devcm-cyan);
--green: var(--devcm-green);
--red: var(--devcm-red);
@@ -65,6 +66,7 @@ html[data-color-scheme="light"] {
--background: var(--devcm-bg);
--foreground: var(--devcm-text);
--selectionBackground: rgba(42, 161, 152, 0.18);
--selectionText: #073642;
--blue: var(--devcm-cyan);
--green: var(--devcm-green);
--red: var(--devcm-red);
@@ -75,6 +77,6 @@ html[data-color-scheme="light"] {
}
::selection {
background: rgba(104, 243, 127, 0.26);
color: #fff6d4;
background: var(--selectionBackground);
color: var(--selectionText);
}
+85
查看文件
@@ -53,6 +53,55 @@
z-index: 10;
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
&.devcm-prompt-stream__line--loading {
opacity: 1;
&::after {
left: 0;
width: 100%;
opacity: 0.78;
filter: blur(3px);
background: linear-gradient(
90deg,
rgba(255, 93, 93, 0.04) 0%,
rgba(255, 93, 93, 0.28) 52%,
rgba(255, 93, 93, 0.46) 78%,
rgba(255, 93, 93, 0.72) 100%
);
animation-name: devcm-route-fill-grow, devcm-route-loading-pulse;
animation-duration: 1.28s, 0.72s;
animation-timing-function: cubic-bezier(0.18, 0.72, 0.22, 1), ease-in-out;
animation-fill-mode: forwards, none;
animation-iteration-count: 1, infinite;
animation-direction: normal, alternate;
transform: translateY(-50%) scaleX(0);
transform-origin: left center;
transition: opacity 0.18s ease;
will-change: transform, opacity, filter;
}
}
&.devcm-prompt-stream__line--finishing {
opacity: 1;
animation: devcm-route-finish-state 0.86s linear forwards;
&::after {
left: 0;
width: 100%;
opacity: 0.66;
filter: blur(3px);
clip-path: inset(0 0 0 0);
background: linear-gradient(90deg, rgba(255, 93, 93, 0.05) 0%, rgba(255, 93, 93, 0.62) 70%, transparent 100%);
animation-name: devcm-route-tail-shrink;
animation-duration: 0.86s;
animation-timing-function: cubic-bezier(0.48, 0, 0.22, 1);
animation-fill-mode: forwards;
transform: translateY(-50%);
transition: opacity 0.18s ease;
will-change: clip-path, opacity, filter;
}
}
}
// 虚线流动动画 - 模拟加载进度
@@ -84,3 +133,39 @@
opacity: 0;
}
}
@keyframes devcm-route-loading-pulse {
0% {
opacity: 0.58;
}
100% {
opacity: 0.78;
}
}
@keyframes devcm-route-fill-grow {
0% {
transform: translateY(-50%) scaleX(0);
}
100% {
transform: translateY(-50%) scaleX(1);
}
}
@keyframes devcm-route-tail-shrink {
0% {
clip-path: inset(0 0 0 0);
opacity: 0.66;
}
100% {
clip-path: inset(0 0 0 100%);
opacity: 0;
}
}
@keyframes devcm-route-finish-state {
0%,
100% {
opacity: 1;
}
}
+2 -1
查看文件
@@ -10,7 +10,8 @@
<span th:text="${category.spec.displayName} + '.md'">category.md</span>
<a
class="devcm-editor-tab__close"
href="/categories"
href="#"
th:href="${!#strings.isEmpty(site.routes.categoriesUri) ? site.routes.categoriesUri : '/categories'}"
th:aria-label="#{devcm.action.closeCategoryTab}"
th:title="#{devcm.action.closeCategoryTab}"
>×</a
+11 -7
查看文件
@@ -16,10 +16,12 @@
<th:block
th:with="items=${theme.config.activity.activity_items},
primaryMenu=${menuFinder.getPrimary()},
archiveMenuItem=${primaryMenu != null and primaryMenu.menuItems != null and !#lists.isEmpty(primaryMenu.menuItems) ? primaryMenu.menuItems.^[status != null and !#strings.isEmpty(status.href) and ((spec != null and spec.href == '/archives') or status.href == '/archives' or status.displayName == 'Archives' or status.displayName == '归档')] : null},
archiveActivityItem=${items != null and !#lists.isEmpty(items) ? items.^[type == 'archive' or key == 'archive'] : null},
activityArchiveHref=${archiveMenuItem != null and archiveMenuItem.status != null and !#strings.isEmpty(archiveMenuItem.status.href) ? archiveMenuItem.status.href : (archiveActivityItem != null and !#strings.isEmpty(archiveActivityItem.url) ? archiveActivityItem.url : (archiveActivityItem != null and !#strings.isEmpty(archiveActivityItem.href) ? archiveActivityItem.href : '/archives'))}"
categoriesActivityItem=${items != null and !#lists.isEmpty(items) ? items.^[type == 'categories' or key == 'categories'] : null},
tagsActivityItem=${items != null and !#lists.isEmpty(items) ? items.^[type == 'tags' or key == 'tags'] : null},
activityArchiveHref=${!#strings.isEmpty(site.routes.archivesUri) ? site.routes.archivesUri : (archiveActivityItem != null and !#strings.isEmpty(archiveActivityItem.url) ? archiveActivityItem.url : (archiveActivityItem != null and !#strings.isEmpty(archiveActivityItem.href) ? archiveActivityItem.href : '/archives'))},
activityCategoriesHref=${!#strings.isEmpty(site.routes.categoriesUri) ? site.routes.categoriesUri : (categoriesActivityItem != null and !#strings.isEmpty(categoriesActivityItem.url) ? categoriesActivityItem.url : (categoriesActivityItem != null and !#strings.isEmpty(categoriesActivityItem.href) ? categoriesActivityItem.href : '/categories'))},
activityTagsHref=${!#strings.isEmpty(site.routes.tagsUri) ? site.routes.tagsUri : (tagsActivityItem != null and !#strings.isEmpty(tagsActivityItem.url) ? tagsActivityItem.url : (tagsActivityItem != null and !#strings.isEmpty(tagsActivityItem.href) ? tagsActivityItem.href : '/tags'))}"
>
<div class="devcm-activity__group devcm-activity__group--up">
<th:block th:if="${items != null and !#lists.isEmpty(items)}">
@@ -29,7 +31,7 @@
itemType=${!#strings.isEmpty(item.type) ? item.type : (!#strings.isEmpty(item.key) ? item.key : 'custom')},
entryAction=${itemType == 'search' or item.action == 'search' ? 'search' : 'link'},
entryConfiguredHref=${!#strings.isEmpty(item.url) ? item.url : (!#strings.isEmpty(item.href) ? item.href : '#')},
entryHref=${entryAction == 'search' ? '#' : (itemType == 'archive' ? activityArchiveHref : entryConfiguredHref)},
entryHref=${entryAction == 'search' ? '#' : (itemType == 'archive' ? activityArchiveHref : (itemType == 'categories' ? activityCategoriesHref : (itemType == 'tags' ? activityTagsHref : entryConfiguredHref)))},
entryLabel=${!#strings.isEmpty(item.label) ? item.label : (!#strings.isEmpty(item.labelKey) ? #messages.msg(item.labelKey) : #messages.msg('devcm.activity.link'))}"
>
<a
@@ -112,7 +114,8 @@
<a
class="devcm-activity__button"
th:classappend="${pageType == 'categories' or pageType == 'category'} ? ' devcm-activity__button--active' : ''"
href="/categories"
href="#"
th:href="${activityCategoriesHref}"
th:title="#{devcm.activity.categories}"
th:attr="data-label=#{devcm.activity.categories},aria-label=#{devcm.activity.categories}"
>
@@ -121,7 +124,8 @@
<a
class="devcm-activity__button"
th:classappend="${pageType == 'tags' or pageType == 'tag'} ? ' devcm-activity__button--active' : ''"
href="/tags"
href="#"
th:href="${activityTagsHref}"
th:title="#{devcm.activity.tags}"
th:attr="data-label=#{devcm.activity.tags},aria-label=#{devcm.activity.tags}"
>
@@ -138,7 +142,7 @@
itemType=${!#strings.isEmpty(item.type) ? item.type : (!#strings.isEmpty(item.key) ? item.key : 'custom')},
entryAction=${itemType == 'search' or item.action == 'search' ? 'search' : 'link'},
entryConfiguredHref=${!#strings.isEmpty(item.url) ? item.url : (!#strings.isEmpty(item.href) ? item.href : '#')},
entryHref=${entryAction == 'search' ? '#' : (itemType == 'archive' ? activityArchiveHref : entryConfiguredHref)},
entryHref=${entryAction == 'search' ? '#' : (itemType == 'archive' ? activityArchiveHref : (itemType == 'categories' ? activityCategoriesHref : (itemType == 'tags' ? activityTagsHref : entryConfiguredHref)))},
entryLabel=${!#strings.isEmpty(item.label) ? item.label : (!#strings.isEmpty(item.labelKey) ? #messages.msg(item.labelKey) : #messages.msg('devcm.activity.link'))}"
>
<a
+1 -3
查看文件
@@ -10,10 +10,8 @@
class="devcm-editor-tab devcm-editor-tab--active"
th:with="postTabBaseName=${!#strings.isEmpty(post.spec.slug) ? post.spec.slug : post.metadata.name},
postTabName=${#strings.endsWith(postTabBaseName, '.md') ? postTabBaseName : postTabBaseName + '.md'},
primaryMenu=${menuFinder.getPrimary()},
archiveMenuItem=${primaryMenu != null and primaryMenu.menuItems != null and !#lists.isEmpty(primaryMenu.menuItems) ? primaryMenu.menuItems.^[status != null and !#strings.isEmpty(status.href) and ((spec != null and spec.href == '/archives') or status.href == '/archives' or status.displayName == 'Archives' or status.displayName == '归档')] : null},
archiveActivityItem=${theme.config.activity.activity_items != null and !#lists.isEmpty(theme.config.activity.activity_items) ? theme.config.activity.activity_items.^[type == 'archive' or key == 'archive'] : null},
postCloseHref=${archiveMenuItem != null and archiveMenuItem.status != null and !#strings.isEmpty(archiveMenuItem.status.href) ? archiveMenuItem.status.href : (archiveActivityItem != null and !#strings.isEmpty(archiveActivityItem.url) ? archiveActivityItem.url : (archiveActivityItem != null and !#strings.isEmpty(archiveActivityItem.href) ? archiveActivityItem.href : '/archives'))}"
postCloseHref=${!#strings.isEmpty(site.routes.archivesUri) ? site.routes.archivesUri : (archiveActivityItem != null and !#strings.isEmpty(archiveActivityItem.url) ? archiveActivityItem.url : (archiveActivityItem != null and !#strings.isEmpty(archiveActivityItem.href) ? archiveActivityItem.href : '/archives'))}"
>
<span th:text="${postTabName}">post</span>
<a
+2 -1
查看文件
@@ -10,7 +10,8 @@
<span th:text="'#' + ${tag.spec.displayName} + '.md'">tag.md</span>
<a
class="devcm-editor-tab__close"
href="/tags"
href="#"
th:href="${!#strings.isEmpty(site.routes.tagsUri) ? site.routes.tagsUri : '/tags'}"
th:aria-label="#{devcm.action.closeTagTab}"
th:title="#{devcm.action.closeTagTab}"
>×</a
+1 -1
查看文件
@@ -13,5 +13,5 @@ spec:
repo: https://git.dev.cm/theme-terminal
settingName: "theme-terminal-setting"
configMapName: "theme-terminal-configMap"
version: 2.0.7
version: 2.0.10
require: ">=2.22.0"