Files
szurubooru/client/js/util/misc.js
Eva 12a7f4f78a client/search: autocomplete tag alias that the user searched for
Instead of always using the first alias.
"Tag input" fields (edit page, bulk tagging, etc.) will display the
matched name in suggestions, and use the first alias upon selection.
2025-04-04 11:31:43 +02:00

240 lines
6.5 KiB
JavaScript

"use strict";
const markdown = require("./markdown.js");
const uri = require("./uri.js");
const settings = require("../models/settings.js");
function decamelize(str, sep) {
sep = sep === undefined ? "-" : sep;
return str
.replace(/([a-z\d])([A-Z])/g, "$1" + sep + "$2")
.replace(/([A-Z]+)([A-Z][a-z\d]+)/g, "$1" + sep + "$2")
.toLowerCase();
}
function* range(start = 0, end = null, step = 1) {
if (end === null) {
end = start;
start = 0;
}
for (let i = start; i < end; i += step) {
yield i;
}
}
function _formatUnits(number, base, suffixes, callback) {
if (!number && number !== 0) {
return NaN;
}
number *= 1.0;
let suffix = suffixes.shift();
while (number >= base && suffixes.length > 0) {
suffix = suffixes.shift();
number /= base;
}
if (callback === undefined) {
callback = (number, suffix) => {
return suffix ? number.toFixed(1) + suffix : number;
};
}
return callback(number, suffix);
}
function formatFileSize(fileSize) {
return _formatUnits(
fileSize,
1024,
["B", "K", "M", "G"],
(number, suffix) => {
const decimalPlaces = number < 20 && suffix !== "B" ? 1 : 0;
return number.toFixed(decimalPlaces) + suffix;
}
);
}
function formatRelativeTime(timeString) {
if (!timeString) {
return "never";
}
const then = Date.parse(timeString);
const now = Date.now();
const difference = Math.abs(now - then) / 1000.0;
const future = now < then;
const descriptions = [
[60, "a few seconds", null],
[60 * 2, "a minute", null],
[60 * 60, "% minutes", 60],
[60 * 60 * 2, "an hour", null],
[60 * 60 * 24, "% hours", 60 * 60],
[60 * 60 * 24 * 2, "a day", null],
[60 * 60 * 24 * 30.42, "% days", 60 * 60 * 24],
[60 * 60 * 24 * 30.42 * 2, "a month", null],
[60 * 60 * 24 * 30.42 * 12, "% months", 60 * 60 * 24 * 30.42],
[60 * 60 * 24 * 30.42 * 12 * 2, "a year", null],
[8640000000000000 /* max*/, "% years", 60 * 60 * 24 * 30.42 * 12],
];
let text = null;
for (let kv of descriptions) {
const multiplier = kv[0];
const template = kv[1];
const divider = kv[2];
if (difference < multiplier) {
text = template.replace(/%/, Math.round(difference / divider));
break;
}
}
if (text === "a day") {
return future ? "tomorrow" : "yesterday";
}
return future ? "in " + text : text + " ago";
}
function formatMarkdown(text) {
return markdown.formatMarkdown(text);
}
function formatInlineMarkdown(text) {
return markdown.formatInlineMarkdown(text);
}
function splitByWhitespace(str) {
return str.split(/\s+/).filter((s) => s);
}
function unindent(callSite, ...args) {
function format(str) {
let size = -1;
return str.replace(/\n(\s+)/g, (m, m1) => {
if (size < 0) {
size = m1.replace(/\t/g, " ").length;
}
return "\n" + m1.slice(Math.min(m1.length, size));
});
}
if (typeof callSite === "string") {
return format(callSite);
}
if (typeof callSite === "function") {
return (...args) => format(callSite(...args));
}
let output = callSite
.slice(0, args.length + 1)
.map((text, i) => (i === 0 ? "" : args[i - 1]) + text)
.join("");
return format(output);
}
function enableExitConfirmation() {
window.onbeforeunload = (e) => {
return (
"Are you sure you want to leave? " +
"Data you have entered may not be saved."
);
};
}
function disableExitConfirmation() {
window.onbeforeunload = null;
}
function confirmPageExit() {
if (!window.onbeforeunload) {
return true;
}
if (window.confirm(window.onbeforeunload())) {
disableExitConfirmation();
return true;
}
}
function makeCssName(text, suffix) {
return suffix + "-" + text.replace(/[^a-z0-9]/g, "_");
}
function escapeHtml(unsafe) {
return unsafe
.toString()
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&apos;");
}
function arraysDiffer(source1, source2, orderImportant) {
source1 = [...source1];
source2 = [...source2];
if (orderImportant === true) {
if (source1.length !== source2.length) {
return true;
}
for (let i = 0; i < source1.length; i++) {
if (source1[i] !== source2[i]) {
return true;
}
}
return false;
}
return (
source1.filter((value) => !source2.includes(value)).length > 0 ||
source2.filter((value) => !source1.includes(value)).length > 0
);
}
function escapeSearchTerm(text) {
return text.replace(/([a-z_-]):/g, "$1\\:").replace(/\./g, "\\.");
}
function dataURItoBlob(dataURI) {
const chunks = dataURI.split(",");
const byteString =
chunks[0].indexOf("base64") >= 0
? window.atob(chunks[1])
: unescape(chunks[1]);
const mimeString = chunks[0].split(":")[1].split(";")[0];
const data = new Uint8Array(byteString.length);
for (let i = 0; i < byteString.length; i++) {
data[i] = byteString.charCodeAt(i);
}
return new Blob([data], { type: mimeString });
}
function getPrettyName(tag) {
if (settings.get().tagUnderscoresAsSpaces) {
return tag.replace(/_/g, " ");
}
return tag;
}
function wildcardMatch(pattern, str, sensitive = false) {
let w = pattern.replace(/[.+^${}()|[\]\\?]/g, "\\$&");
const re = new RegExp(`^${w.replace(/\(--wildcard--\)|\*/g, ".*")}$`, sensitive ? "" : "i");
return re.test(str);
}
module.exports = {
range: range,
formatRelativeTime: formatRelativeTime,
formatFileSize: formatFileSize,
formatMarkdown: formatMarkdown,
formatInlineMarkdown: formatInlineMarkdown,
unindent: unindent,
enableExitConfirmation: enableExitConfirmation,
disableExitConfirmation: disableExitConfirmation,
confirmPageExit: confirmPageExit,
escapeHtml: escapeHtml,
makeCssName: makeCssName,
splitByWhitespace: splitByWhitespace,
arraysDiffer: arraysDiffer,
decamelize: decamelize,
escapeSearchTerm: escapeSearchTerm,
dataURItoBlob: dataURItoBlob,
getPrettyName: getPrettyName,
wildcardMatch: wildcardMatch,
};