完成世界书、骰子、apiconfig页面处理

This commit is contained in:
2026-04-30 01:35:10 +08:00
parent a3e3711b2b
commit ba9b925c32
4602 changed files with 785225 additions and 23 deletions

459
frontend/node_modules/@iconify/types/README.md generated vendored Normal file
View File

@@ -0,0 +1,459 @@
# Iconify Types
Type definitions for using Iconify icon sets with TypeScript.
## Files structure
Iconify icon sets are available in several formats:
- Big JSON files that combine many icons in one file
- Node.js packages split into individual icons
### Icon format
Each icon is represented by the `IconifyIcon` type. It is a simple object with multiple string, number or boolean attributes.
The only required attribute is:
- `body`: string. Value contains inner HTML of an icon as a string, for example `<path d="..."/>`.
Optional attributes are represented by type `IconifyOptional`. They are split into several types: dimensions (`IconifyDimenisons` type) and transformations (`IconifyTransformations` type).
Dimensions attributes:
- `width`: number. viewBox width, number. If missing, value is set to 16.
- `height`: number. viewBox height, number. If missing, value is set to 16.
- `left`: number. viewBox left, number. If missing, the value is set to 0.
- `top`: number. viewBox top, number. If missing, the value is set to 0.
Transformations:
- `rotate`: number. Icon rotation. Iconify icons can be rotated in 90 degrees increments, allowing to reuse the same source icon for multiple icons, such as arrow-up being a copy of arrow-left rotated by 90 degrees. Values are 0 for 0 degrees, 1 for 90 degrees, 2 for 180 degrees, 3 for 270 degrees. The default value is 0.
- `hFlip`: boolean. Horizontal flip. Similar to the rotation transformation, an icon can be flipped horizontally and vertically. It can be used to quickly create aliases, such as arrow-left being an alias of arrow-right, but with hFlip set to true. The default value is false.
- `vFlip`: boolean. Vertical flip. The default value is false.
Example of icon object:
```js
const mdiHandIcon = {
body: '<path d="M6.58 19h8v3h-8v-3m13.16-7.4c-.19-.2-.45-.32-.74-.32l-.22.03l-3.2 1.69v-1.17l.51-8.93c.03-.55-.39-1.03-.94-1.06c-.55-.03-1.03.39-1.06.94l-.27 4.69h-.24l-1.04.11V2a1 1 0 0 0-1-1c-.54 0-1 .45-1 1v6.41l-.82.37l-.69-5.46c-.07-.55-.57-.94-1.12-.87c-.55.05-.94.55-.87 1.12l.77 6.06l-.38.17c-.13.05-.25.13-.36.2l-1.1-3.89c-.16-.57-.72-.91-1.26-.77c-.53.16-.83.74-.67 1.31l2.57 9.12c0 .03.02.07.03.1l.03.13h.01c.22.57.79 1 1.4 1h6.5c.39 0 .74-.16 1-.43l4.92-4.2l-.76-.77z" fill="currentColor"/>',
width: 24,
height: 24,
};
```
### Icon sets format
Iconify icon sets format is available from multiple sources:
- NPM package `@iconify/json` that includes all icon sets
- API responses used by SVG framework
Icon set format structure is available as the `IconifyJSON` type. It is an object with several fields:
- `prefix`: string. Icon set prefix.
- `icons`: object. Icons data. Value is an object that represents a set of icons, where the key is an icon name and value is `IconifyIcon` object (see "Icon format" above).
- `aliases`: object. Icon aliases, similar to the `icons` object (see "Aliases" section below).
Example:
```json
{
"prefix": "mdi",
"icons": {
"home": {
"body": "<path d=\"M10 20v-6h4v6h5v-8h3L12 3L2 12h3v8h5z\" fill=\"currentColor\"/>",
"width": 24,
"height": 24
},
"arrow-left": {
"body": "<path d=\"M20 11v2H8l5.5 5.5l-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5L8 11h12z\" fill=\"currentColor\"/>",
"width": 24,
"height": 24
}
}
}
```
All icon properties except for `body` are optional and are represented by type `IconifyOptional`. Type `IconifyJSON` also extends type `IconifyOptional`, allowing all optional properties to be placed in the root object.
If an icon is missing a property, look in the root object for the default value. If the root object does not have the default value, use Iconify default value for that property (see list of properties and default values in the "Icon format" section above).
Default values in the root object make it possible to reduce duplication.
Example:
```json
{
"prefix": "mdi",
"icons": {
"home": {
"body": "<path d=\"M10 20v-6h4v6h5v-8h3L12 3L2 12h3v8h5z\" fill=\"currentColor\"/>"
},
"arrow-left": {
"body": "<path d=\"M20 11v2H8l5.5 5.5l-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5L8 11h12z\" fill=\"currentColor\"/>"
}
},
"width": 24,
"height": 24
}
```
In this example, both icons are 24x24, so width and height have been moved to the root object.
Another example:
```json
{
"prefix": "fa-solid",
"icons": {
"arrow-left": {
"body": "<path d=\"M257.5 445.1l-22.2 22.2c-9.4 9.4-24.6 9.4-33.9 0L7 273c-9.4-9.4-9.4-24.6 0-33.9L201.4 44.7c9.4-9.4 24.6-9.4 33.9 0l22.2 22.2c9.5 9.5 9.3 25-.4 34.3L136.6 216H424c13.3 0 24 10.7 24 24v32c0 13.3-10.7 24-24 24H136.6l120.5 114.8c9.8 9.3 10 24.8.4 34.3z\" fill=\"currentColor\"/>",
"width": 448
},
"arrow-circle-left": {
"body": "<path d=\"M256 504C119 504 8 393 8 256S119 8 256 8s248 111 248 248s-111 248-248 248zm28.9-143.6L209.4 288H392c13.3 0 24-10.7 24-24v-16c0-13.3-10.7-24-24-24H209.4l75.5-72.4c9.7-9.3 9.9-24.8.4-34.3l-11-10.9c-9.4-9.4-24.6-9.4-33.9 0L107.7 239c-9.4 9.4-9.4 24.6 0 33.9l132.7 132.7c9.4 9.4 24.6 9.4 33.9 0l11-10.9c9.5-9.5 9.3-25-.4-34.3z\" fill=\"currentColor\"/>"
},
"barcode": {
"body": "<path d=\"M0 448V64h18v384H0zm26.857-.273V64H36v383.727h-9.143zm27.143 0V64h8.857v383.727H54zm44.857 0V64h8.857v383.727h-8.857zm36 0V64h17.714v383.727h-17.714zm44.857 0V64h8.857v383.727h-8.857zm18 0V64h8.857v383.727h-8.857zm18 0V64h8.857v383.727h-8.857zm35.715 0V64h18v383.727h-18zm44.857 0V64h18v383.727h-18zm35.999 0V64h18.001v383.727h-18.001zm36.001 0V64h18.001v383.727h-18.001zm26.857 0V64h18v383.727h-18zm45.143 0V64h26.857v383.727h-26.857zm35.714 0V64h9.143v383.727H476zm18 .273V64h18v384h-18z\" fill=\"currentColor\"/>"
}
},
"width": 512,
"height": 512
}
```
In this example `arrow-circle-left` and `barcode` have width of 512, `arrow-left` has width of 448. All icons have a height of 512.
#### Aliases
In addition to `icons`, another important field in icon set object is `aliases`.
Aliases object is similar to icons object, except that instead of icon body icons reference another icon.
Each entry has the same attributes as an icon, except for `body` and has required attribute `parent` that contains the name of the parent icon. The parent icon must be present in the icon set file.
Example:
```json
{
"prefix": "fa",
"icons": {
"automobile": {
"body": "<path d=\"M480 960q0-66-47-113t-113-47t-113 47t-47 113t47 113t113 47t113-47t47-113zm36-320h1016l-89-357q-2-8-14-17.5t-21-9.5H640q-9 0-21 9.5T605 283zm1372 320q0-66-47-113t-113-47t-113 47t-47 113t47 113t113 47t113-47t47-113zm160-96v384q0 14-9 23t-23 9h-96v128q0 80-56 136t-136 56t-136-56t-56-136v-128H512v128q0 80-56 136t-136 56t-136-56t-56-136v-128H32q-14 0-23-9t-9-23V864q0-93 65.5-158.5T224 640h28l105-419q23-94 104-157.5T640 0h768q98 0 179 63.5T1691 221l105 419h28q93 0 158.5 65.5T2048 864z\" fill=\"currentColor\"/>",
"width": 2048,
"height": 1600
}
},
"aliases": {
"car": {
"parent": "automobile"
}
}
}
```
In this example `car` is an alias of `automobile`, allowing to use the same icon by multiple names.
Another example:
```json
{
"prefix": "fa",
"icons": {
"caret-left": {
"body": "<path d=\"M576 192v896q0 26-19 45t-45 19t-45-19L19 685Q0 666 0 640t19-45l448-448q19-19 45-19t45 19t19 45z\" fill=\"currentColor\"/>",
"width": 576,
"height": 1280
}
},
"aliases": {
"caret-right": {
"parent": "caret-left",
"hFlip": true
}
}
}
```
In this example `caret-right` is alias of `caret-left`, but with additional `hFlip` attribute. It is identical to this:
```json
{
"prefix": "fa",
"icons": {
"caret-left": {
"body": "<path d=\"M576 192v896q0 26-19 45t-45 19t-45-19L19 685Q0 666 0 640t19-45l448-448q19-19 45-19t45 19t19 45z\" fill=\"currentColor\"/>"
},
"caret-right": {
"body": "<path d=\"M576 192v896q0 26-19 45t-45 19t-45-19L19 685Q0 666 0 640t19-45l448-448q19-19 45-19t45 19t19 45z\" fill=\"currentColor\"/>",
"hFlip": true
}
},
"width": 576,
"height": 1280
}
```
##### Merging alias attributes
If both icon and alias have same attribute, following rules apply:
- `rotate`: attributes are combined. For example, icon has rotate = 1, alias has rotate = 1. Result will have rotate = 2. To prevent overflow, if rotate > 3, rotate = rotate - 4.
- `hFlip` and `vFlip`: attributes are combined. For example, icon has hFlip = true, alias also has hFlip = true (icon.hFlip !== alias.hFlip). Result is false. false + false = false, false + true = true, true + true = false.
- other attributes are overwritten.
Example:
```json
{
"prefix": "fa",
"icons": {
"caret-left": {
"body": "<path d=\"M576 192v896q0 26-19 45t-45 19t-45-19L19 685Q0 666 0 640t19-45l448-448q19-19 45-19t45 19t19 45z\" fill=\"currentColor\"/>",
"hFlip": true,
"width": 576,
"height": 1280
}
},
"aliases": {
"caret-left-compact": {
"parent": "caret-left",
"left": 64,
"width": 448
},
"caret-right": {
"parent": "caret-left",
"hFlip": true
}
}
}
```
is identical to:
```json
{
"prefix": "fa",
"icons": {
"caret-left": {
"body": "<path d=\"M576 192v896q0 26-19 45t-45 19t-45-19L19 685Q0 666 0 640t19-45l448-448q19-19 45-19t45 19t19 45z\" fill=\"currentColor\"/>",
"hFlip": true
},
"caret-left-compact": {
"body": "<path d=\"M576 192v896q0 26-19 45t-45 19t-45-19L19 685Q0 666 0 640t19-45l448-448q19-19 45-19t45 19t19 45z\" fill=\"currentColor\"/>",
"hFlip": true, // from caret-left
"left": 64, // overwritten
"width": 448 // overwritten
},
"caret-right": {
"body": "<path d=\"M576 192v896q0 26-19 45t-45 19t-45-19L19 685Q0 666 0 640t19-45l448-448q19-19 45-19t45 19t19 45z\" fill=\"currentColor\"/>"
// hFlip = false, which is default value, so it was removed
}
},
"width": 576,
"height": 1280
}
```
#### Metadata
Icon set files might also contain the metadata. That data is used for browsing icons, searching icons, exporting icon sets as fonts.
Metadata is a combination of several types, represented as type `IconifyMetaData`.
##### Icon set information
Icon set information is part of the metadata, it includes information about an author and license.
Example:
```json
{
"prefix": "dashicons",
"info": {
"name": "Dashicons",
"total": 304,
"author": {
"name": "WordPress",
"url": "https://github.com/WordPress/dashicons"
},
"license": {
"title": "GPL 2.0",
"spdx": "GPL-2.0-only",
"url": "http://www.gnu.org/licenses/gpl-2.0.html"
},
"version": "0.9.0",
"samples": ["shortcode", "businessperson", "editor-expand"],
"height": 20,
"category": "General",
"palette": false
},
"icons": {
// Icons here
}
}
```
##### Info
Information block is part of the metadata, it is used for browsing or searching icon sets. It also contains the license for icons in the icon set and information about the author.
Info block is represented by the type `IconifyInfo`. You can see an example above in "info" property.
IconifyInfo type has the following properties, most of them are optional:
- `name`: string. Icon set name. This field is always set.
- `total`: number. The total number of icons, optional.
- `version`: string. The current version, optional.
- `author`: object. Information about the author, always set. Author information has the following properties:
- `name`: string. Author name. This field is always set.
- `url`: string. Link to icon set, optional. Usually links to GitHub repository.
- `license`: object. Information about the license, always set. License information has the following properties:
- `title`: string. License title. This field is always set.
- `spdx`: string. SPDX license identifier, optional.
- `url`: string. Link to the license, optional.
- `samples`: string[]. Value is an array of icon names that should be used as samples when showing the icon set in an icon sets list.
- `height`: number | number[]. Value is a number or array of numbers, values are pixel grids used in the icon set. If any icons in an icon set do not match the grid, this attribute should not be set.
- `displayHeight`: number. The height value that should be used for displaying samples. Value is a number between 16 and 30 (inclusive).
##### Characters map
Characters map is part of the metadata, it is used for icon sets that are either imported from icon fonts or intended to be exported to icon font.
Characters map allows storing characters for export as well as searching icons by character used in an icon font.
It is a simple object, where the key is character code in hexadecimal form, value is an icon name.
Important: each icon can have multiple characters!
Example:
```json
{
"prefix": "fa",
"icons": {
// Icons here
},
"chars": {
"f000": "glass",
"f001": "music",
"f002": "search",
"f003": "envelope-o",
"f004": "heart",
"f005": "star"
// and so on...
}
}
```
##### Categories
Categories are part of the metadata, used to allow filtering icons when showing the entire icons set.
Categories list is a simple object, where the key is category name, value is the list of icons.
Important: each icon can belong to multiple categories!
```json
{
"prefix": "fa-solid",
"icons": {
// Icons here
},
"categories": {
"Accessibility": [
"american-sign-language-interpreting",
"assistive-listening-systems",
"audio-description",
"blind",
"braille",
"closed-captioning",
"deaf",
"low-vision",
"phone-volume",
"question-circle",
"sign-language",
"tty",
"universal-access",
"wheelchair"
],
"Alert": [
"bell",
"bell-slash",
"exclamation",
"exclamation-circle",
"exclamation-triangle",
"radiation",
"radiation-alt",
"skull-crossbones"
]
// and so on...
}
}
```
##### Themes
Themes are part of the metadata, similar to categories, but using prefixes or suffixes to identify icons that belong to a theme.
This is useful when icon set has variations of icons, such as "baseline-_", "outline-_".
Example:
```json
{
"prefix": "ic",
"icons": {
// Icons here
},
"themes": {
"baseline": {
"title": "Baseline",
"prefix": "baseline-"
},
"outline": {
"title": "Outline",
"prefix": "outline-"
},
"round": {
"title": "Round",
"prefix": "round-"
},
"sharp": {
"title": "Sharp",
"prefix": "sharp-"
},
"twotone": {
"title": "Two-Tone",
"prefix": "twotone-"
}
}
}
```
Each theme can have one of the attributes: `prefix` or `suffix`. The prefix must end with `-`, suffix must start with `-`.
In an example above, all icons that start with "baseline-", such as "baseline-home", are considered part of the "Baseline" theme.
#### All attributes
For an example of full icon set files that include metadata, look in icon set files in `@iconify/json` package or browse it at GitHub: [https://github.com/iconify/collections-json](https://github.com/iconify/collections-json)
For an example of individual icons, look in JavaScript files in NPM packages such as `@iconify/icons-mdi`.
## Usage
This repository is intended to be used with any Iconify packages.
At the moment of writing, multiple Iconify packages are written without TypeScript. At the beginning of the year 2020 plan is to rewrite all of them with TypeScript to make sure data is consistent and avoid duplication, this package will be used for sharing types between Iconify packages.
## License
This package is licensed under MIT license.
`SPDX-License-Identifier: MIT`
Previous versions of this package were dual-licensed under Apache 2.0 and GPL 2.0 licence, which was messy and confusing. This was later changed to MIT for simplicity.
© 2021 - 2022 Vjacheslav Trushkin / Iconify OÜ

23
frontend/node_modules/@iconify/types/package.json generated vendored Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "@iconify/types",
"type": "module",
"description": "Types for Iconify data",
"version": "2.0.0",
"author": "Vjacheslav Trushkin",
"license": "MIT",
"main": "./types.js",
"types": "./types.d.ts",
"bugs": "https://github.com/iconify/iconify/issues",
"homepage": "https://github.com/iconify/iconify",
"repository": {
"type": "git",
"url": "https://github.com/iconify/iconify.git",
"directory": "packages/types"
},
"devDependencies": {
"typescript": "^4.8.2"
},
"scripts": {
"test": "tsc --noEmit --strict --typeRoots '[]' types.d.ts"
}
}

15
frontend/node_modules/@iconify/types/pnpm-lock.yaml generated vendored Normal file
View File

@@ -0,0 +1,15 @@
lockfileVersion: 5.4
specifiers:
typescript: ^4.6.2
devDependencies:
typescript: 4.7.4
packages:
/typescript/4.7.4:
resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true

3
frontend/node_modules/@iconify/types/provider.js generated vendored Normal file
View File

@@ -0,0 +1,3 @@
/**
* Empty file. This repository contains only TypeScript types
*/

3
frontend/node_modules/@iconify/types/types.js generated vendored Normal file
View File

@@ -0,0 +1,3 @@
/**
* Empty file. This repository contains only TypeScript types
*/

View File

@@ -0,0 +1,44 @@
interface RGBColor {
type: 'rgb';
r: number;
g: number;
b: number;
alpha: number;
}
interface HSLColor {
type: 'hsl';
h: number;
s: number;
l: number;
alpha: number;
}
interface LABColor {
type: 'lab';
l: number;
a: number;
b: number;
alpha: number;
}
interface LCHColor {
type: 'lch';
l: number;
c: number;
h: number;
alpha: number;
}
interface FunctionColor {
type: 'function';
func: string;
value: string;
}
interface TransparentColor {
type: 'transparent';
}
interface NoColor {
type: 'none';
}
interface CurrentColor {
type: 'current';
}
type Color = RGBColor | HSLColor | LABColor | LCHColor | FunctionColor | TransparentColor | NoColor | CurrentColor;
export { Color, CurrentColor, FunctionColor, HSLColor, LABColor, LCHColor, NoColor, RGBColor, TransparentColor };

View File

@@ -0,0 +1 @@
export { };

40
frontend/node_modules/@iconify/utils/lib/css/format.js generated vendored Normal file
View File

@@ -0,0 +1,40 @@
const format = {
selectorStart: {
compressed: "{",
compact: " {",
expanded: " {"
},
selectorEnd: {
compressed: "}",
compact: "; }\n",
expanded: ";\n}\n"
},
rule: {
compressed: "{key}:",
compact: " {key}: ",
expanded: "\n {key}: "
}
};
/**
* Format data
*
* Key is selector, value is list of rules
*/
function formatCSS(data, mode = "expanded") {
const results = [];
for (let i = 0; i < data.length; i++) {
const { selector, rules } = data[i];
let entry = (selector instanceof Array ? selector.join(mode === "compressed" ? "," : ", ") : selector) + format.selectorStart[mode];
let firstRule = true;
for (const key in rules) {
if (!firstRule) entry += ";";
entry += format.rule[mode].replace("{key}", key) + rules[key];
firstRule = false;
}
entry += format.selectorEnd[mode];
results.push(entry);
}
return results.join(mode === "compressed" ? "" : "\n");
}
export { formatCSS };

View File

@@ -0,0 +1,5 @@
/**
* Get boolean customisation value from attribute
*/
declare function toBoolean(name: string, value: unknown, defaultValue: boolean): boolean;
export { toBoolean };

View File

@@ -0,0 +1,18 @@
const separator = /[\s,]+/;
/**
* Apply "flip" string to icon customisations
*/
function flipFromString(custom, flip) {
flip.split(separator).forEach((str) => {
switch (str.trim()) {
case "horizontal":
custom.hFlip = true;
break;
case "vertical":
custom.vFlip = true;
break;
}
});
}
export { flipFromString };

View File

@@ -0,0 +1,31 @@
/**
* Get rotation value
*/
function rotateFromString(value, defaultValue = 0) {
const units = value.replace(/^-?[0-9.]*/, "");
function cleanup(value$1) {
while (value$1 < 0) value$1 += 4;
return value$1 % 4;
}
if (units === "") {
const num = parseInt(value);
return isNaN(num) ? 0 : cleanup(num);
} else if (units !== value) {
let split = 0;
switch (units) {
case "%":
split = 25;
break;
case "deg": split = 90;
}
if (split) {
let num = parseFloat(value.slice(0, value.length - units.length));
if (isNaN(num)) return 0;
num = num / split;
return num % 1 === 0 ? cleanup(num) : 0;
}
}
return defaultValue;
}
export { rotateFromString };

View File

@@ -0,0 +1,32 @@
/** Joiner in emoji sequences */
declare const joinerEmoji = 8205;
/** Emoji as icon */
declare const vs16Emoji = 65039;
/** Keycap, preceeded by mandatory VS16 for full emoji */
declare const keycapEmoji = 8419;
/**
* Variations, UTF-32
*
* First value in array is minimum, second value is maximum+1
*/
type EmojiComponentType = 'skin-tone' | 'hair-style';
type Range = [number, number];
declare const emojiComponents: Record<EmojiComponentType, Range>;
/**
* Minimum UTF-32 number
*/
declare const minUTF32 = 65536;
/**
* Codes for UTF-32 characters presented as UTF-16
*
* startUTF32Pair1 <= code < startUTF32Pair2 -> code for first character in pair
* startUTF32Pair2 <= code < endUTF32Pair -> code for second character in pair
*/
declare const startUTF32Pair1 = 55296;
declare const startUTF32Pair2 = 56320;
declare const endUTF32Pair = 57344;
/**
* Emoji version as string
*/
declare const emojiVersion = "17.0";
export { EmojiComponentType, emojiComponents, emojiVersion, endUTF32Pair, joinerEmoji, keycapEmoji, minUTF32, startUTF32Pair1, startUTF32Pair2, vs16Emoji };

View File

@@ -0,0 +1,32 @@
import { IconifyJSON } from "@iconify/types";
/** Parsed icon */
interface PreparedEmojiIcon {
/** Icon name */
icon: string;
/** Emoji sequence as string */
sequence: string;
}
/**
* Parse
*/
interface PreparedEmojiResult {
/** List of icons */
icons: PreparedEmojiIcon[];
/** Regular expression */
regex: string;
}
/**
* Prepare emoji for icons list
*
* Test data should be fetched from 'https://unicode.org/Public/emoji/17.0/emoji-test.txt'
* It is used to detect missing emojis and optimise regular expression
*/
declare function prepareEmojiForIconsList(icons: Record<string, string>, rawTestData?: string): PreparedEmojiResult;
/**
* Prepare emoji for an icon set
*
* Test data should be fetched from 'https://unicode.org/Public/emoji/15.1/emoji-test.txt'
* It is used to detect missing emojis and optimise regular expression
*/
declare function prepareEmojiForIconSet(iconSet: IconifyJSON, rawTestData?: string): PreparedEmojiResult;
export { PreparedEmojiIcon, PreparedEmojiResult, prepareEmojiForIconSet, prepareEmojiForIconsList };

View File

@@ -0,0 +1,74 @@
/**
* Regex in item
*/
interface BaseEmojiItemRegex {
type: 'utf16' | 'sequence' | 'set' | 'optional';
regex: string;
group: boolean;
length: number;
}
interface EmojiItemRegexWithNumbers {
numbers?: number[];
}
interface UTF16EmojiItemRegex extends BaseEmojiItemRegex, Required<EmojiItemRegexWithNumbers> {
type: 'utf16';
group: true;
}
type SequenceEmojiItemRegexItem = UTF16EmojiItemRegex | SetEmojiItemRegex | OptionalEmojiItemRegex;
interface SequenceEmojiItemRegex extends BaseEmojiItemRegex, EmojiItemRegexWithNumbers {
type: 'sequence';
items: SequenceEmojiItemRegexItem[];
}
type SetEmojiItemRegexItem = UTF16EmojiItemRegex | SequenceEmojiItemRegex | OptionalEmojiItemRegex;
interface SetEmojiItemRegex extends BaseEmojiItemRegex, EmojiItemRegexWithNumbers {
type: 'set';
sets: SetEmojiItemRegexItem[];
}
type OptionalEmojiItemRegexItem = UTF16EmojiItemRegex | SequenceEmojiItemRegex | SetEmojiItemRegex;
interface OptionalEmojiItemRegex extends BaseEmojiItemRegex {
type: 'optional';
item: OptionalEmojiItemRegexItem;
group: true;
}
type EmojiItemRegex = UTF16EmojiItemRegex | SequenceEmojiItemRegex | SetEmojiItemRegex | OptionalEmojiItemRegex;
/**
* Wrap regex in group
*/
declare function wrapRegexInGroup(regex: string): string;
/**
* Update UTF16 item, return regex
*/
declare function updateUTF16EmojiRegexItem(item: UTF16EmojiItemRegex): string;
/**
* Create UTF-16 regex
*/
declare function createUTF16EmojiRegexItem(numbers: number[]): UTF16EmojiItemRegex;
/**
* Update sequence regex. Does not update group
*/
declare function updateSequenceEmojiRegexItem(item: SequenceEmojiItemRegex): string;
/**
* Create sequence regex
*/
declare function createSequenceEmojiRegexItem(sequence: EmojiItemRegex[], numbers?: number[]): SequenceEmojiItemRegex;
/**
* Update set regex and group
*/
declare function updateSetEmojiRegexItem(item: SetEmojiItemRegex): string;
/**
* Create set regex
*/
declare function createSetEmojiRegexItem(set: EmojiItemRegex[]): SetEmojiItemRegex;
/**
* Update optional regex
*/
declare function updateOptionalEmojiRegexItem(item: OptionalEmojiItemRegex): string;
/**
* Create optional item
*/
declare function createOptionalEmojiRegexItem(item: EmojiItemRegex): OptionalEmojiItemRegex;
/**
* Clone item
*/
declare function cloneEmojiRegexItem<T extends BaseEmojiItemRegex>(item: T, shallow?: boolean): T;
export { EmojiItemRegex, OptionalEmojiItemRegex, SequenceEmojiItemRegex, SetEmojiItemRegex, SetEmojiItemRegexItem, UTF16EmojiItemRegex, cloneEmojiRegexItem, createOptionalEmojiRegexItem, createSequenceEmojiRegexItem, createSetEmojiRegexItem, createUTF16EmojiRegexItem, updateOptionalEmojiRegexItem, updateSequenceEmojiRegexItem, updateSetEmojiRegexItem, updateUTF16EmojiRegexItem, wrapRegexInGroup };

View File

@@ -0,0 +1,204 @@
/**
* Convert number to string
*/
function toString(number) {
if (number < 255) {
if (number > 32 && number < 127) {
const char = String.fromCharCode(number);
if (number > 47 && number < 58 || number > 64 && number < 91 || number > 94 && number < 123) return char;
return "\\" + char;
}
return "\\x" + (number < 16 ? "0" : "") + number.toString(16).toUpperCase();
}
return "\\u" + number.toString(16).toUpperCase();
}
/**
* Typescript stuff
*/
function assertNever(v) {}
/**
* Wrap regex in group
*/
function wrapRegexInGroup(regex) {
return "(?:" + regex + ")";
}
/**
* Update UTF16 item, return regex
*/
function updateUTF16EmojiRegexItem(item) {
const numbers = item.numbers;
if (numbers.length === 1) {
const num = numbers[0];
return item.regex = toString(num);
}
numbers.sort((a, b) => a - b);
const chars = [];
let range = null;
const addRange = () => {
if (range) {
const { start, last, numbers: numbers$1 } = range;
range = null;
if (last > start + 1) chars.push(toString(start) + "-" + toString(last));
else for (let i = 0; i < numbers$1.length; i++) chars.push(toString(numbers$1[i]));
}
};
for (let i = 0; i < numbers.length; i++) {
const num = numbers[i];
if (range) {
if (range.last === num) continue;
if (range.last === num - 1) {
range.numbers.push(num);
range.last = num;
continue;
}
}
addRange();
range = {
start: num,
last: num,
numbers: [num]
};
}
addRange();
if (!chars.length) throw new Error("Unexpected empty range");
return item.regex = "[" + chars.join("") + "]";
}
/**
* Create UTF-16 regex
*/
function createUTF16EmojiRegexItem(numbers) {
const result = {
type: "utf16",
regex: "",
numbers,
length: 1,
group: true
};
updateUTF16EmojiRegexItem(result);
return result;
}
/**
* Update sequence regex. Does not update group
*/
function updateSequenceEmojiRegexItem(item) {
return item.regex = item.items.map((childItem) => {
if (!childItem.group && childItem.type === "set") return wrapRegexInGroup(childItem.regex);
return childItem.regex;
}).join("");
}
/**
* Create sequence regex
*/
function createSequenceEmojiRegexItem(sequence, numbers) {
let items = [];
sequence.forEach((item) => {
if (item.type === "sequence") items = items.concat(item.items);
else items.push(item);
});
if (!items.length) throw new Error("Empty sequence");
const result = {
type: "sequence",
items,
regex: "",
length: items.reduce((length, item) => item.length + length, 0),
group: false
};
if (sequence.length === 1) {
const firstItem = sequence[0];
result.group = firstItem.group;
if (firstItem.type !== "optional") {
const numbers$1 = firstItem.numbers;
if (numbers$1) result.numbers = numbers$1;
}
}
if (numbers) result.numbers = numbers;
updateSequenceEmojiRegexItem(result);
return result;
}
/**
* Update set regex and group
*/
function updateSetEmojiRegexItem(item) {
if (item.sets.length === 1) {
const firstItem = item.sets[0];
item.group = firstItem.group;
return item.regex = firstItem.regex;
}
item.group = false;
return item.regex = item.sets.map((childItem) => childItem.regex).join("|");
}
/**
* Create set regex
*/
function createSetEmojiRegexItem(set) {
let sets = [];
let numbers = [];
set.forEach((item) => {
if (item.type === "set") sets = sets.concat(item.sets);
else sets.push(item);
if (numbers) if (item.type === "optional" || !item.numbers) numbers = null;
else numbers = [...numbers, ...item.numbers];
});
sets.sort((a, b) => {
if (a.length === b.length) return a.regex.localeCompare(b.regex);
return b.length - a.length;
});
const result = {
type: "set",
sets,
regex: "",
length: sets.reduce((length, item) => length ? Math.min(length, item.length) : item.length, 0),
group: false
};
if (numbers) result.numbers = numbers;
if (set.length === 1) result.group = set[0].group;
updateSetEmojiRegexItem(result);
return result;
}
/**
* Update optional regex
*/
function updateOptionalEmojiRegexItem(item) {
const childItem = item.item;
return item.regex = (childItem.group ? childItem.regex : wrapRegexInGroup(childItem.regex)) + "?";
}
/**
* Create optional item
*/
function createOptionalEmojiRegexItem(item) {
if (item.type === "optional") return item;
const result = {
type: "optional",
item,
regex: "",
length: item.length,
group: true
};
updateOptionalEmojiRegexItem(result);
return result;
}
/**
* Clone item
*/
function cloneEmojiRegexItem(item, shallow = false) {
const result = { ...item };
if (result.type !== "optional" && result.numbers) result.numbers = [...result.numbers];
switch (result.type) {
case "utf16": break;
case "sequence":
if (shallow) result.items = [...result.items];
else result.items = result.items.map((item$1) => cloneEmojiRegexItem(item$1, false));
break;
case "set":
if (shallow) result.sets = [...result.sets];
else result.sets = result.sets.map((item$1) => cloneEmojiRegexItem(item$1, false));
break;
case "optional":
if (!shallow) result.item = cloneEmojiRegexItem(result.item, false);
break;
default: assertNever(result);
}
return result;
}
export { cloneEmojiRegexItem, createOptionalEmojiRegexItem, createSequenceEmojiRegexItem, createSetEmojiRegexItem, createUTF16EmojiRegexItem, updateOptionalEmojiRegexItem, updateSequenceEmojiRegexItem, updateSetEmojiRegexItem, updateUTF16EmojiRegexItem, wrapRegexInGroup };

View File

@@ -0,0 +1,20 @@
/**
* Create optimised regex
*/
declare function createOptimisedRegexForEmojiSequences(sequences: number[][]): string;
/**
* Create optimised regex for emojis
*
* First parameter is array of emojis, entry can be either list of
* code points or emoji sequence as a string
*
* Examples of acceptable strings (case insensitive):
* '1F636 200D 1F32B FE0F' - space separated UTF32 sequence
* '1f636-200d-1f32b-fe0f' - dash separated UTF32 sequence
* 'd83d-de36-200d-d83c-df2b-fe0f' - dash separated UTF16 sequence
* '\\uD83D\\uDE36\\u200D\\uD83C\\uDF2B\\uFE0F' - UTF16 sequence escaped with '\\u'
*
* All examples above refer to the same emoji and will generate the same regex result
*/
declare function createOptimisedRegex(emojis: (string | number[])[]): string;
export { createOptimisedRegex, createOptimisedRegexForEmojiSequences };

View File

@@ -0,0 +1,14 @@
import { EmojiItemRegex, OptionalEmojiItemRegex, SequenceEmojiItemRegex, SetEmojiItemRegex, UTF16EmojiItemRegex } from "./base.js";
/**
* Create regex item for set of numbers
*/
declare function createEmojiRegexItemForNumbers(numbers: number[]): UTF16EmojiItemRegex | SequenceEmojiItemRegex | SetEmojiItemRegex;
/**
* Create sequence of numbers
*/
declare function createRegexForNumbersSequence(numbers: number[], optionalVariations?: boolean): SequenceEmojiItemRegex | UTF16EmojiItemRegex | OptionalEmojiItemRegex;
/**
* Attempt to optimise numbers in a set
*/
declare function optimiseNumbersSet(set: SetEmojiItemRegex): EmojiItemRegex;
export { createEmojiRegexItemForNumbers, createRegexForNumbersSequence, optimiseNumbersSet };

View File

@@ -0,0 +1,18 @@
import { EmojiItemRegex } from "./base.js";
/**
* Tree item
*/
interface TreeItem {
regex: EmojiItemRegex;
end?: true;
children?: TreeItem[];
}
/**
* Create tree
*/
declare function createEmojisTree(sequences: number[][]): TreeItem[];
/**
* Parse tree
*/
declare function parseEmojiTree(items: TreeItem[]): EmojiItemRegex;
export { createEmojisTree, parseEmojiTree };

View File

@@ -0,0 +1,81 @@
import { joinerEmoji } from "../data.js";
import { convertEmojiSequenceToUTF32 } from "../convert.js";
import { splitEmojiSequences } from "../cleanup.js";
import { createOptionalEmojiRegexItem, createSequenceEmojiRegexItem, createSetEmojiRegexItem, createUTF16EmojiRegexItem } from "./base.js";
import { createRegexForNumbersSequence } from "./numbers.js";
import { mergeSimilarItemsInSet } from "./similar.js";
/**
* Create tree
*/
function createEmojisTree(sequences) {
const root = [];
for (let i = 0; i < sequences.length; i++) {
const split = splitEmojiSequences(convertEmojiSequenceToUTF32(sequences[i]));
let parent = root;
for (let j = 0; j < split.length; j++) {
const regex = createRegexForNumbersSequence(split[j]);
let item;
const match = parent.find((item$1) => item$1.regex.regex === regex.regex);
if (!match) {
item = { regex };
parent.push(item);
} else item = match;
if (j === split.length - 1) {
item.end = true;
break;
}
parent = item.children || (item.children = []);
}
}
return root;
}
/**
* Parse tree
*/
function parseEmojiTree(items) {
function mergeParsedChildren(items$1) {
const parsedItems = [];
const mapWithoutEnd = Object.create(null);
const mapWithEnd = Object.create(null);
for (let i = 0; i < items$1.length; i++) {
const item = items$1[i];
const children = item.children;
if (children) {
const fullItem = item;
const target = item.end ? mapWithEnd : mapWithoutEnd;
const regex = children.regex;
if (!target[regex]) target[regex] = [fullItem];
else target[regex].push(fullItem);
} else parsedItems.push(item.regex);
}
[mapWithEnd, mapWithoutEnd].forEach((source) => {
for (const regex in source) {
const items$2 = source[regex];
const firstItem = items$2[0];
let childSequence = [createUTF16EmojiRegexItem([joinerEmoji]), firstItem.children];
if (firstItem.end) childSequence = [createOptionalEmojiRegexItem(createSequenceEmojiRegexItem(childSequence))];
let mergedRegex;
if (items$2.length === 1) mergedRegex = firstItem.regex;
else mergedRegex = mergeSimilarItemsInSet(createSetEmojiRegexItem(items$2.map((item) => item.regex)));
const sequence = createSequenceEmojiRegexItem([mergedRegex, ...childSequence]);
parsedItems.push(sequence);
}
});
if (parsedItems.length === 1) return parsedItems[0];
return mergeSimilarItemsInSet(createSetEmojiRegexItem(parsedItems));
}
function parseItemChildren(item) {
const result = {
regex: item.regex,
end: !!item.end
};
const children = item.children;
if (!children) return result;
result.children = mergeParsedChildren(children.map(parseItemChildren));
return result;
}
return mergeParsedChildren(items.map(parseItemChildren));
}
export { createEmojisTree, parseEmojiTree };

View File

@@ -0,0 +1,14 @@
import { EmojiRegexMatch } from "./find.js";
/**
* Callback for replacing emoji in text
*
* Returns text to replace emoji with, undefined to skip replacement
*/
type FindAndReplaceEmojisInTextCallback = (match: EmojiRegexMatch, prev: string) => string | undefined;
/**
* Find and replace emojis in text
*
* Returns null if nothing was replaced
*/
declare function findAndReplaceEmojisInText(regexp: string | RegExp | (string | RegExp)[], content: string, callback: FindAndReplaceEmojisInTextCallback): string | null;
export { FindAndReplaceEmojisInTextCallback, findAndReplaceEmojisInText };

View File

@@ -0,0 +1,41 @@
import { EmojiComponentType } from "../data.js";
import { EmojiTestData, EmojiTestDataItem } from "./parse.js";
interface EmojiTestDataComponentsMap {
converted: Map<number, string>;
items: Map<string | number, EmojiTestDataItem>;
names: Map<string | number, string>;
types: Record<string, EmojiComponentType>;
keywords: Record<string, string>;
}
/**
* Map components from test data
*/
declare function mapEmojiTestDataComponents(testSequences: EmojiTestData): EmojiTestDataComponentsMap;
/**
* Sequence with components
*/
type EmojiSequenceWithComponents = (EmojiComponentType | number)[];
/**
* Convert to string
*/
declare function emojiSequenceWithComponentsToString(sequence: EmojiSequenceWithComponents): string;
/**
* Entry in sequence
*/
interface EmojiSequenceComponentEntry {
index: number;
type: EmojiComponentType;
}
/**
* Find variations in sequence
*/
declare function findEmojiComponentsInSequence(sequence: number[]): EmojiSequenceComponentEntry[];
/**
* Component values
*/
type EmojiSequenceComponentValues = Partial<Record<EmojiComponentType, number[]>>;
/**
* Replace components in sequence
*/
declare function replaceEmojiComponentsInCombinedSequence(sequence: EmojiSequenceWithComponents, values: EmojiSequenceComponentValues): number[];
export { EmojiSequenceComponentEntry, EmojiSequenceComponentValues, EmojiSequenceWithComponents, EmojiTestDataComponentsMap, emojiSequenceWithComponentsToString, findEmojiComponentsInSequence, mapEmojiTestDataComponents, replaceEmojiComponentsInCombinedSequence };

View File

@@ -0,0 +1,21 @@
import { BaseEmojiTestDataItem, EmojiTestData, EmojiTestDataItem } from "./parse.js";
import { EmojiSequenceWithComponents, EmojiTestDataComponentsMap } from "./components.js";
import { SplitEmojiName } from "./name.js";
/**
* Similar test data items as one item
*/
interface CombinedEmojiTestDataItem extends BaseEmojiTestDataItem {
name: SplitEmojiName;
sequenceKey: string;
sequence: EmojiSequenceWithComponents;
}
type SimilarEmojiTestData = Record<string, CombinedEmojiTestDataItem>;
/**
* Find components in item, generate CombinedEmojiTestDataItem
*/
declare function findComponentsInEmojiTestItem(item: EmojiTestDataItem, componentsData: EmojiTestDataComponentsMap): CombinedEmojiTestDataItem;
/**
* Combine similar items in one iteratable item
*/
declare function combineSimilarEmojiTestData(data: EmojiTestData, componentsData?: EmojiTestDataComponentsMap): SimilarEmojiTestData;
export { CombinedEmojiTestDataItem, SimilarEmojiTestData, combineSimilarEmojiTestData, findComponentsInEmojiTestItem };

View File

@@ -0,0 +1,26 @@
import { IconifyInfo } from "@iconify/types";
/**
* Item provided by API or loaded from collections.json, slightly different from IconifyInfo
*/
interface LegacyIconifyInfo {
name: string;
total?: number;
version?: string;
author?: string;
url?: string;
license?: string;
licenseURL?: string;
licenseSPDX?: string;
samples?: string[];
height?: number | number[];
displayHeight?: number;
samplesHeight?: number;
category?: string;
palette?: 'Colorless' | 'Colorful';
hidden?: boolean;
}
/**
* Convert data to valid CollectionInfo
*/
declare function convertIconSetInfo(data: unknown, expectedPrefix?: string): IconifyInfo | null;
export { LegacyIconifyInfo, convertIconSetInfo };

View File

@@ -0,0 +1,126 @@
const minDisplayHeight = 16;
const maxDisplayHeight = 24;
/**
* Check if displayHeight value is valid, returns 0 on failure
*/
function validateDisplayHeight(value) {
while (value < minDisplayHeight) value *= 2;
while (value > maxDisplayHeight) value /= 2;
return value === Math.round(value) && value >= minDisplayHeight && value <= maxDisplayHeight ? value : 0;
}
/**
* Convert data to valid CollectionInfo
*/
function convertIconSetInfo(data, expectedPrefix = "") {
if (typeof data !== "object" || data === null) return null;
const source = data;
const getSourceNestedString = (field, key, defaultValue = "") => {
if (typeof source[field] !== "object") return defaultValue;
const obj = source[field];
return typeof obj[key] === "string" ? obj[key] : defaultValue;
};
let name;
if (typeof source.name === "string") name = source.name;
else if (typeof source.title === "string") name = source.title;
else return null;
if (expectedPrefix !== "" && typeof source.prefix === "string" && source.prefix !== expectedPrefix) return null;
const info = { name };
switch (typeof source.total) {
case "number":
info.total = source.total;
break;
case "string": {
const num = parseInt(source.total);
if (num > 0) info.total = num;
break;
}
}
if (typeof source.version === "string") info.version = source.version;
info.author = { name: getSourceNestedString("author", "name", typeof source.author === "string" ? source.author : "") };
if (typeof source.author === "object") {
const sourceAuthor = source.author;
if (typeof sourceAuthor.url === "string") info.author.url = sourceAuthor.url;
}
info.license = { title: getSourceNestedString("license", "title", typeof source.license === "string" ? source.license : "") };
if (typeof source.license === "object") {
const sourceLicense = source.license;
if (typeof sourceLicense.spdx === "string") info.license.spdx = sourceLicense.spdx;
if (typeof sourceLicense.url === "string") info.license.url = sourceLicense.url;
}
if (source.samples instanceof Array) {
const samples = [];
source.samples.forEach((item) => {
if (typeof item === "string" && !samples.includes(item)) samples.push(item);
});
if (samples.length) info.samples = samples;
}
if (typeof source.height === "number" || typeof source.height === "string") {
const num = parseInt(source.height);
if (num > 0) info.height = num;
}
if (source.height instanceof Array) {
source.height.forEach((item) => {
const num = parseInt(item);
if (num > 0) {
if (!(info.height instanceof Array)) info.height = [];
info.height.push(num);
}
});
switch (info.height.length) {
case 0:
delete info.height;
break;
case 1: info.height = info.height[0];
}
}
if (typeof info.height === "number") {
const displayHeight = validateDisplayHeight(info.height);
if (displayHeight && displayHeight !== info.height) info.displayHeight = displayHeight;
}
["samplesHeight", "displayHeight"].forEach((prop) => {
const value = source[prop];
if (typeof value === "number" || typeof value === "string") {
const displayHeight = validateDisplayHeight(parseInt(value));
if (displayHeight) info.displayHeight = displayHeight;
}
});
if (typeof source.category === "string") info.category = source.category;
if (source.tags instanceof Array) info.tags = source.tags;
switch (typeof source.palette) {
case "boolean":
info.palette = source.palette;
break;
case "string":
switch (source.palette.toLowerCase()) {
case "colorless":
case "false":
info.palette = false;
break;
case "colorful":
case "true": info.palette = true;
}
break;
}
if (source.hidden === true) info.hidden = true;
Object.keys(source).forEach((key) => {
const value = source[key];
if (typeof value !== "string") return;
switch (key) {
case "url":
case "uri":
info.author.url = value;
break;
case "licenseURL":
case "licenseURI":
info.license.url = value;
break;
case "licenseID":
case "licenseSPDX":
info.license.spdx = value;
break;
}
});
return info;
}
export { convertIconSetInfo };

View File

@@ -0,0 +1,8 @@
import { IconifyJSON } from "@iconify/types";
/**
* Expand minified icon set
*
* Opposite of minifyIconSet() from ./minify.ts
*/
declare function expandIconSet(data: IconifyJSON): void;
export { expandIconSet };

View File

@@ -0,0 +1,10 @@
import { ExtendedIconifyIcon, IconifyJSON } from "@iconify/types";
/**
* Get icon data, using prepared aliases tree
*/
declare function internalGetIconData(data: IconifyJSON, name: string, tree: string[]): ExtendedIconifyIcon;
/**
* Get data for icon
*/
declare function getIconData(data: IconifyJSON, name: string): ExtendedIconifyIcon | null;
export { getIconData, internalGetIconData };

View File

@@ -0,0 +1,38 @@
import { defaultIconDimensions } from "../icon/defaults.js";
import { getIconsTree } from "./tree.js";
/**
* Optional properties that must be copied when copying icon set
*/
const propsToCopy = Object.keys(defaultIconDimensions).concat(["provider"]);
/**
* Extract icons from icon set
*/
function getIcons(data, names, not_found) {
const icons = Object.create(null);
const aliases = Object.create(null);
const result = {
prefix: data.prefix,
icons
};
const sourceIcons = data.icons;
const sourceAliases = data.aliases || Object.create(null);
if (data.lastModified) result.lastModified = data.lastModified;
const tree = getIconsTree(data, names);
let empty = true;
for (const name in tree) if (!tree[name]) {
if (not_found && names.indexOf(name) !== -1) (result.not_found || (result.not_found = [])).push(name);
} else if (sourceIcons[name]) {
icons[name] = { ...sourceIcons[name] };
empty = false;
} else {
aliases[name] = { ...sourceAliases[name] };
result.aliases = aliases;
}
propsToCopy.forEach((attr) => {
if (attr in data) result[attr] = data[attr];
});
return empty && not_found !== true ? null : result;
}
export { getIcons, propsToCopy };

View File

@@ -0,0 +1,43 @@
import { IconifyJSON } from "@iconify/types";
/**
* Minify icon set
*
* Function finds common values for few numeric properties, such as 'width' and 'height' (see defaultIconDimensions keys for list of properties),
* removes entries from icons and sets default entry in root of icon set object.
*
* For example, this:
* {
* icons: {
* foo: {
* body: '<g />',
* width: 24
* },
* bar: {
* body: '<g />',
* width: 24
* },
* baz: {
* body: '<g />',
* width: 16
* }
* }
* }
* is changed to this:
* {
* icons: {
* foo: {
* body: '<g />'
* },
* bar: {
* body: '<g />'
* },
* baz: {
* body: '<g />',
* width: 16
* }
* },
* width: 24
* }
*/
declare function minifyIconSet(data: IconifyJSON): void;
export { minifyIconSet };

View File

@@ -0,0 +1,19 @@
import { ExtendedIconifyIcon, IconifyJSON } from "@iconify/types";
/**
* Callback to call for each icon.
*
* If data === null, icon is missing.
*/
type SplitIconSetCallback = (name: string, data: ExtendedIconifyIcon | null) => unknown;
type SplitIconSetAsyncCallback = (name: string, data: ExtendedIconifyIcon | null) => Promise<unknown>;
/**
* Extract icons from an icon set
*
* Returns list of icons that were found in icon set
*/
declare function parseIconSet(data: IconifyJSON, callback: SplitIconSetCallback): string[];
/**
* Async version of parseIconSet()
*/
declare function parseIconSetAsync(data: IconifyJSON, callback: SplitIconSetAsyncCallback): Promise<string[]>;
export { SplitIconSetAsyncCallback, SplitIconSetCallback, parseIconSet, parseIconSetAsync };

View File

@@ -0,0 +1,48 @@
import { getIconsTree } from "./tree.js";
import { internalGetIconData } from "./get-icon.js";
/**
* Extract icons from an icon set
*
* Returns list of icons that were found in icon set
*/
function parseIconSet(data, callback) {
const names = [];
if (typeof data !== "object" || typeof data.icons !== "object") return names;
if (data.not_found instanceof Array) data.not_found.forEach((name) => {
callback(name, null);
names.push(name);
});
const tree = getIconsTree(data);
for (const name in tree) {
const item = tree[name];
if (item) {
callback(name, internalGetIconData(data, name, item));
names.push(name);
}
}
return names;
}
/**
* Async version of parseIconSet()
*/
async function parseIconSetAsync(data, callback) {
const names = [];
if (typeof data !== "object" || typeof data.icons !== "object") return names;
if (data.not_found instanceof Array) for (let i = 0; i < data.not_found.length; i++) {
const name = data.not_found[i];
await callback(name, null);
names.push(name);
}
const tree = getIconsTree(data);
for (const name in tree) {
const item = tree[name];
if (item) {
await callback(name, internalGetIconData(data, name, item));
names.push(name);
}
}
return names;
}
export { parseIconSet, parseIconSetAsync };

View File

@@ -0,0 +1,26 @@
/** Default values for dimensions */
const defaultIconDimensions = Object.freeze({
left: 0,
top: 0,
width: 16,
height: 16
});
/** Default values for transformations */
const defaultIconTransformations = Object.freeze({
rotate: 0,
vFlip: false,
hFlip: false
});
/** Default values for all optional IconifyIcon properties */
const defaultIconProps = Object.freeze({
...defaultIconDimensions,
...defaultIconTransformations
});
/** Default values for all properties used in ExtendedIconifyIcon */
const defaultExtendedIconProps = Object.freeze({
...defaultIconProps,
body: "",
hidden: false
});
export { defaultExtendedIconProps, defaultIconDimensions, defaultIconProps, defaultIconTransformations };

18
frontend/node_modules/@iconify/utils/lib/icon/merge.js generated vendored Normal file
View File

@@ -0,0 +1,18 @@
import { defaultExtendedIconProps, defaultIconTransformations } from "./defaults.js";
import { mergeIconTransformations } from "./transformations.js";
/**
* Merge icon and alias
*
* Can also be used to merge default values and icon
*/
function mergeIconData(parent, child) {
const result = mergeIconTransformations(parent, child);
for (const key in defaultExtendedIconProps) if (key in defaultIconTransformations) {
if (key in parent && !(key in result)) result[key] = defaultIconTransformations[key];
} else if (key in child) result[key] = child[key];
else if (key in parent) result[key] = parent[key];
return result;
}
export { mergeIconData };

View File

@@ -0,0 +1,11 @@
import { SVGViewBox } from "../svg/viewbox.js";
import { IconifyIcon } from "@iconify/types";
/**
* Make icon square
*/
declare function makeIconSquare(icon: Required<IconifyIcon>): Required<IconifyIcon>;
/**
* Make icon viewBox square
*/
declare function makeViewBoxSquare(viewBox: SVGViewBox): SVGViewBox;
export { makeIconSquare, makeViewBoxSquare };

View File

@@ -0,0 +1,34 @@
/**
* Make icon square
*/
function makeIconSquare(icon) {
if (icon.width !== icon.height) {
const max = Math.max(icon.width, icon.height);
return {
...icon,
width: max,
height: max,
left: icon.left - (max - icon.width) / 2,
top: icon.top - (max - icon.height) / 2
};
}
return icon;
}
/**
* Make icon viewBox square
*/
function makeViewBoxSquare(viewBox) {
const [left, top, width, height] = viewBox;
if (width !== height) {
const max = Math.max(width, height);
return [
left - (max - width) / 2,
top - (max - height) / 2,
max,
max
];
}
return viewBox;
}
export { makeIconSquare, makeViewBoxSquare };

53
frontend/node_modules/@iconify/utils/lib/index.js generated vendored Normal file
View File

@@ -0,0 +1,53 @@
import { defaultExtendedIconProps, defaultIconDimensions, defaultIconProps, defaultIconTransformations } from "./icon/defaults.js";
import { defaultIconCustomisations, defaultIconSizeCustomisations } from "./customisations/defaults.js";
import { mergeCustomisations } from "./customisations/merge.js";
import { toBoolean } from "./customisations/bool.js";
import { flipFromString } from "./customisations/flip.js";
import { rotateFromString } from "./customisations/rotate.js";
import { matchIconName, stringToIcon, validateIconName } from "./icon/name.js";
import { mergeIconTransformations } from "./icon/transformations.js";
import { mergeIconData } from "./icon/merge.js";
import { makeIconSquare } from "./icon/square.js";
import { getIconsTree } from "./icon-set/tree.js";
import { getIconData } from "./icon-set/get-icon.js";
import { parseIconSet, parseIconSetAsync } from "./icon-set/parse.js";
import { validateIconSet } from "./icon-set/validate.js";
import { quicklyValidateIconSet } from "./icon-set/validate-basic.js";
import { expandIconSet } from "./icon-set/expand.js";
import { minifyIconSet } from "./icon-set/minify.js";
import { getIcons } from "./icon-set/get-icons.js";
import { convertIconSetInfo } from "./icon-set/convert-info.js";
import { calculateSize } from "./svg/size.js";
import { mergeDefsAndContent, splitSVGDefs, wrapSVGContent } from "./svg/defs.js";
import { iconToSVG } from "./svg/build.js";
import { clearIDCache, replaceIDs } from "./svg/id.js";
import { svgToData, svgToURL } from "./svg/url.js";
import { encodeSvgForCss } from "./svg/encode-svg-for-css.js";
import { trimSVG } from "./svg/trim.js";
import { prettifySVG } from "./svg/pretty.js";
import { iconToHTML } from "./svg/html.js";
import { cleanUpInnerHTML } from "./svg/inner-html.js";
import { getSVGViewBox } from "./svg/viewbox.js";
import { buildParsedSVG, convertParsedSVG, parseSVGContent } from "./svg/parse.js";
import { colorKeywords } from "./colors/keywords.js";
import { colorToString, compareColors, stringToColor } from "./colors/index.js";
import { getIconCSS, getIconContentCSS } from "./css/icon.js";
import { getIconsCSS, getIconsContentCSS } from "./css/icons.js";
import { mergeIconProps } from "./loader/utils.js";
import { getCustomIcon } from "./loader/custom.js";
import { searchForIcon } from "./loader/modern.js";
import { loadIcon } from "./loader/loader.js";
import { convertEmojiSequenceToUTF16, convertEmojiSequenceToUTF32, getEmojiCodePoint, getEmojiUnicode, isUTF32SplitNumber, mergeUTF32Numbers, splitUTF32Number } from "./emoji/convert.js";
import { getEmojiSequenceFromString, getUnqualifiedEmojiSequence } from "./emoji/cleanup.js";
import { getEmojiSequenceKeyword, getEmojiSequenceString, getEmojiUnicodeString } from "./emoji/format.js";
import { parseEmojiTestFile } from "./emoji/test/parse.js";
import { getQualifiedEmojiVariations } from "./emoji/test/variations.js";
import { findMissingEmojis } from "./emoji/test/missing.js";
import { createOptimisedRegex, createOptimisedRegexForEmojiSequences } from "./emoji/regex/create.js";
import { prepareEmojiForIconSet, prepareEmojiForIconsList } from "./emoji/parse.js";
import { findAndReplaceEmojisInText } from "./emoji/replace/replace.js";
import { camelToKebab, camelize, pascalize, snakelize } from "./misc/strings.js";
import { commonObjectProps, compareObjects, unmergeObjects } from "./misc/objects.js";
import { sanitiseTitleAttribute } from "./misc/title.js";
export { buildParsedSVG, calculateSize, camelToKebab, camelize, cleanUpInnerHTML, clearIDCache, colorKeywords, colorToString, commonObjectProps, compareColors, compareObjects, convertEmojiSequenceToUTF16, convertEmojiSequenceToUTF32, convertIconSetInfo, convertParsedSVG, createOptimisedRegex, createOptimisedRegexForEmojiSequences, defaultExtendedIconProps, defaultIconCustomisations, defaultIconDimensions, defaultIconProps, defaultIconSizeCustomisations, defaultIconTransformations, encodeSvgForCss, expandIconSet, findAndReplaceEmojisInText, findMissingEmojis, flipFromString, getCustomIcon, getEmojiCodePoint, getEmojiSequenceFromString, getEmojiSequenceKeyword, getEmojiSequenceString, getEmojiUnicode, getEmojiUnicodeString, getIconCSS, getIconContentCSS, getIconData, getIcons, getIconsCSS, getIconsContentCSS, getIconsTree, getQualifiedEmojiVariations, getSVGViewBox, getUnqualifiedEmojiSequence, iconToHTML, iconToSVG, isUTF32SplitNumber, loadIcon, makeIconSquare, matchIconName, mergeCustomisations, mergeDefsAndContent, mergeIconData, mergeIconProps, mergeIconTransformations, mergeUTF32Numbers, minifyIconSet, parseEmojiTestFile, parseIconSet, parseIconSetAsync, parseSVGContent, pascalize, prepareEmojiForIconSet, prepareEmojiForIconsList, prettifySVG, quicklyValidateIconSet, replaceIDs, rotateFromString, sanitiseTitleAttribute, searchForIcon, snakelize, splitSVGDefs, splitUTF32Number, stringToColor, stringToIcon, svgToData, svgToURL, toBoolean, trimSVG, unmergeObjects, validateIconName, validateIconSet, wrapSVGContent };

View File

@@ -0,0 +1,6 @@
import { CustomIconLoader, IconifyLoaderOptions, InlineCollection } from "./types.js";
/**
* Get custom icon from inline collection or using loader
*/
declare function getCustomIcon(custom: CustomIconLoader | InlineCollection, collection: string, icon: string, options?: IconifyLoaderOptions): Promise<string | undefined>;
export { getCustomIcon };

View File

@@ -0,0 +1,32 @@
import { trimSVG } from "../svg/trim.js";
import { mergeIconProps } from "./utils.js";
/**
* Get custom icon from inline collection or using loader
*/
async function getCustomIcon(custom, collection, icon, options) {
let result;
try {
if (typeof custom === "function") result = await custom(icon);
else {
const inline = custom[icon];
result = typeof inline === "function" ? await inline() : inline;
}
} catch (err) {
console.warn(`Failed to load custom icon "${icon}" in "${collection}":`, err);
return;
}
if (result) {
const cleanupIdx = result.indexOf("<svg");
if (cleanupIdx > 0) result = result.slice(cleanupIdx);
const { transform } = options?.customizations ?? {};
result = typeof transform === "function" ? await transform(result, collection, icon) : result;
if (!result.startsWith("<svg")) {
console.warn(`Custom icon "${icon}" in "${collection}" is not a valid SVG`);
return result;
}
return await mergeIconProps(options?.customizations?.trimCustomSvg === true ? trimSVG(result) : result, collection, icon, options, void 0);
}
}
export { getCustomIcon };

View File

@@ -0,0 +1,28 @@
import { warnOnce } from "./warn.js";
import { installPackage } from "@antfu/install-pkg";
import { styleText } from "node:util";
let pending;
const tasks = {};
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function tryInstallPkg(name, autoInstall) {
if (pending) await pending;
if (!tasks[name]) {
console.log(styleText("cyan", `Installing ${name}...`));
if (typeof autoInstall === "function") tasks[name] = pending = autoInstall(name).then(() => sleep(300)).finally(() => {
pending = void 0;
});
else tasks[name] = pending = installPackage(name, {
dev: true,
preferOffline: true
}).then(() => sleep(300)).catch((e) => {
warnOnce(`Failed to install ${name}`);
console.error(e);
}).finally(() => {
pending = void 0;
});
}
return tasks[name];
}
export { tryInstallPkg };

View File

@@ -0,0 +1,3 @@
import { UniversalIconLoader } from "./types.js";
declare const loadIcon: UniversalIconLoader;
export { loadIcon };

View File

@@ -0,0 +1,28 @@
import { getCustomIcon } from "./custom.js";
import { searchForIcon } from "./modern.js";
const loadIcon = async (collection, icon, options) => {
const custom = options?.customCollections?.[collection];
if (custom) if (typeof custom === "function") {
let result;
try {
result = await custom(icon);
} catch (err) {
console.warn(`Failed to load custom icon "${icon}" in "${collection}":`, err);
return;
}
if (result) {
if (typeof result === "string") return await getCustomIcon(() => result, collection, icon, options);
if ("icons" in result) {
const ids = [
icon,
icon.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(),
icon.replace(/([a-z])(\d+)/g, "$1-$2")
];
return await searchForIcon(result, collection, ids, options);
}
}
} else return await getCustomIcon(custom, collection, icon, options);
};
export { loadIcon };

View File

@@ -0,0 +1,42 @@
import { defaultIconCustomisations } from "../customisations/defaults.js";
import { getIconData } from "../icon-set/get-icon.js";
import { calculateSize } from "../svg/size.js";
import { iconToSVG, isUnsetKeyword } from "../svg/build.js";
import { mergeIconProps } from "./utils.js";
async function searchForIcon(iconSet, collection, ids, options) {
let iconData;
const { customize } = options?.customizations ?? {};
for (const id of ids) {
iconData = getIconData(iconSet, id);
if (iconData) {
let defaultCustomizations = { ...defaultIconCustomisations };
if (typeof customize === "function") {
iconData = Object.assign({}, iconData);
defaultCustomizations = customize(defaultCustomizations, iconData, `${collection}:${id}`) ?? defaultCustomizations;
}
const { attributes: { width, height, ...restAttributes }, body } = iconToSVG(iconData, defaultCustomizations);
const scale = options?.scale;
return await mergeIconProps(`<svg >${body}</svg>`, collection, id, options, () => {
return { ...restAttributes };
}, (props) => {
const check = (prop, defaultValue) => {
const propValue = props[prop];
let value;
if (!isUnsetKeyword(propValue)) {
if (propValue) return;
if (typeof scale === "number") {
if (scale) value = calculateSize(defaultValue ?? "1em", scale);
} else value = defaultValue;
}
if (!value) delete props[prop];
else props[prop] = value;
};
check("width", width);
check("height", height);
});
}
}
}
export { searchForIcon };

View File

@@ -0,0 +1,12 @@
import { resolvePath } from "mlly";
/**
* Resolve path to package
*/
async function resolvePathAsync(packageName, cwd) {
try {
return await resolvePath(packageName, { url: cwd });
} catch {}
}
export { resolvePathAsync };

View File

@@ -0,0 +1,63 @@
import { calculateSize } from "../svg/size.js";
import { isUnsetKeyword } from "../svg/build.js";
const svgWidthRegex = /\swidth\s*=\s*["']([\w.]+)["']/;
const svgHeightRegex = /\sheight\s*=\s*["']([\w.]+)["']/;
const svgTagRegex = /<svg\s+/;
function configureSvgSize(svg, props, scale) {
const svgNode = svg.slice(0, svg.indexOf(">"));
const check = (prop, regex) => {
const result = regex.exec(svgNode);
const isSet = result != null;
const propValue = props[prop];
if (!propValue && !isUnsetKeyword(propValue)) {
if (typeof scale === "number") {
if (scale > 0) props[prop] = calculateSize(result?.[1] ?? "1em", scale);
} else if (result) props[prop] = result[1];
}
return isSet;
};
return [check("width", svgWidthRegex), check("height", svgHeightRegex)];
}
async function mergeIconProps(svg, collection, icon, options, propsProvider, afterCustomizations) {
const { scale, addXmlNs = false } = options ?? {};
const { additionalProps = {}, iconCustomizer } = options?.customizations ?? {};
const props = await propsProvider?.() ?? {};
await iconCustomizer?.(collection, icon, props);
Object.keys(additionalProps).forEach((p) => {
const v = additionalProps[p];
if (v !== void 0 && v !== null) props[p] = v;
});
afterCustomizations?.(props);
const [widthOnSvg, heightOnSvg] = configureSvgSize(svg, props, scale);
if (addXmlNs) {
if (!svg.includes("xmlns=") && !props["xmlns"]) props["xmlns"] = "http://www.w3.org/2000/svg";
if (!svg.includes("xmlns:xlink=") && svg.includes("xlink:") && !props["xmlns:xlink"]) props["xmlns:xlink"] = "http://www.w3.org/1999/xlink";
}
const propsToAdd = Object.keys(props).map((p) => p === "width" && widthOnSvg || p === "height" && heightOnSvg ? null : `${p}="${props[p]}"`).filter((p) => p != null);
if (propsToAdd.length) svg = svg.replace(svgTagRegex, `<svg ${propsToAdd.join(" ")} `);
if (options) {
const { defaultStyle, defaultClass } = options;
if (defaultClass && !svg.includes("class=")) svg = svg.replace(svgTagRegex, `<svg class="${defaultClass}" `);
if (defaultStyle && !svg.includes("style=")) svg = svg.replace(svgTagRegex, `<svg style="${defaultStyle}" `);
}
const usedProps = options?.usedProps;
if (usedProps) {
Object.keys(additionalProps).forEach((p) => {
const v = props[p];
if (v !== void 0 && v !== null) usedProps[p] = v;
});
if (typeof props.width !== "undefined" && props.width !== null) usedProps.width = props.width;
if (typeof props.height !== "undefined" && props.height !== null) usedProps.height = props.height;
}
return svg;
}
function getPossibleIconNames(icon) {
return [
icon,
icon.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(),
icon.replace(/([a-z])(\d+)/g, "$1-$2")
];
}
export { getPossibleIconNames, mergeIconProps };

View File

@@ -0,0 +1,2 @@
declare function warnOnce(msg: string): void;
export { warnOnce };

View File

@@ -0,0 +1,61 @@
/** Completely free, no limits */
const freeLicense = {
attribution: false,
commercial: true
};
/** Requires same license for derived works */
const freeSameLicense = {
attribution: false,
commercial: true,
sameLicense: true
};
/** Requires attribution */
const attribLicense = {
attribution: true,
commercial: true
};
/** Requires attribution and same license for derived works */
const attribSameLicense = {
attribution: true,
commercial: true,
sameLicense: true
};
/** Requires attribution and non-commercial use */
const attribNonCommercialLicense = {
attribution: true,
commercial: false
};
/** Requires attribution, non-commercial use and same license for derived works */
const attribNonCommercialSameLicense = {
attribution: true,
commercial: false,
sameLicense: true
};
/**
* Data for open source licenses used by icon sets in `@iconify/json` package and smaller packages
*
* Key is SPDX license identifier
*/
const licensesData = {
"Apache-2.0": freeLicense,
"MIT": freeLicense,
"MPL-2.0": freeLicense,
"CC0-1.0": freeLicense,
"CC-BY-3.0": attribLicense,
"CC-BY-SA-3.0": attribSameLicense,
"CC-BY-4.0": attribLicense,
"CC-BY-SA-4.0": attribSameLicense,
"CC-BY-NC-4.0": attribNonCommercialLicense,
"CC-BY-NC-SA-4.0": attribNonCommercialSameLicense,
"ISC": freeLicense,
"OFL-1.1": freeLicense,
"GPL-2.0-only": freeSameLicense,
"GPL-2.0-or-later": freeSameLicense,
"GPL-3.0": freeSameLicense,
"GPL-3.0-or-later": freeSameLicense,
"Unlicense": freeLicense,
"BSD-2-Clause": freeLicense,
"BSD-3-Clause": freeLicense
};
export { licensesData };

View File

@@ -0,0 +1,17 @@
/**
* Convert string to camelCase
*/
declare function camelize(str: string): string;
/**
* Convert string to PascaleCase
*/
declare function pascalize(str: string): string;
/**
* Convert camelCase string to kebab-case
*/
declare function camelToKebab(key: string): string;
/**
* Convert camelCase string to snake-case
*/
declare function snakelize(str: string): string;
export { camelToKebab, camelize, pascalize, snakelize };

View File

@@ -0,0 +1,27 @@
/**
* Convert string to camelCase
*/
function camelize(str) {
return str.replace(/-([a-z0-9])/g, (g) => g[1].toUpperCase());
}
/**
* Convert string to PascaleCase
*/
function pascalize(str) {
const camel = camelize(str);
return camel.slice(0, 1).toUpperCase() + camel.slice(1);
}
/**
* Convert camelCase string to kebab-case
*/
function camelToKebab(key) {
return key.replace(/:/g, "-").replace(/([A-Z])/g, " $1").trim().split(/\s+/g).join("-").toLowerCase();
}
/**
* Convert camelCase string to snake-case
*/
function snakelize(str) {
return camelToKebab(str).replace(/-/g, "_");
}
export { camelToKebab, camelize, pascalize, snakelize };

View File

@@ -0,0 +1,7 @@
/**
* Sanitises title, removing any unwanted characters that might break XML.
*
* This is a very basic funciton, not full parser.
*/
declare function sanitiseTitleAttribute(content: string): string;
export { sanitiseTitleAttribute };

View File

@@ -0,0 +1,5 @@
/**
* Generate <svg>
*/
declare function iconToHTML(body: string, attributes: Record<string, string>): string;
export { iconToHTML };

55
frontend/node_modules/@iconify/utils/lib/svg/pretty.js generated vendored Normal file
View File

@@ -0,0 +1,55 @@
/**
* Tags to skip
*/
const skipTags = ["script", "style"];
/**
* Prettify SVG
*/
function prettifySVG(content, tab = " ", depth = 0) {
let result = "";
let level = 0;
content = content.replace(/(\s)*\/>/g, " />");
while (content.length > 0) {
const openIndex = content.indexOf("<");
let closeIndex = content.indexOf(">");
if (openIndex === -1 && closeIndex === -1) return result;
if (openIndex === -1 || closeIndex === -1 || closeIndex < openIndex) return null;
const text = content.slice(0, openIndex);
const trimmedText = text.trim();
if (trimmedText.length) if (text.trimStart() !== text && text.trimEnd() !== text) result += trimmedText + "\n" + tab.repeat(level + depth);
else result = result.trim() + text;
content = content.slice(openIndex);
closeIndex -= openIndex;
const lastChar = content.slice(closeIndex - 1, closeIndex);
const isClosing = content.slice(0, 2) === "</";
let isSelfClosing = lastChar === "/" || lastChar === "?";
if (isClosing && isSelfClosing) return null;
const tagName = content.slice(isClosing ? 2 : 1).split(/[\s>]/).shift();
const ignoreTagContent = !isSelfClosing && !isClosing && skipTags.includes(tagName);
if (!ignoreTagContent) {
const nextOpenIndex = content.indexOf("<", 1);
if (nextOpenIndex !== -1 && nextOpenIndex < closeIndex) return null;
}
if (isClosing && tab.length) {
if (result.slice(0 - tab.length) === tab) result = result.slice(0, result.length - tab.length);
}
result += content.slice(0, closeIndex + 1);
content = content.slice(closeIndex + 1);
if (ignoreTagContent) {
const closingIndex = content.indexOf("</" + tagName);
const closingEnd = content.indexOf(">", closingIndex);
if (closingIndex < 0 || closingEnd < 0) return null;
result += content.slice(0, closingEnd + 1);
content = content.slice(closingEnd + 1);
isSelfClosing = true;
}
if (isClosing) {
level--;
if (level < 0) return null;
} else if (!isSelfClosing) level++;
result += "\n" + tab.repeat(level + depth);
}
return level === 0 ? result : null;
}
export { prettifySVG };

16
frontend/node_modules/@iconify/utils/lib/svg/url.d.ts generated vendored Normal file
View File

@@ -0,0 +1,16 @@
/**
* Encode SVG for use in url()
*
* Short alternative to encodeURIComponent() that encodes only stuff used in SVG, generating
* smaller code.
*/
declare function encodeSVGforURL(svg: string): string;
/**
* Generate data: URL from SVG
*/
declare function svgToData(svg: string): string;
/**
* Generate url() from SVG
*/
declare function svgToURL(svg: string): string;
export { encodeSVGforURL, svgToData, svgToURL };