quickshell: use qs imports instead of root:/

This commit is contained in:
end-4
2025-07-17 20:24:01 +07:00
parent fb6721e348
commit 254870c9ab
177 changed files with 983 additions and 1252 deletions
@@ -1,6 +1,6 @@
import QtQuick
import Quickshell
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import qs.modules.common.functions
pragma Singleton
pragma ComponentBehavior: Bound
@@ -1,11 +1,10 @@
pragma Singleton
pragma ComponentBehavior: Bound
import "root:/modules/common/functions/file_utils.js" as FileUtils
import qs.modules.common.functions
import Qt.labs.platform
import QtQuick
import Quickshell
import Quickshell.Hyprland
Singleton {
// XDG Dirs, with "file://"
@@ -0,0 +1,114 @@
pragma Singleton
import Quickshell
Singleton {
id: root
/**
* Returns a color with the hue of color2 and the saturation, value, and alpha of color1.
*
* @param {string} color1 - The base color (any Qt.color-compatible string).
* @param {string} color2 - The color to take hue from.
* @returns {Qt.rgba} The resulting color.
*/
function colorWithHueOf(color1, color2) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
// Qt.color hsvHue/hsvSaturation/hsvValue/alpha return 0-1
var hue = c2.hsvHue;
var sat = c1.hsvSaturation;
var val = c1.hsvValue;
var alpha = c1.a;
return Qt.hsva(hue, sat, val, alpha);
}
/**
* Returns a color with the saturation of color2 and the hue/value/alpha of color1.
*
* @param {string} color1 - The base color (any Qt.color-compatible string).
* @param {string} color2 - The color to take saturation from.
* @returns {Qt.rgba} The resulting color.
*/
function colorWithSaturationOf(color1, color2) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
var hue = c1.hsvHue;
var sat = c2.hsvSaturation;
var val = c1.hsvValue;
var alpha = c1.a;
return Qt.hsva(hue, sat, val, alpha);
}
/**
* Returns a color with the given lightness and the hue, saturation, and alpha of the input color (using HSL).
*
* @param {string} color - The base color (any Qt.color-compatible string).
* @param {number} lightness - The lightness value to use (0-1).
* @returns {Qt.rgba} The resulting color.
*/
function colorWithLightness(color, lightness) {
var c = Qt.color(color);
return Qt.hsla(c.hslHue, c.hslSaturation, lightness, c.a);
}
/**
* Returns a color with the lightness of color2 and the hue, saturation, and alpha of color1 (using HSL).
*
* @param {string} color1 - The base color (any Qt.color-compatible string).
* @param {string} color2 - The color to take lightness from.
* @returns {Qt.rgba} The resulting color.
*/
function colorWithLightnessOf(color1, color2) {
var c2 = Qt.color(color2);
return colorWithLightness(color1, c2.hslLightness);
}
/**
* Adapts color1 to the accent (hue and saturation) of color2 using HSL, keeping lightness and alpha from color1.
*
* @param {string} color1 - The base color (any Qt.color-compatible string).
* @param {string} color2 - The accent color.
* @returns {Qt.rgba} The resulting color.
*/
function adaptToAccent(color1, color2) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
var hue = c2.hslHue;
var sat = c2.hslSaturation;
var light = c1.hslLightness;
var alpha = c1.a;
return Qt.hsla(hue, sat, light, alpha);
}
/**
* Mixes two colors by a given percentage.
*
* @param {string} color1 - The first color (any Qt.color-compatible string).
* @param {string} color2 - The second color.
* @param {number} percentage - The mix ratio (0-1). 1 = all color1, 0 = all color2.
* @returns {Qt.rgba} The resulting mixed color.
*/
function mix(color1, color2, percentage = 0.5) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
return Qt.rgba(percentage * c1.r + (1 - percentage) * c2.r, percentage * c1.g + (1 - percentage) * c2.g, percentage * c1.b + (1 - percentage) * c2.b, percentage * c1.a + (1 - percentage) * c2.a);
}
/**
* Transparentizes a color by a given percentage.
*
* @param {string} color - The color (any Qt.color-compatible string).
* @param {number} percentage - The amount to transparentize (0-1).
* @returns {Qt.rgba} The resulting color.
*/
function transparentize(color, percentage = 1) {
var c = Qt.color(color);
return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage));
}
}
@@ -0,0 +1,41 @@
pragma Singleton
import Quickshell
Singleton {
id: root
/**
* Trims the File protocol off the input string
* @param {string} str
* @returns {string}
*/
function trimFileProtocol(str) {
return str.startsWith("file://") ? str.slice(7) : str;
}
/**
* Extracts the file name from a file path
* @param {string} str
* @returns {string}
*/
function fileNameForPath(str) {
if (typeof str !== "string") return "";
const trimmed = trimFileProtocol(str);
return trimmed.split(/[\\/]/).pop();
}
/**
* Removes the file extension from a file path or name
* @param {string} str
* @returns {string}
*/
function trimFileExt(str) {
if (typeof str !== "string") return "";
const trimmed = trimFileProtocol(str);
const lastDot = trimmed.lastIndexOf(".");
if (lastDot > -1 && lastDot > trimmed.lastIndexOf("/")) {
return trimmed.slice(0, lastDot);
}
return trimmed;
}
}
@@ -0,0 +1,98 @@
pragma Singleton
import Quickshell
Singleton {
id: root
function toPlainObject(qtObj) {
if (qtObj === null || typeof qtObj !== "object") return qtObj;
// Handle true arrays
if (Array.isArray(qtObj)) {
return qtObj.map(item => toPlainObject(item));
}
// Handle array-like Qt objects (e.g., have length and numeric keys)
if (
typeof qtObj.length === "number" &&
qtObj.length > 0 &&
Object.keys(qtObj).every(
key => !isNaN(key) || key === "length"
)
) {
let arr = [];
for (let i = 0; i < qtObj.length; i++) {
arr.push(toPlainObject(qtObj[i]));
}
return arr;
}
const result = ({});
for (let key in qtObj) {
if (
typeof qtObj[key] !== "function" &&
!key.startsWith("objectName") &&
!key.startsWith("children") &&
!key.startsWith("object") &&
!key.startsWith("parent") &&
!key.startsWith("metaObject") &&
!key.startsWith("destroyed") &&
!key.startsWith("reloadableId")
) {
result[key] = toPlainObject(qtObj[key]);
}
}
// console.log(JSON.stringify(result))
return result;
}
function applyToQtObject(qtObj, jsonObj) {
// console.log("applyToQtObject", JSON.stringify(qtObj, null, 2), "<<", JSON.stringify(jsonObj, null, 2));
if (!qtObj || typeof jsonObj !== "object" || jsonObj === null) return;
// Detect array-like Qt objects
const isQtArrayLike = obj => {
return obj && typeof obj === "object" &&
typeof obj.length === "number" &&
obj.length > 0 &&
Object.keys(obj).every(key => !isNaN(key) || key === "length");
};
// If both are arrays or array-like, update in place or replace
if ((Array.isArray(qtObj) || isQtArrayLike(qtObj)) && Array.isArray(jsonObj)) {
qtObj.length = 0;
for (let i = 0; i < jsonObj.length; i++) {
qtObj.push(jsonObj[i]);
}
return;
}
// If target is array or array-like but source is not, clear
if ((Array.isArray(qtObj) || isQtArrayLike(qtObj)) && !Array.isArray(jsonObj)) {
qtObj.length = 0;
return;
}
// If source is array but target is not, assign directly if possible
if (!(Array.isArray(qtObj) || isQtArrayLike(qtObj)) && Array.isArray(jsonObj)) {
return jsonObj;
}
for (let key in jsonObj) {
if (!qtObj.hasOwnProperty(key)) continue;
const value = qtObj[key];
const jsonValue = jsonObj[key];
// console.log("applying to qt obj key:", value, "jsonValue:", jsonValue);
if ((Array.isArray(value) || isQtArrayLike(value)) && Array.isArray(jsonValue)) {
value.length = 0;
for (let i = 0; i < jsonValue.length; i++) {
value.push(jsonValue[i]);
}
} else if (value && typeof value === "object" && !Array.isArray(value) && !isQtArrayLike(value)) {
applyToQtObject(value, jsonValue);
} else {
qtObj[key] = jsonValue;
}
}
}
}
@@ -0,0 +1,221 @@
pragma Singleton
import Quickshell
Singleton {
id: root
/**
* Formats a string according to the args that are passed inc
* @param { string } str
* @param {...any} args
* @returns
*/
function format(str, ...args) {
return str.replace(/{(\d+)}/g, (match, index) => typeof args[index] !== 'undefined' ? args[index] : match);
}
/**
* Returns the domain of the passed in url or null
* @param { string } url
* @returns { string| null }
*/
function getDomain(url) {
const match = url.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/);
return match ? match[1] : null;
}
/**
* Returns the base url of the passed in url or null
* @param { string } url
* @returns { string | null }
*/
function getBaseUrl(url) {
const match = url.match(/^(https?:\/\/[^\/]+)(\/.*)?$/);
return match ? match[1] : null;
}
/**
* Escapes single quotes in shell commands
* @param { string } str
* @returns { string }
*/
function shellSingleQuoteEscape(str) {
// escape single quotes
return String(str)
// .replace(/\\/g, '\\\\')
.replace(/'/g, "'\\''");
}
/**
* Splits markdown blocks into three different types: text, think, and code.
* @param { string } markdown
*/
function splitMarkdownBlocks(markdown) {
const regex = /```(\w+)?\n([\s\S]*?)```|<think>([\s\S]*?)<\/think>/g;
/**
* @type {{type: "text" | "think" | "code"; content: string; lang: string | undefined; completed: boolean | undefined}[]}
*/
let result = [];
let lastIndex = 0;
let match;
while ((match = regex.exec(markdown)) !== null) {
if (match.index > lastIndex) {
const text = markdown.slice(lastIndex, match.index);
if (text.trim()) {
result.push({
type: "text",
content: text
});
}
}
if (match[0].startsWith('```')) {
if (match[2] && match[2].trim()) {
result.push({
type: "code",
lang: match[1] || "",
content: match[2],
completed: true
});
}
} else if (match[0].startsWith('<think>')) {
if (match[3] && match[3].trim()) {
result.push({
type: "think",
content: match[3],
completed: true
});
}
}
lastIndex = regex.lastIndex;
}
// Handle any remaining text after the last match
if (lastIndex < markdown.length) {
const text = markdown.slice(lastIndex);
// Check for unfinished <think> block
const thinkStart = text.indexOf('<think>');
const codeStart = text.indexOf('```');
if (thinkStart !== -1 && (codeStart === -1 || thinkStart < codeStart)) {
const beforeThink = text.slice(0, thinkStart);
if (beforeThink.trim()) {
result.push({
type: "text",
content: beforeThink
});
}
const thinkContent = text.slice(thinkStart + 7);
if (thinkContent.trim()) {
result.push({
type: "think",
content: thinkContent,
completed: false
});
}
} else if (codeStart !== -1) {
const beforeCode = text.slice(0, codeStart);
if (beforeCode.trim()) {
result.push({
type: "text",
content: beforeCode
});
}
// Try to detect language after ```
const codeLangMatch = text.slice(codeStart + 3).match(/^(\w+)?\n/);
let lang = "";
let codeContentStart = codeStart + 3;
if (codeLangMatch) {
lang = codeLangMatch[1] || "";
codeContentStart += codeLangMatch[0].length;
} else if (text[codeStart + 3] === '\n') {
codeContentStart += 1;
}
const codeContent = text.slice(codeContentStart);
if (codeContent.trim()) {
result.push({
type: "code",
lang,
content: codeContent,
completed: false
});
}
} else if (text.trim()) {
result.push({
type: "text",
content: text
});
}
}
// console.log(JSON.stringify(result, null, 2));
return result;
}
/**
* Returns the original string with backslashes escaped
* @param { string } str
* @returns { string }
*/
function escapeBackslashes(str) {
return str.replace(/\\/g, '\\\\');
}
/**
* Wraps words to supplied maximum length
* @param { string | null } str
* @param { number } maxLen
* @returns { string }
*/
function wordWrap(str, maxLen) {
if (!str)
return "";
let words = str.split(" ");
let lines = [];
let current = "";
for (let i = 0; i < words.length; ++i) {
if ((current + (current.length > 0 ? " " : "") + words[i]).length > maxLen) {
if (current.length > 0)
lines.push(current);
current = words[i];
} else {
current += (current.length > 0 ? " " : "") + words[i];
}
}
if (current.length > 0)
lines.push(current);
return lines.join("\n");
}
function cleanMusicTitle(title) {
if (!title)
return "";
// Brackets
title = title.replace(/^ *\([^)]*\) */g, " "); // Round brackets
title = title.replace(/^ *\[[^\]]*\] */g, " "); // Square brackets
title = title.replace(/^ *\{[^\}]*\} */g, " "); // Curly brackets
// Japenis brackets
title = title.replace(/^ *【[^】]*】/, ""); // Touhou
title = title.replace(/^ *《[^》]*》/, ""); // ??
title = title.replace(/^ *「[^」]*」/, ""); // OP/ED thingie
title = title.replace(/^ *『[^』]*』/, ""); // OP/ED thingie
return title.trim();
}
function friendlyTimeForSeconds(seconds) {
if (isNaN(seconds) || seconds < 0)
return "0:00";
seconds = Math.floor(seconds);
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
if (h > 0) {
return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
} else {
return `${m}:${s.toString().padStart(2, '0')}`;
}
}
function escapeHtml(str) {
if (typeof str !== 'string')
return str;
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}
}
@@ -1,109 +0,0 @@
// This module provides high level utility functions for color manipulation.
/**
* Returns a color with the hue of color2 and the saturation, value, and alpha of color1.
*
* @param {string} color1 - The base color (any Qt.color-compatible string).
* @param {string} color2 - The color to take hue from.
* @returns {Qt.rgba} The resulting color.
*/
function colorWithHueOf(color1, color2) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
// Qt.color hsvHue/hsvSaturation/hsvValue/alpha return 0-1
var hue = c2.hsvHue;
var sat = c1.hsvSaturation;
var val = c1.hsvValue;
var alpha = c1.a;
return Qt.hsva(hue, sat, val, alpha);
}
/**
* Returns a color with the saturation of color2 and the hue/value/alpha of color1.
*
* @param {string} color1 - The base color (any Qt.color-compatible string).
* @param {string} color2 - The color to take saturation from.
* @returns {Qt.rgba} The resulting color.
*/
function colorWithSaturationOf(color1, color2) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
var hue = c1.hsvHue;
var sat = c2.hsvSaturation;
var val = c1.hsvValue;
var alpha = c1.a;
return Qt.hsva(hue, sat, val, alpha);
}
/**
* Returns a color with the given lightness and the hue, saturation, and alpha of the input color (using HSL).
*
* @param {string} color - The base color (any Qt.color-compatible string).
* @param {number} lightness - The lightness value to use (0-1).
* @returns {Qt.rgba} The resulting color.
*/
function colorWithLightness(color, lightness) {
var c = Qt.color(color);
return Qt.hsla(c.hslHue, c.hslSaturation, lightness, c.a);
}
/**
* Returns a color with the lightness of color2 and the hue, saturation, and alpha of color1 (using HSL).
*
* @param {string} color1 - The base color (any Qt.color-compatible string).
* @param {string} color2 - The color to take lightness from.
* @returns {Qt.rgba} The resulting color.
*/
function colorWithLightnessOf(color1, color2) {
var c2 = Qt.color(color2);
return colorWithLightness(color1, c2.hslLightness);
}
/**
* Adapts color1 to the accent (hue and saturation) of color2 using HSL, keeping lightness and alpha from color1.
*
* @param {string} color1 - The base color (any Qt.color-compatible string).
* @param {string} color2 - The accent color.
* @returns {Qt.rgba} The resulting color.
*/
function adaptToAccent(color1, color2) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
var hue = c2.hslHue;
var sat = c2.hslSaturation;
var light = c1.hslLightness;
var alpha = c1.a;
return Qt.hsla(hue, sat, light, alpha);
}
/**
* Mixes two colors by a given percentage.
*
* @param {string} color1 - The first color (any Qt.color-compatible string).
* @param {string} color2 - The second color.
* @param {number} percentage - The mix ratio (0-1). 1 = all color1, 0 = all color2.
* @returns {Qt.rgba} The resulting mixed color.
*/
function mix(color1, color2, percentage = 0.5) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
return Qt.rgba(percentage * c1.r + (1 - percentage) * c2.r, percentage * c1.g + (1 - percentage) * c2.g, percentage * c1.b + (1 - percentage) * c2.b, percentage * c1.a + (1 - percentage) * c2.a);
}
/**
* Transparentizes a color by a given percentage.
*
* @param {string} color - The color (any Qt.color-compatible string).
* @param {number} percentage - The amount to transparentize (0-1).
* @returns {Qt.rgba} The resulting color.
*/
function transparentize(color, percentage = 1) {
var c = Qt.color(color);
return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage));
}
@@ -1,34 +0,0 @@
/**
* Trims the File protocol off the input string
* @param {string} str
* @returns {string}
*/
function trimFileProtocol(str) {
return str.startsWith("file://") ? str.slice(7) : str;
}
/**
* Extracts the file name from a file path
* @param {string} str
* @returns {string}
*/
function fileNameForPath(str) {
if (typeof str !== "string") return "";
const trimmed = trimFileProtocol(str);
return trimmed.split(/[\\/]/).pop();
}
/**
* Removes the file extension from a file path or name
* @param {string} str
* @returns {string}
*/
function trimFileExt(str) {
if (typeof str !== "string") return "";
const trimmed = trimFileProtocol(str);
const lastDot = trimmed.lastIndexOf(".");
if (lastDot > -1 && lastDot > trimmed.lastIndexOf("/")) {
return trimmed.slice(0, lastDot);
}
return trimmed;
}
@@ -1,91 +0,0 @@
function toPlainObject(qtObj) {
if (qtObj === null || typeof qtObj !== "object") return qtObj;
// Handle true arrays
if (Array.isArray(qtObj)) {
return qtObj.map(item => toPlainObject(item));
}
// Handle array-like Qt objects (e.g., have length and numeric keys)
if (
typeof qtObj.length === "number" &&
qtObj.length > 0 &&
Object.keys(qtObj).every(
key => !isNaN(key) || key === "length"
)
) {
let arr = [];
for (let i = 0; i < qtObj.length; i++) {
arr.push(toPlainObject(qtObj[i]));
}
return arr;
}
const result = ({});
for (let key in qtObj) {
if (
typeof qtObj[key] !== "function" &&
!key.startsWith("objectName") &&
!key.startsWith("children") &&
!key.startsWith("object") &&
!key.startsWith("parent") &&
!key.startsWith("metaObject") &&
!key.startsWith("destroyed") &&
!key.startsWith("reloadableId")
) {
result[key] = toPlainObject(qtObj[key]);
}
}
// console.log(JSON.stringify(result))
return result;
}
function applyToQtObject(qtObj, jsonObj) {
// console.log("applyToQtObject", JSON.stringify(qtObj, null, 2), "<<", JSON.stringify(jsonObj, null, 2));
if (!qtObj || typeof jsonObj !== "object" || jsonObj === null) return;
// Detect array-like Qt objects
const isQtArrayLike = obj => {
return obj && typeof obj === "object" &&
typeof obj.length === "number" &&
obj.length > 0 &&
Object.keys(obj).every(key => !isNaN(key) || key === "length");
};
// If both are arrays or array-like, update in place or replace
if ((Array.isArray(qtObj) || isQtArrayLike(qtObj)) && Array.isArray(jsonObj)) {
qtObj.length = 0;
for (let i = 0; i < jsonObj.length; i++) {
qtObj.push(jsonObj[i]);
}
return;
}
// If target is array or array-like but source is not, clear
if ((Array.isArray(qtObj) || isQtArrayLike(qtObj)) && !Array.isArray(jsonObj)) {
qtObj.length = 0;
return;
}
// If source is array but target is not, assign directly if possible
if (!(Array.isArray(qtObj) || isQtArrayLike(qtObj)) && Array.isArray(jsonObj)) {
return jsonObj;
}
for (let key in jsonObj) {
if (!qtObj.hasOwnProperty(key)) continue;
const value = qtObj[key];
const jsonValue = jsonObj[key];
// console.log("applying to qt obj key:", value, "jsonValue:", jsonValue);
if ((Array.isArray(value) || isQtArrayLike(value)) && Array.isArray(jsonValue)) {
value.length = 0;
for (let i = 0; i < jsonValue.length; i++) {
value.push(jsonValue[i]);
}
} else if (value && typeof value === "object" && !Array.isArray(value) && !isQtArrayLike(value)) {
applyToQtObject(value, jsonValue);
} else {
qtObj[key] = jsonValue;
}
}
}
@@ -1,188 +0,0 @@
/**
* Formats a string according to the args that are passed in
* @param { string } str
* @param {...any} args
* @returns
*/
function format(str, ...args) {
return str.replace(/{(\d+)}/g, (match, index) =>
typeof args[index] !== 'undefined' ? args[index] : match
);
}
/**
* Returns the domain of the passed in url or null
* @param { string } url
* @returns { string| null }
*/
function getDomain(url) {
const match = url.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/);
return match ? match[1] : null;
}
/**
* Returns the base url of the passed in url or null
* @param { string } url
* @returns { string | null }
*/
function getBaseUrl(url) {
const match = url.match(/^(https?:\/\/[^\/]+)(\/.*)?$/);
return match ? match[1] : null;
}
/**
* Escapes single quotes in shell commands
* @param { string } str
* @returns { string }
*/
function shellSingleQuoteEscape(str) {
// escape single quotes
return String(str)
// .replace(/\\/g, '\\\\')
.replace(/'/g, "'\\''");
}
/**
* Splits markdown blocks into three different types: text, think, and code.
* @param { string } markdown
*/
function splitMarkdownBlocks(markdown) {
const regex = /```(\w+)?\n([\s\S]*?)```|<think>([\s\S]*?)<\/think>/g;
/**
* @type {{type: "text" | "think" | "code"; content: string; lang: string | undefined; completed: boolean | undefined}[]}
*/
let result = [];
let lastIndex = 0;
let match;
while ((match = regex.exec(markdown)) !== null) {
if (match.index > lastIndex) {
const text = markdown.slice(lastIndex, match.index);
if (text.trim()) {
result.push({ type: "text", content: text });
}
}
if (match[0].startsWith('```')) {
if (match[2] && match[2].trim()) {
result.push({ type: "code", lang: match[1] || "", content: match[2], completed: true });
}
} else if (match[0].startsWith('<think>')) {
if (match[3] && match[3].trim()) {
result.push({ type: "think", content: match[3], completed: true });
}
}
lastIndex = regex.lastIndex;
}
// Handle any remaining text after the last match
if (lastIndex < markdown.length) {
const text = markdown.slice(lastIndex);
// Check for unfinished <think> block
const thinkStart = text.indexOf('<think>');
const codeStart = text.indexOf('```');
if (
thinkStart !== -1 &&
(codeStart === -1 || thinkStart < codeStart)
) {
const beforeThink = text.slice(0, thinkStart);
if (beforeThink.trim()) {
result.push({ type: "text", content: beforeThink });
}
const thinkContent = text.slice(thinkStart + 7);
if (thinkContent.trim()) {
result.push({ type: "think", content: thinkContent, completed: false });
}
} else if (codeStart !== -1) {
const beforeCode = text.slice(0, codeStart);
if (beforeCode.trim()) {
result.push({ type: "text", content: beforeCode });
}
// Try to detect language after ```
const codeLangMatch = text.slice(codeStart + 3).match(/^(\w+)?\n/);
let lang = "";
let codeContentStart = codeStart + 3;
if (codeLangMatch) {
lang = codeLangMatch[1] || "";
codeContentStart += codeLangMatch[0].length;
} else if (text[codeStart + 3] === '\n') {
codeContentStart += 1;
}
const codeContent = text.slice(codeContentStart);
if (codeContent.trim()) {
result.push({ type: "code", lang, content: codeContent, completed: false });
}
} else if (text.trim()) {
result.push({ type: "text", content: text });
}
}
// console.log(JSON.stringify(result, null, 2));
return result;
}
/**
* Returns the original string with backslashes escaped
* @param { string } str
* @returns { string }
*/
function escapeBackslashes(str) {
return str.replace(/\\/g, '\\\\');
}
/**
* Wraps words to supplied maximum length
* @param { string | null } str
* @param { number } maxLen
* @returns { string }
*/
function wordWrap(str, maxLen) {
if (!str) return "";
let words = str.split(" ");
let lines = [];
let current = "";
for (let i = 0; i < words.length; ++i) {
if ((current + (current.length > 0 ? " " : "") + words[i]).length > maxLen) {
if (current.length > 0) lines.push(current);
current = words[i];
} else {
current += (current.length > 0 ? " " : "") + words[i];
}
}
if (current.length > 0) lines.push(current);
return lines.join("\n");
}
function cleanMusicTitle(title) {
if (!title) return "";
// Brackets
title = title.replace(/^ *\([^)]*\) */g, " "); // Round brackets
title = title.replace(/^ *\[[^\]]*\] */g, " "); // Square brackets
title = title.replace(/^ *\{[^\}]*\} */g, " "); // Curly brackets
// Japenis brackets
title = title.replace(/^ *【[^】]*】/, "") // Touhou
title = title.replace(/^ *《[^》]*》/, "") // ??
title = title.replace(/^ *「[^」]*」/, "") // OP/ED thingie
title = title.replace(/^ *『[^』]*』/, "") // OP/ED thingie
return title.trim();
}
function friendlyTimeForSeconds(seconds) {
if (isNaN(seconds) || seconds < 0) return "0:00";
seconds = Math.floor(seconds);
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
if (h > 0) {
return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
} else {
return `${m}:${s.toString().padStart(2, '0')}`;
}
}
function escapeHtml(str) {
if (typeof str !== 'string') return str;
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
@@ -1,8 +1,6 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
/**
@@ -2,7 +2,7 @@
// License: LGPL-3.0 - A copy can be found in `licenses` folder of repo
import QtQuick
import "root:/modules/common"
import qs.modules.common
/**
* Material 3 circular progress. See https://m3.material.io/components/progress-indicators/specs
@@ -1,17 +1,11 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import "root:/modules/common/functions/file_utils.js" as FileUtils
import qs.modules.common
import qs.modules.common.widgets
import qs.services
import qs.modules.common.functions
import Qt5Compat.GraphicalEffects
import Qt.labs.platform
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import Quickshell.Hyprland
Rectangle {
id: root
@@ -1,14 +1,9 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import "root:/services/"
import "root:/modules/common/"
import "root:/modules/common/widgets/"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import "root:/modules/common/functions/file_utils.js" as FileUtils
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
Flow {
id: root
@@ -1,8 +1,7 @@
import "root:/modules/common/widgets/"
import "root:/modules/common/"
import qs.modules.common.widgets
import qs.modules.common
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
RowLayout {
id: root
@@ -1,5 +1,5 @@
import "root:/modules/common/widgets/"
import "root:/modules/common/"
import qs.modules.common.widgets
import qs.modules.common
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
@@ -1,8 +1,7 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "root:/modules/common/"
import "root:/modules/common/widgets/"
import qs.modules.common
import qs.modules.common.widgets
Flickable {
id: root
@@ -1,8 +1,8 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "root:/modules/common/"
import "root:/modules/common/widgets/"
import qs.modules.common
import qs.modules.common.widgets
ColumnLayout {
id: root
@@ -1,8 +1,7 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "root:/modules/common/"
import "root:/modules/common/widgets/"
import qs.modules.common
import qs.modules.common.widgets
ColumnLayout {
id: root
@@ -1,7 +1,7 @@
import QtQuick
import QtQuick.Layouts
import "root:/modules/common/"
import "root:/modules/common/widgets/"
import qs.modules.common
import qs.modules.common.widgets
StyledText {
text: "Subsection"
@@ -9,7 +9,7 @@ Item {
property bool colorize: false
property color color
property string source: ""
property string iconFolder: "root:/assets/icons" // The folder to check first
property string iconFolder: Qt.resolvedUrl(Quickshell.configPath("assets/icons")) // The folder to check first
width: 30
height: 30
@@ -17,8 +17,9 @@ Item {
id: iconImage
anchors.fill: parent
source: {
if (iconFolder && iconFolder + "/" + root.source) {
return iconFolder + "/" + root.source
const fullPathWhenSourceIsIconName = iconFolder + "/" + root.source;
if (iconFolder && fullPathWhenSourceIsIconName) {
return fullPathWhenSourceIsIconName
}
return root.source
}
@@ -1,10 +1,5 @@
import "root:/modules/common"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import qs.modules.common
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
/**
* Material 3 dialog button. See https://m3.material.io/components/dialogs/overview
@@ -1,8 +1,6 @@
import "root:/modules/common"
import "root:/services"
import qs.modules.common
import qs.services
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
/**
* A convenience MouseArea for handling drag events.
@@ -1,16 +1,11 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import "root:/modules/common/functions/file_utils.js" as FileUtils
import qs.modules.common
import qs.modules.common.widgets
import qs.services
import qs.modules.common.functions
import Qt5Compat.GraphicalEffects
import Qt.labs.platform
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Io
import Quickshell.Widgets
import Quickshell.Hyprland
IconImage {
id: root
@@ -1,8 +1,7 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "root:/modules/common/"
import "root:/modules/common/widgets/"
import qs.modules.common
import qs.modules.common.widgets
/**
* Material 3 FAB.
@@ -1,12 +1,9 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import Qt5Compat.GraphicalEffects
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Io
import Quickshell.Widgets
/**
* Material 3 button with expressive bounciness.
@@ -1,9 +1,5 @@
import "root:/modules/common"
import qs.modules.common
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
Rectangle {
id: root
@@ -1,13 +1,10 @@
import "root:/"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import qs
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
GroupButton {
id: lightDarkButtonRoot
@@ -1,6 +1,5 @@
import "root:/modules/common/"
import qs.modules.common
import QtQuick
import QtQuick.Layouts
Text {
id: root
@@ -1,5 +1,4 @@
import "root:/modules/common"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import qs.modules.common
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Controls
@@ -1,10 +1,5 @@
import "root:/modules/common"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import qs.modules.common
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
RippleButton {
id: root
@@ -1,8 +1,7 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "root:/modules/common/"
import "root:/modules/common/widgets/"
import qs.modules.common
import qs.modules.common.widgets
ColumnLayout { // Window content with navigation rail and content pane
id: root
@@ -1,10 +1,9 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Io
TabButton {
id: root
@@ -1,8 +1,7 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "root:/modules/common/"
import "root:/modules/common/widgets/"
import qs.modules.common
import qs.modules.common.widgets
RippleButton {
id: root
@@ -1,10 +1,7 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Io
Item {
id: root
@@ -1,9 +1,6 @@
import "root:/modules/common"
import "root:/services"
import qs.modules.common
import qs.services
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Notifications
RippleButton {
@@ -1,9 +1,7 @@
import "root:/modules/common"
import qs.modules.common
import "./notification_utils.js" as NotificationUtils
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Quickshell.Services.Notifications
@@ -1,18 +1,10 @@
import "root:/modules/common"
import "root:/services"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import qs.modules.common
import qs.services
import qs.modules.common.functions
import "./notification_utils.js" as NotificationUtils
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import Quickshell.Hyprland
import Quickshell.Services.Notifications
/**
* A group of notifications from the same app.
@@ -1,12 +1,8 @@
import "root:/modules/common"
import "root:/services"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import qs.services
import qs.modules.common
import qs.modules.common.functions
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Notifications
RippleButton { // Expand button
id: root
@@ -1,16 +1,10 @@
import "root:/modules/common"
import "root:/services"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import "./notification_utils.js" as NotificationUtils
import Qt5Compat.GraphicalEffects
import qs
import qs.modules.common
import qs.services
import qs.modules.common.functions
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import Quickshell.Hyprland
import Quickshell.Services.Notifications
@@ -1,13 +1,9 @@
import "root:/"
import "root:/modules/common/"
import "root:/modules/common/widgets"
import "root:/services"
import qs
import qs.modules.common
import qs.modules.common.widgets
import qs.services
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
StyledListView { // Scrollable window
id: root
@@ -1,9 +1,8 @@
import "root:/modules/common"
import "root:/"
import qs.modules.common
import qs
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
ColumnLayout {
id: root
@@ -1,12 +1,10 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Io
import Quickshell.Widgets
TabButton {
id: button
@@ -1,6 +1,5 @@
import "root:/modules/common"
import qs.modules.common
import QtQuick
import Quickshell
/**
* Recreation of GTK revealer. Expects one single child.
@@ -1,12 +1,9 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Io
import Quickshell.Widgets
/**
* A button with ripple effect similar to in Material Design.
@@ -1,8 +1,7 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "root:/modules/common/"
import "root:/modules/common/widgets/"
import qs.modules.common
import qs.modules.common.widgets
RippleButton {
id: buttonWithIconRoot
@@ -1,12 +1,10 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Io
import Quickshell.Widgets
TabButton {
id: root
@@ -1,10 +1,8 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import "root:/"
import Qt5Compat.GraphicalEffects
import qs.modules.common
import qs.modules.common.widgets
import qs.services
import qs
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
@@ -3,9 +3,9 @@ import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import "root:/services/"
import "root:/modules/common/"
import "root:/modules/common/widgets/"
import qs.services
import qs.modules.common
import qs.modules.common.widgets
GroupButton {
id: root
@@ -1,7 +1,6 @@
import "root:/modules/common"
import qs.modules.common
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Label {
renderType: Text.NativeRendering
@@ -1,13 +1,8 @@
import "root:/"
import "root:/modules/common/"
import "root:/modules/common/widgets"
import "root:/services"
import qs
import qs.modules.common
import qs.modules.common.widgets
import qs.services
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
/**
* A ListView with animations.
@@ -1,6 +1,6 @@
import "root:/services"
import "root:/modules/common"
import "root:/modules/common/widgets"
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
@@ -1,6 +1,6 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import qs.modules.common
import qs.modules.common.widgets
import qs.services
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
@@ -1,6 +1,6 @@
import QtQuick
import QtQuick.Effects
import "root:/modules/common"
import qs.modules.common
RectangularShadow {
required property var target
@@ -1,6 +1,6 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import qs.modules.common
import qs.modules.common.widgets
import qs.services
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
@@ -1,5 +1,5 @@
import "root:/modules/common"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import qs.modules.common
import qs.modules.common.functions
import QtQuick
import QtQuick.Controls
@@ -1,4 +1,4 @@
import "root:/modules/common/"
import qs.modules.common
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
@@ -1,4 +1,4 @@
import "root:/modules/common"
import qs.modules.common
import QtQuick
import QtQuick.Layouts
@@ -1,4 +1,4 @@
import "root:/modules/common"
import qs.modules.common
import QtQuick
import QtQuick.Controls
@@ -1,4 +1,4 @@
import "root:/modules/common"
import qs.modules.common
import QtQuick
import QtQuick.Controls
@@ -1,5 +1,5 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
@@ -1,8 +1,6 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
/**
@@ -1,13 +1,8 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Io
Canvas { // Visualizer
id: root