Files
FixIt/assets/js/utils/common.js
T
Cell 164a699701 refactor(UI/UX)!: refactor styles for theme support and enhance visual consistency (#722)
* refactor(styles): unify theme properties and update color variables across stylesheets

* Implement theme mode support with light-dark color adjustments across stylesheets

- Added color-scheme support in _root.scss for light and dark modes.
- Replaced theme property mixins with light-dark function in various widget styles including _cell-tooltip.scss, _cookieconsent.scss, _fixed-button.scss, and others.
- Updated alert styles in _alert.scss to use light-dark for border and text colors.
- Refactored code syntax highlighting in _code.scss and _code-syntax/_index.scss to utilize light-dark for background gradients.
- Enhanced search input styles in _search.scss to adapt to theme mode.
- Modified theme switching logic in color-scheme.js and theme.js to support new theme mode structure.
- Adjusted APlayer and Valine styles to accommodate dark mode using new theme variables.

* feat(shortcode): add auto-dark shortcode for dark mode adaptation

* refactor(styles): update theme colors and improve box-shadow properties for better visual consistency

* feat(header): add scroll-state container queries and enhance header shadow effects

* feat(styles): enhance scroll behavior with overscroll-behavior adjustments for better UX

* feat(styles): update code&table styles and colors for improved visual consistency

* feat(styles): update color variables and improve font styling for better visual hierarchy
2026-03-30 16:48:55 +08:00

186 lines
5.3 KiB
JavaScript

/**
* Iterate over an array-like collection.
* If any handler call returns a Promise, all Promises are collected and returned.
* @param {ArrayLike<*>|Array<*>} elements collection to iterate
* @param {Function} handler callback for each item
* @returns {Promise<Array<*>>} resolved results for async handlers
*/
export function forEach(elements, handler) {
elements = elements || [];
const promises = [];
for (let i = 0; i < elements.length; i++) {
const result = handler(elements[i], i);
if (result instanceof Promise) {
promises.push(result);
}
}
return Promise.all(promises);
}
/**
* Get the current vertical scroll position.
* @returns {number} current scroll top
*/
export function getScrollTop() {
return (document.documentElement ?? document.body).scrollTop;
}
/**
* Check whether the current viewport matches the mobile breakpoint.
* @returns {Boolean} whether the viewport is mobile-sized
*/
export function isMobile() {
return window.matchMedia('only screen and (max-width: 680px)').matches;
}
/**
* Check whether the table of contents should use the static layout.
* @returns {Boolean} whether the TOC should be rendered as static
*/
export function isTocStatic() {
return document.getElementById('toc-static').dataset.kept === 'true' || window.matchMedia('only screen and (max-width: 960px)').matches;
}
/**
* Get the current theme mode from the root element.
* @returns {String} one of auto, light, or dark
*/
export function getThemeMode() {
return document.documentElement.dataset.themeMode || 'auto';
}
/**
* Check whether the current effective theme is dark.
* In auto mode, this follows the system color scheme preference.
* @returns {Boolean} whether dark mode is currently active
*/
export function isDarkMode() {
const themeMode = getThemeMode();
return themeMode === 'auto'
? window.matchMedia('(prefers-color-scheme: dark)').matches
: themeMode === 'dark';
}
/**
* Add one or more Animate.css classes to an element.
* @param {Element} element target element
* @param {String|Array<String>} animation animation name or names
* @param {Boolean} reserved whether to keep animation classes after completion
* @param {Function} callback callback invoked after animation ends
*/
export function animateCSS(element, animation, reserved, callback) {
!Array.isArray(animation) && (animation = [animation]);
element.classList.add('animate__animated', ...animation);
element.addEventListener('animationend', () => {
!reserved && element.classList.remove('animate__animated', ...animation);
typeof callback === 'function' && callback();
}, { once: true });
}
/**
* Validate whether a value is a valid Date instance.
* @param {*} date value to validate
* @returns {Boolean} whether the value is a valid date
*/
export function isValidDate(date) {
return date instanceof Date && !isNaN(date.getTime());
}
/**
* Scroll an element into view smoothly.
* @param {String} selector selector or id reference beginning with #
*/
export function scrollIntoView(selector) {
const element = selector.startsWith('#')
? document.getElementById(selector.slice(1))
: document.querySelector(selector);
element?.scrollIntoView({
behavior: 'smooth'
});
}
/**
* Create a hidden staging element for temporary DOM operations.
* @returns {Object} staging helpers and the staging element itself
*/
export function getStagingDOM() {
const stagingElement = document.createElement('div')
stagingElement.style.display = 'none';
stagingElement.dataset.stagingId = Math.random().toString(36).slice(2);
document.body.appendChild(stagingElement);
return {
$el: stagingElement,
stage(dom) {
stagingElement.innerHTML = '';
stagingElement.appendChild(dom);
},
contentAsHtml() {
return stagingElement.innerHTML;
},
contentAsText() {
return stagingElement.innerText;
},
contentAsJson() {
return JSON.parse(stagingElement.innerHTML);
},
destroy() {
document.body.removeChild(stagingElement);
}
}
}
/**
* Create a text-copy helper with clipboard API fallback.
* @returns {Function} function that copies text and returns a Promise
*/
export function createCopyText() {
if (navigator.clipboard) {
return (text) => navigator.clipboard.writeText(text);
}
return (text) => new Promise((resolve, reject) => {
const input = document.createElement('input');
input.value = text;
document.body.appendChild(input);
input.select();
if (document.execCommand('copy')) {
document.body.removeChild(input);
resolve();
} else {
reject();
}
});
}
/**
* Check whether a string looks like a JavaScript object literal.
* @example isObjectLiteral("{a:1,b:2}") // true
* @param {String} str string to check
* @returns {Boolean} whether the string is an object literal
*/
export function isObjectLiteral(str) {
if (typeof str !== 'string') {
return false;
}
str = str.replace(/\s+/g, ' ').trim().replace(/;$/, '')
if (str.startsWith('{') && str.endsWith('}')) {
return true;
}
return false;
}
/**
* Escape a string for safe HTML text output.
* @param {String} str string to escape
* @returns {String} escaped HTML string
*/
export function HTMLEscape(str) {
return str.replace(/[&<>"']/g, char => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
})[char]);
}