Files
Yoshihiro OKUMURA 2b47d882f7 refactor: clear all biome lint diagnostics
Biome reported 229 diagnostics after aligning the config with
bsi-ni.brain.riken.jp. Fix them all so `biome check` passes clean
without per-rule overrides.

- replace `any` in LokiJS queries with an `ItemFilter` interface and
  a single `asLokiQuery()` cast, matching bsi-ni
- type the XOOPS redirect maps as
  `Record<string, { key: string; isNumber: boolean }>`
- use `ItemSubTypes<string>` instead of `any` for sub item types
- join author lists with `.join(', ')` instead of index-keyed nodes
- give list elements stable keys derived from their own data
- return `React.ReactNode` from `ListBase.renderBody()`
- drop the temporary `noArrayIndexKey` override from biome.json
2026-09-11 17:28:44 +09:00

137 lines
3.9 KiB
TypeScript

import Config, { type MultiLang } from './config';
const escapeUrl = (str: string) => {
return encodeURIComponent(str).replace(/[!'()*]/g, (c) => {
return `%${c.charCodeAt(0).toString(16)}`;
});
};
const unescapeUrl = (str: string) => {
return decodeURIComponent(str);
};
const htmlspecialchars = (str: string) => {
return str.replace(/(<|>|&|'|")/g, (match) => {
switch (match) {
case '<':
return '&lt;';
case '>':
return '&gt;';
case '&':
return '&amp;';
case "'":
return '&#039;';
case '"':
return '&quot;';
}
return '';
});
};
const camelCase = (str: string) => {
return str.replace(/[-_](.)/g, (...matches) => {
return matches[1].toUpperCase();
});
};
const snakeCase = (str: string) => {
var camel = camelCase(str);
return camel.replace(/[A-Z]/g, (...matches) => {
return `_${matches[0].charAt(0).toLowerCase()}`;
});
};
const pascalCase = (str: string) => {
var camel = camelCase(str);
return camel.charAt(0).toUpperCase() + camel.slice(1);
};
const base64Encode = (str: string) => {
return btoa(unescape(encodeURIComponent(str)));
};
const base64Decode = (str: string) => {
return decodeURIComponent(escape(atob(str)));
};
const ordinal = (num: number) => {
const hundred = num % 100;
if (hundred >= 10 && hundred <= 20) {
return 'th';
}
const ten = num % 10;
const suffix = ['st', 'nd', 'rd'];
return ten > 0 && ten < 4 ? suffix[ten - 1] : 'th';
};
const mlang = (str: string, lang: MultiLang) => {
const mlLangs = ['en', 'ja'];
// escape brackets inside of <input type="text" value="...">
let text = str.replace(/(<input)([^>]*)(>)/gis, (whole, m1, m2, m3) => {
if (m2.match(/type=["']?(?:text|hidden)["']?/is)) {
return m1 + m2.replace(/\[/g, '__ml[ml__') + m3;
}
return whole;
});
// escape brackets inside of <textarea></textarea>
text = text.replace(/(<textarea[^>]*>)(.*)(<\/textarea>)/gis, (_whole, m1, m2, m3) => {
return m1 + m2.replace(/\[/g, '__ml[ml__') + m3;
});
// simple pattern to strip selected lang_tags
const re = new RegExp(`\\[/?(?:[^\\]]+\\|)?${lang}(?:\\|[^\\]]+)?\\](?:<br />)?`, 'g');
text = text.replace(re, '');
// eliminate description between the other language tags.
mlLangs.forEach((mlLang) => {
if (mlLang !== lang) {
const re = new RegExp(
'\\[(?:[^/][^\\]]+\\|)?' +
mlLang +
'(?:\\|[^\\]]+)?\\].*?\\[/(?:[^\\]]+\\|)?' +
mlLang +
'(?:\\|[^\\]]+)?\\](?:<br />)?',
'gis'
);
text = text.replace(re, (whole) => {
return whole.match(/<\/table>/) ? whole : '';
});
}
});
// unescape brackets inside of <textarea></textarea>
text = text.replace(/(<textarea[^>]*>)(.*)(<\/textarea>)/gis, (_whole, m1, m2, m3) => {
return m1 + m2.replace(/__ml\[ml__/g, '[') + m3;
});
// unescape brackets inside of <input type="text" value="...">
text = text.replace(/(<input)([^>]*)(>)/gis, (whole, m1, m2, m3) => {
if (m2.match(/type=["']?(?:text|hidden)["']?/is)) {
return m1 + m2.replace(/__ml\[ml__/g, '[') + m3;
}
return whole;
});
return text;
};
const siteTitle = (lang: MultiLang) => {
return mlang(Config.SITE_TITLE, lang);
};
const siteSlogan = (lang: MultiLang) => {
return mlang(Config.SITE_SLOGAN, lang);
};
const Functions = {
escape: escapeUrl,
unescape: unescapeUrl,
htmlspecialchars,
camelCase,
snakeCase,
pascalCase,
base64Encode,
base64Decode,
ordinal,
mlang,
siteTitle,
siteSlogan,
};
export default Functions;