Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ home_banner:
hitokoto: # 一言配置
enable: false # Whether to enable hitokoto
show_author: false # Whether to show author
refresh_on_loop: false # Whether to fetch a new quote on each loop
api: https://v1.hitokoto.cn # API URL, can add types, see https://developer.hitokoto.cn/sentence/#%E5%8F%A5%E5%AD%90%E7%B1%BB%E5%9E%8B-%E5%8F%82%E6%95%B0
typing_speed: 100 # Typing speed (ms)
backing_speed: 80 # Backing speed (ms)
Expand Down
90 changes: 77 additions & 13 deletions source/js/plugins/typed.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const destroyInstance = (id) => {
}
};

const createTyped = (id, strings, options) => {
const createTyped = (id, strings, options, callbacks = {}) => {
if (typeof window.Typed === "undefined") {
return;
}
Expand All @@ -54,6 +54,7 @@ const createTyped = (id, strings, options) => {
backDelay: options.backDelay,
loop: options.loop,
startDelay: options.startDelay,
...callbacks,
});

instances.set(id, instance);
Expand All @@ -62,6 +63,39 @@ const createTyped = (id, strings, options) => {
const subtitleConfig = theme?.home_banner?.subtitle || {};
const hitokotoConfig = subtitleConfig.hitokoto || {};

const getHitokotoText = (data) => {
const quote = typeof data?.hitokoto === "string" ? data.hitokoto : "";
if (!quote) {
return "";
}

const author =
typeof data?.from_who === "string" && hitokotoConfig.show_author
? data.from_who
: "";
return author ? `${quote}——${author}` : quote;
};

const fetchHitokoto = (api) =>
fetch(api)
.then((response) => {
if (!response.ok) {
throw new Error(
`Hitokoto request failed with status ${response.status}`,
);
}

return response.json();
})
.then(getHitokotoText)
.then((text) => {
if (!text) {
throw new Error("Hitokoto response did not contain a quote");
}

return text;
});

export const config = {
usrTypeSpeed: subtitleConfig.typing_speed,
usrBackSpeed: subtitleConfig.backing_speed,
Expand Down Expand Up @@ -102,25 +136,55 @@ export default function initTyped(id) {
return;
}

fetch(usrHitokotoAPI)
.then((response) => response.json())
.then((data) => {
fetchHitokoto(usrHitokotoAPI)
.then((text) => {
if (initTokens.get(id) !== currentToken) {
return;
}

const quote = typeof data?.hitokoto === "string" ? data.hitokoto : "";
if (!quote) {
const shouldRefreshOnLoop = Boolean(
hitokotoConfig.refresh_on_loop && options.loop,
);

if (!shouldRefreshOnLoop) {
createTyped(id, [text], options);
return;
}

const author =
typeof data?.from_who === "string" && hitokotoConfig.show_author
? data.from_who
: "";
const text = author ? `${quote}——${author}` : quote;

createTyped(id, [text], options);
let currentText = text;
let nextText = null;
let refreshRequest = null;

const prefetchNextText = () => {
if (refreshRequest || nextText) {
return;
}

refreshRequest = fetchHitokoto(usrHitokotoAPI)
.then((nextQuote) => {
if (initTokens.get(id) === currentToken) {
nextText = nextQuote;
}
})
.catch((error) => {
console.error("Failed to refresh hitokoto:", error);
})
.finally(() => {
refreshRequest = null;
});
};

createTyped(id, [currentText], options, {
onStringTyped: prefetchNextText,
onLastStringBackspaced: (instance) => {
if (nextText) {
currentText = nextText;
nextText = null;
}

instance.strings[0] = currentText;
},
});
})
.catch((error) => {
console.error("Failed to fetch hitokoto:", error);
Expand Down
Loading