generated from LiteLoaderQQNT/Plugin-Template
-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
651 additions
and
484 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
// Description: Utilities related to CSS. | ||
import { log } from "./debug.js"; | ||
/** | ||
* Attribute name for the style element to store the path of the CSS file. | ||
*/ | ||
const styleDataAttr = "data-transitio-style"; | ||
/** | ||
* Get the default value of a select variable, given the arguments. | ||
* @param {Array} varArgs Arguments for the select variable. | ||
* @returns {String} The default value of the select variable. | ||
*/ | ||
function getSelectDefaultValue(varArgs) { | ||
// varArgs: [default-index, option1, option2, ...] | ||
// option: [value, label] or value | ||
const defaultIndex = varArgs[0]; | ||
const defaultOption = varArgs[defaultIndex + 1]; | ||
if (Array.isArray(defaultOption)) { | ||
return defaultOption[0]; | ||
} else { | ||
return defaultOption; | ||
} | ||
} | ||
/** | ||
* Construct the value of a variable, to be applied into the CSS. | ||
* @param {Object} varObj Variable object. | ||
* @returns {String} The value of the variable. | ||
*/ | ||
function constructVarValue(varObj) { | ||
const value = varObj.value ?? varObj.args[0]; | ||
switch (varObj.type) { | ||
case "text": | ||
return `"${CSS.escape(value)}"`; | ||
case "number": | ||
return isNaN(value) ? value : value.toString(); | ||
case "percent": | ||
case "percentage": | ||
return isNaN(value) ? value : `${value}%`; | ||
case "checkbox": | ||
// varObj.args: [default-index/boolean, option1, option2, ...] | ||
// option: value | ||
// varObj.value: default-index/boolean | ||
return value ? varObj.args[2] : varObj.args[1]; | ||
case "select": { | ||
// varObj.args: [default-index, option1, option2, ...] | ||
// option: [value, label] or value | ||
// varObj.value: value | ||
return varObj.value ?? getSelectDefaultValue(varObj.args); | ||
} | ||
default: // color/colour, raw | ||
return value.toString(); | ||
} | ||
} | ||
/** | ||
* Apply variables to the CSS. | ||
* @param {String} css CSS content. | ||
* @param {Object} variables A dictionary of variables. | ||
* @returns {String} The CSS content with variables applied. | ||
*/ | ||
function applyVariables(css, variables) { | ||
// Regular expression to match the variable pattern `var(--name)` | ||
const varRegex = /var\(--([^)]+)\)/g; | ||
return css.replace(varRegex, (match, varName) => { | ||
const varObj = variables[varName]; | ||
if (!varObj) { | ||
return match; | ||
} | ||
return constructVarValue(varObj); | ||
}); | ||
} | ||
/** | ||
* Inject CSS into the document. | ||
* @param {String} path Path of the CSS file. | ||
* @param {String} css CSS content. | ||
* @returns {Element} The style element. | ||
*/ | ||
function injectCSS(path, css) { | ||
const style = document.createElement("style"); | ||
style.setAttribute(styleDataAttr, path); | ||
style.textContent = css; | ||
document.head.appendChild(style); | ||
return style; | ||
} | ||
/** | ||
* Helper function that applies variables to CSS and injects it into the document (or updates an existing style element). | ||
* @param {String} path Path of the CSS file. | ||
* @param {String} css CSS content. | ||
* @param {Boolean} enabled Whether the CSS shall be enabled. | ||
* @param {Object} meta Metadata of the CSS file. | ||
*/ | ||
function cssHelper(path, css, enabled, meta) { | ||
const current = document.querySelector(`style[${styleDataAttr}="${path}"]`); | ||
log("Applying variables to", path, meta.variables) | ||
const processedCSS = enabled ? applyVariables(css, meta.variables) : `/* ${meta.description || "此文件没有描述"} */`; | ||
if (current) { | ||
current.textContent = processedCSS; | ||
} else { | ||
injectCSS(path, processedCSS); | ||
} | ||
} | ||
|
||
export { | ||
styleDataAttr, | ||
getSelectDefaultValue, | ||
// constructVarValue, | ||
// applyVariables, | ||
// injectCSS, | ||
cssHelper | ||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// Description: Debugging utilities for the renderer. | ||
let isDebug = false; | ||
|
||
/** | ||
* Log to console if debug mode is enabled. | ||
* @param {...any} args Arguments to log | ||
*/ | ||
function log(...args) { | ||
if (isDebug) { | ||
console.log("[Transitio]", ...args); | ||
} | ||
} | ||
|
||
transitio.queryIsDebug().then((result) => { | ||
isDebug = result; | ||
}); | ||
|
||
/** | ||
* Show debug hint on settings page. | ||
* @param {Element} view View element | ||
*/ | ||
function showDebugHint(view) { | ||
if (isDebug) { | ||
const debug = view.querySelector("#transitio-debug"); | ||
debug.style.color = "red"; | ||
debug.title = "Debug 模式已激活"; | ||
} | ||
} | ||
|
||
export { log, showDebugHint }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// Description: Easter egg at settings view. | ||
|
||
/** Function to setup the easter egg at the settings view. | ||
* @param {HTMLElement} logo - The logo element. | ||
* @returns {void} | ||
*/ | ||
function setupEasterEgg(logo) { | ||
const title = document.querySelector(".setting-title"); | ||
function lumos() { | ||
document.body.classList.remove("q-theme-tokens-dark"); | ||
document.body.classList.add("q-theme-tokens-light"); | ||
document.body.setAttribute("q-theme", "light"); | ||
title.classList.add("lumos"); | ||
setTimeout(() => { | ||
title.classList.remove("lumos"); | ||
}, 2000); | ||
} | ||
function nox() { | ||
document.body.classList.remove("q-theme-tokens-light"); | ||
document.body.classList.add("q-theme-tokens-dark"); | ||
document.body.setAttribute("q-theme", "dark"); | ||
title.classList.add("nox"); | ||
setTimeout(() => { | ||
title.classList.remove("nox"); | ||
}, 2000); | ||
} | ||
function currentTheme() { | ||
return document.body.getAttribute("q-theme"); | ||
} | ||
logo.addEventListener("animationend", () => { | ||
document.startViewTransition(() => { | ||
if (currentTheme() == "light") { | ||
nox(); | ||
} else { | ||
lumos(); | ||
} | ||
}); | ||
}); | ||
} | ||
|
||
export { setupEasterEgg }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
// Description: Search module for settings view. | ||
import { log } from "./debug.js"; | ||
|
||
/** Search `keyword` in the `el` and highlight the matched text. | ||
* @param {Highlight} highlight The highlight object. | ||
* @param {HTMLElement} el The element to search. | ||
* @param {string} keyword The keyword to search. | ||
* @returns {boolean} Returns `true` if a match is found. | ||
*/ | ||
function searchAndHighlight(highlight, el, keyword) { | ||
if (!el) return false; | ||
const textContent = el.textContent.toLowerCase(); | ||
let isMatch = false; | ||
let startIndex = 0; | ||
let index; | ||
while ((index = textContent.indexOf(keyword, startIndex)) !== -1) { | ||
const range = new Range(); | ||
range.setStart(el.firstChild, index); | ||
range.setEnd(el.firstChild, index + keyword.length); | ||
highlight.add(range); | ||
isMatch = true; | ||
startIndex = index + keyword.length; | ||
} | ||
return isMatch; | ||
} | ||
/** Search all `keywords` in the `details` and highlight the matched text. | ||
* @param {Highlight} highlight The highlight object. | ||
* @param {HTMLElement} detail The `details` element to search. | ||
* @param {Set<string>} keywords The keywords to search. | ||
* @returns {boolean} Returns `true` if all keywords are found in the `details`. | ||
*/ | ||
function searchAllAndHighlight(highlight, detail, keywords) { | ||
const settingItem = detail.querySelector("summary > setting-item"); | ||
const nameEl = settingItem.querySelector("setting-text"); | ||
const descEl = settingItem.querySelector("setting-text[data-type='secondary']"); | ||
let matches = 0; | ||
for (const keyword of keywords) { | ||
const nameMatch = searchAndHighlight(highlight, nameEl, keyword); | ||
const descMatch = searchAndHighlight(highlight, descEl, keyword); | ||
if (nameMatch || descMatch) { | ||
matches++; | ||
} | ||
} | ||
return matches === keywords.size; | ||
} | ||
/** Perform search and hide the `details` that doesn't match the search. | ||
* @param {Highlight} highlight The highlight object. | ||
* @param {string} text The search text. | ||
* @param {HTMLElement} container The container element. | ||
* @returns {void} | ||
*/ | ||
function doSearch(highlight, text, container) { // Main function for searching | ||
log("Search", text); | ||
highlight.clear(); // Clear previous highlights | ||
const items = container.querySelectorAll("details"); | ||
const searchWords = new Set( // Use Set to remove duplicates | ||
text.toLowerCase() // Convert to lowercase | ||
.split(" ") // Split by space | ||
.map(word => word.trim()) // Remove leading and trailing spaces | ||
.filter(word => word.length > 0) // Remove empty strings | ||
); | ||
items.forEach((detail) => { // Iterate through all `details` | ||
const isMatch = searchAllAndHighlight(highlight, detail, searchWords); | ||
detail.toggleAttribute(searchHiddenDataAttr, !isMatch); // Hide the `details` if it doesn't match | ||
}); | ||
} | ||
|
||
/** Setup the search bar for the settings view. | ||
* @param {HTMLElement} view The settings view. | ||
* @returns {void} | ||
*/ | ||
function setupSearch(view) { | ||
const inputTags = ["INPUT", "SELECT", "TEXTAREA"]; | ||
const search = view.querySelector("#transitio-search"); | ||
const container = view.querySelector("setting-section.snippets > setting-panel > setting-list"); | ||
const highlight = new Highlight(); | ||
CSS.highlights.set("transitio-search-highlight", highlight); | ||
document.addEventListener("keydown", (e) => { | ||
if (!view.checkVisibility()) return; // The setting window is not visible | ||
if (document.activeElement === search) { // The search bar is focused | ||
// Escape closes the window | ||
if (e.key === "Enter") { // Search | ||
search.scrollIntoView(); | ||
} | ||
} else if (!inputTags.includes(document.activeElement.tagName)) { // Not focusing on some other input element | ||
// Focus on the search bar when "Enter" or "Ctrl + F" is pressed | ||
if (e.key === "Enter" || (e.ctrlKey && e.key === "f")) { | ||
e.preventDefault(); | ||
search.focus(); | ||
search.scrollIntoView(); | ||
} | ||
} | ||
}); | ||
search.addEventListener("change", () => { doSearch(highlight, search.value, container); }); | ||
} | ||
|
||
export { setupSearch }; |
Oops, something went wrong.