mirror of
https://github.com/simple-icons/simple-icons.git
synced 2024-12-16 01:10:30 +02:00
Fix linting error and refactor .svglintrc.mjs
(#9195)
This commit is contained in:
parent
fe7d231142
commit
720a0e4d53
@ -4,8 +4,5 @@
|
|||||||
},
|
},
|
||||||
"icon-size": {
|
"icon-size": {
|
||||||
"M22.915 8.321c-.642-.997-1.542-1.879-2.672-2.624-2.185-1.436-5.056-2.227-8.084-2.227-1.012 0-2.009.088-2.976.262a9.84 9.84 0 0 0-2.046-1.509C4.378.848 1.947 1.361.719 1.802a.59.59 0 0 0-.229.964c.866.894 2.299 2.66 1.946 4.267C1.067 8.431.324 10.117.324 11.872c0 1.789.742 3.475 2.112 4.873.352 1.607-1.081 3.374-1.947 4.268a.589.589 0 0 0 .229.963c1.228.442 3.659.955 6.418-.421a9.892 9.892 0 0 0 2.046-1.509c.968.174 1.964.262 2.976.262 3.029 0 5.9-.79 8.084-2.226 1.131-.744 2.031-1.626 2.672-2.624.715-1.11 1.077-2.306 1.077-3.552.001-1.279-.361-2.473-1.076-3.585zm-10.881 9.916c-1.309 0-2.558-.169-3.696-.474l-.832.8A7.609 7.609 0 0 1 5.972 19.7a6.033 6.033 0 0 1-2.17.613c.041-.073.078-.147.117-.221.833-1.531 1.059-2.907.674-4.128-1.363-1.071-2.181-2.442-2.181-3.935 0-3.427 4.308-6.206 9.621-6.206 5.313 0 9.622 2.779 9.622 6.206.001 3.429-4.307 6.208-9.621 6.208zM8.85 12.01c0 .777-.635 1.407-1.418 1.407-.783 0-1.418-.63-1.418-1.407s.635-1.407 1.418-1.407c.783 0 1.418.63 1.418 1.407zm4.563 0c0 .777-.635 1.407-1.418 1.407-.783 0-1.418-.63-1.418-1.407s.635-1.407 1.418-1.407c.783 0 1.418.63 1.418 1.407zm4.565 0c0 .777-.635 1.407-1.418 1.407-.783 0-1.418-.63-1.418-1.407s.635-1.407 1.418-1.407c.783 0 1.418.63 1.418 1.407z": "Rocket.Chat"
|
"M22.915 8.321c-.642-.997-1.542-1.879-2.672-2.624-2.185-1.436-5.056-2.227-8.084-2.227-1.012 0-2.009.088-2.976.262a9.84 9.84 0 0 0-2.046-1.509C4.378.848 1.947 1.361.719 1.802a.59.59 0 0 0-.229.964c.866.894 2.299 2.66 1.946 4.267C1.067 8.431.324 10.117.324 11.872c0 1.789.742 3.475 2.112 4.873.352 1.607-1.081 3.374-1.947 4.268a.589.589 0 0 0 .229.963c1.228.442 3.659.955 6.418-.421a9.892 9.892 0 0 0 2.046-1.509c.968.174 1.964.262 2.976.262 3.029 0 5.9-.79 8.084-2.226 1.131-.744 2.031-1.626 2.672-2.624.715-1.11 1.077-2.306 1.077-3.552.001-1.279-.361-2.473-1.076-3.585zm-10.881 9.916c-1.309 0-2.558-.169-3.696-.474l-.832.8A7.609 7.609 0 0 1 5.972 19.7a6.033 6.033 0 0 1-2.17.613c.041-.073.078-.147.117-.221.833-1.531 1.059-2.907.674-4.128-1.363-1.071-2.181-2.442-2.181-3.935 0-3.427 4.308-6.206 9.621-6.206 5.313 0 9.622 2.779 9.622 6.206.001 3.429-4.307 6.208-9.621 6.208zM8.85 12.01c0 .777-.635 1.407-1.418 1.407-.783 0-1.418-.63-1.418-1.407s.635-1.407 1.418-1.407c.783 0 1.418.63 1.418 1.407zm4.563 0c0 .777-.635 1.407-1.418 1.407-.783 0-1.418-.63-1.418-1.407s.635-1.407 1.418-1.407c.783 0 1.418.63 1.418 1.407zm4.565 0c0 .777-.635 1.407-1.418 1.407-.783 0-1.418-.63-1.418-1.407s.635-1.407 1.418-1.407c.783 0 1.418.63 1.418 1.407z": "Rocket.Chat"
|
||||||
},
|
|
||||||
"ineffective-segments": {
|
|
||||||
"M12 1.25l6.75 6.637V2L12 1.25zm0 0l-6.05 7h12.1l-6.05-7zm0 0L5.25 2v5.887L12 1.25zM5.25 2L0 9l4.416-.68L5.25 2zM0 9l11.959 13.703.008-.014L4.443 9H0zm18.75-7l.834 6.32L24 9l-5.25-7zM24 9h-4.506l-7.523 13.69.029.06L24 9zM12 22.75l-.031-.057-.008.012.039.045zM5.436 9l6.533 13.686L18.564 9H5.436Z": "Sketch"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
426
.svglintrc.mjs
426
.svglintrc.mjs
@ -28,6 +28,7 @@ const svglintIgnores = JSON.parse(fs.readFileSync(svglintIgnoredFile, 'utf8'));
|
|||||||
const svgRegexp =
|
const svgRegexp =
|
||||||
/^<svg( [^\s]*=".*"){3}><title>.*<\/title><path d=".*"\/><\/svg>$/;
|
/^<svg( [^\s]*=".*"){3}><title>.*<\/title><path d=".*"\/><\/svg>$/;
|
||||||
const negativeZerosRegexp = /-0(?=[^\.]|[\s\d\w]|$)/g;
|
const negativeZerosRegexp = /-0(?=[^\.]|[\s\d\w]|$)/g;
|
||||||
|
const svgPathRegexp = /^[Mm][MmZzLlHhVvCcSsQqTtAaEe0-9\-,. ]+$/;
|
||||||
|
|
||||||
const iconSize = 24;
|
const iconSize = 24;
|
||||||
const iconFloatPrecision = 3;
|
const iconFloatPrecision = 3;
|
||||||
@ -105,14 +106,17 @@ const getTitleTextIndex = (svgFileContent) => {
|
|||||||
const hexadecimalToDecimal = (hex) => {
|
const hexadecimalToDecimal = (hex) => {
|
||||||
let result = 0,
|
let result = 0,
|
||||||
digitValue;
|
digitValue;
|
||||||
hex = hex.toLowerCase();
|
for (const digit of hex.toLowerCase()) {
|
||||||
for (var i = 0; i < hex.length; i++) {
|
digitValue = '0123456789abcdefgh'.indexOf(digit);
|
||||||
digitValue = '0123456789abcdefgh'.indexOf(hex[i]);
|
|
||||||
result = result * 16 + digitValue;
|
result = result * 16 + digitValue;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const maybeShortenedWithEllipsis = (str) => {
|
||||||
|
return str.length > 20 ? `${str.substring(0, 20)}...` : str;
|
||||||
|
};
|
||||||
|
|
||||||
if (updateIgnoreFile) {
|
if (updateIgnoreFile) {
|
||||||
process.on('exit', () => {
|
process.on('exit', () => {
|
||||||
// ensure object output order is consistent due to async svglint processing
|
// ensure object output order is consistent due to async svglint processing
|
||||||
@ -154,7 +158,8 @@ export default {
|
|||||||
},
|
},
|
||||||
attr: [
|
attr: [
|
||||||
{
|
{
|
||||||
// ensure that the SVG elm has the appropriate attrs alphabetically ordered
|
// ensure that the SVG element has the appropriate attributes
|
||||||
|
// alphabetically ordered
|
||||||
role: 'img',
|
role: 'img',
|
||||||
viewBox: `0 0 ${iconSize} ${iconSize}`,
|
viewBox: `0 0 ${iconSize} ${iconSize}`,
|
||||||
xmlns: 'http://www.w3.org/2000/svg',
|
xmlns: 'http://www.w3.org/2000/svg',
|
||||||
@ -163,13 +168,14 @@ export default {
|
|||||||
'rule::order': true,
|
'rule::order': true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// ensure that the title elm has the appropriate attr
|
// ensure that the title element has the appropriate attribute
|
||||||
'rule::selector': 'svg > title',
|
'rule::selector': 'svg > title',
|
||||||
'rule::whitelist': true,
|
'rule::whitelist': true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// ensure that the path element only has the 'd' attr (no style, opacity, etc.)
|
// ensure that the path element only has the 'd' attribute
|
||||||
d: /^[,a-zA-Z0-9\. -]+$/,
|
// (no style, opacity, etc.)
|
||||||
|
d: svgPathRegexp,
|
||||||
'rule::selector': 'svg > path',
|
'rule::selector': 'svg > path',
|
||||||
'rule::whitelist': true,
|
'rule::whitelist': true,
|
||||||
},
|
},
|
||||||
@ -190,7 +196,7 @@ export default {
|
|||||||
if (hexadecimalCodepoints.length > 0) {
|
if (hexadecimalCodepoints.length > 0) {
|
||||||
_validCodepointsRepr = false;
|
_validCodepointsRepr = false;
|
||||||
|
|
||||||
hexadecimalCodepoints.forEach((match) => {
|
for (const match of hexadecimalCodepoints) {
|
||||||
const charHexReprIndex =
|
const charHexReprIndex =
|
||||||
getTitleTextIndex(ast.source) + match.index + 1;
|
getTitleTextIndex(ast.source) + match.index + 1;
|
||||||
const charDec = hexadecimalToDecimal(match[1]);
|
const charDec = hexadecimalToDecimal(match[1]);
|
||||||
@ -207,10 +213,11 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reporter.error(
|
reporter.error(
|
||||||
`Hexadecimal representation of encoded character "${match[0]}" found at index ${charHexReprIndex}:` +
|
'Hexadecimal representation of encoded character' +
|
||||||
|
` "${match[0]}" found at index ${charHexReprIndex}:` +
|
||||||
` replace it with "${charRepr}".`,
|
` replace it with "${charRepr}".`,
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// avoid character codepoints as named entities
|
// avoid character codepoints as named entities
|
||||||
@ -218,7 +225,7 @@ export default {
|
|||||||
iconTitleText.matchAll(/&([A-Za-z0-9]+);/g),
|
iconTitleText.matchAll(/&([A-Za-z0-9]+);/g),
|
||||||
);
|
);
|
||||||
if (namedEntitiesCodepoints.length > 0) {
|
if (namedEntitiesCodepoints.length > 0) {
|
||||||
namedEntitiesCodepoints.forEach((match) => {
|
for (const match of namedEntitiesCodepoints) {
|
||||||
const namedEntiyReprIndex =
|
const namedEntiyReprIndex =
|
||||||
getTitleTextIndex(ast.source) + match.index + 1;
|
getTitleTextIndex(ast.source) + match.index + 1;
|
||||||
|
|
||||||
@ -242,11 +249,12 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reporter.error(
|
reporter.error(
|
||||||
`Named entity representation of encoded character "${match[0]}" found at index ${namedEntiyReprIndex}.` +
|
'Named entity representation of encoded character' +
|
||||||
|
` "${match[0]}" found at index ${namedEntiyReprIndex}.` +
|
||||||
` Replace it with ${replacement}.`,
|
` Replace it with ${replacement}.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_validCodepointsRepr) {
|
if (_validCodepointsRepr) {
|
||||||
@ -256,16 +264,15 @@ export default {
|
|||||||
),
|
),
|
||||||
encodedBuf = [];
|
encodedBuf = [];
|
||||||
|
|
||||||
const _indexesToIgnore = [];
|
const indexesToIgnore = [];
|
||||||
for (let m = 0; m < encodingMatches.length; m++) {
|
for (const match of encodingMatches) {
|
||||||
let index = encodingMatches[m].index;
|
for (let r = match.index; r < match.index + match[0].length; r++) {
|
||||||
for (let r = index; r < index + encodingMatches[m][0].length; r++) {
|
indexesToIgnore.push(r);
|
||||||
_indexesToIgnore.push(r);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = iconTitleText.length - 1; i >= 0; i--) {
|
for (let i = iconTitleText.length - 1; i >= 0; i--) {
|
||||||
if (_indexesToIgnore.includes(i)) {
|
if (indexesToIgnore.includes(i)) {
|
||||||
encodedBuf.unshift(iconTitleText[i]);
|
encodedBuf.unshift(iconTitleText[i]);
|
||||||
} else {
|
} else {
|
||||||
// encode all non ascii characters plus "'&<> (XML named entities)
|
// encode all non ascii characters plus "'&<> (XML named entities)
|
||||||
@ -298,32 +305,32 @@ export default {
|
|||||||
|
|
||||||
// check if there are some other encoded characters in decimal notation
|
// check if there are some other encoded characters in decimal notation
|
||||||
// which shouldn't be encoded
|
// which shouldn't be encoded
|
||||||
encodingMatches
|
for (const match of encodingMatches.filter((m) => !isNaN(m[2]))) {
|
||||||
.filter((m) => !isNaN(m[2]))
|
const decimalNumber = parseInt(match[2]);
|
||||||
.forEach((match) => {
|
if (decimalNumber > 127) {
|
||||||
const decimalNumber = parseInt(match[2]);
|
continue;
|
||||||
if (decimalNumber < 128) {
|
}
|
||||||
_validCodepointsRepr = false;
|
_validCodepointsRepr = false;
|
||||||
|
|
||||||
const decimalCodepointCharIndex =
|
const decimalCodepointCharIndex =
|
||||||
getTitleTextIndex(ast.source) + match.index + 1;
|
getTitleTextIndex(ast.source) + match.index + 1;
|
||||||
if (xmlNamedEntitiesCodepoints.includes(decimalNumber)) {
|
if (xmlNamedEntitiesCodepoints.includes(decimalNumber)) {
|
||||||
replacement = `"&${
|
replacement = `"&${
|
||||||
xmlNamedEntities[
|
xmlNamedEntities[
|
||||||
xmlNamedEntitiesCodepoints.indexOf(decimalNumber)
|
xmlNamedEntitiesCodepoints.indexOf(decimalNumber)
|
||||||
]
|
]
|
||||||
};"`;
|
};"`;
|
||||||
} else {
|
} else {
|
||||||
replacement = String.fromCharCode(decimalNumber);
|
replacement = String.fromCodePoint(decimalNumber);
|
||||||
replacement = replacement == '"' ? `'"'` : `"${replacement}"`;
|
replacement = replacement == '"' ? `'"'` : `"${replacement}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
reporter.error(
|
reporter.error(
|
||||||
`Unnecessary encoded character "${match[0]}" found at index ${decimalCodepointCharIndex}:` +
|
`Unnecessary encoded character "${match[0]}" found` +
|
||||||
` replace it with ${replacement}.`,
|
` at index ${decimalCodepointCharIndex}:` +
|
||||||
);
|
` replace it with ${replacement}.`,
|
||||||
}
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
if (_validCodepointsRepr) {
|
if (_validCodepointsRepr) {
|
||||||
const iconName = htmlFriendlyToTitle(iconTitleText);
|
const iconName = htmlFriendlyToTitle(iconTitleText);
|
||||||
@ -338,7 +345,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(reporter, $, ast) => {
|
(reporter, $) => {
|
||||||
reporter.name = 'icon-size';
|
reporter.name = 'icon-size';
|
||||||
|
|
||||||
const iconPath = $.find('path').attr('d');
|
const iconPath = $.find('path').attr('d');
|
||||||
@ -359,7 +366,8 @@ export default {
|
|||||||
}
|
}
|
||||||
} else if (width !== iconSize && height !== iconSize) {
|
} else if (width !== iconSize && height !== iconSize) {
|
||||||
reporter.error(
|
reporter.error(
|
||||||
`Size of <path> must be exactly ${iconSize} in one dimension; the size is currently ${width} x ${height}`,
|
`Size of <path> must be exactly ${iconSize} in one dimension;` +
|
||||||
|
` the size is currently ${width} x ${height}`,
|
||||||
);
|
);
|
||||||
if (updateIgnoreFile) {
|
if (updateIgnoreFile) {
|
||||||
ignoreIcon(reporter.name, iconPath, $);
|
ignoreIcon(reporter.name, iconPath, $);
|
||||||
@ -370,51 +378,36 @@ export default {
|
|||||||
reporter.name = 'icon-precision';
|
reporter.name = 'icon-precision';
|
||||||
|
|
||||||
const iconPath = $.find('path').attr('d');
|
const iconPath = $.find('path').attr('d');
|
||||||
if (!updateIgnoreFile && isIgnored(reporter.name, iconPath)) {
|
const segments = parsePath(iconPath);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const segments = parsePath(iconPath),
|
for (const segment of segments) {
|
||||||
svgFileContent = $.html();
|
|
||||||
|
|
||||||
segments.forEach((segment) => {
|
|
||||||
const precisionMax = Math.max(
|
const precisionMax = Math.max(
|
||||||
...segment.params.slice(1).map(countDecimals),
|
...segment.params.slice(1).map(countDecimals),
|
||||||
);
|
);
|
||||||
if (precisionMax > iconMaxFloatPrecision) {
|
if (precisionMax > iconMaxFloatPrecision) {
|
||||||
let errorMsg = `found ${precisionMax} decimals in segment "${iconPath.substring(
|
let errorMsg =
|
||||||
segment.start,
|
`found ${precisionMax} decimals in segment` +
|
||||||
segment.end,
|
` "${iconPath.substring(segment.start, segment.end)}"`;
|
||||||
)}"`;
|
|
||||||
if (segment.chained) {
|
if (segment.chained) {
|
||||||
let readableChain = iconPath.substring(
|
const readableChain = maybeShortenedWithEllipsis(
|
||||||
segment.chainStart,
|
iconPath.substring(segment.chainStart, segment.chainEnd),
|
||||||
segment.chainEnd,
|
|
||||||
);
|
);
|
||||||
if (readableChain.length > 20) {
|
|
||||||
readableChain = `${readableChain.substring(0, 20)}...`;
|
|
||||||
}
|
|
||||||
errorMsg += ` of chain "${readableChain}"`;
|
errorMsg += ` of chain "${readableChain}"`;
|
||||||
}
|
}
|
||||||
errorMsg += ` at index ${
|
errorMsg += ` at index ${
|
||||||
segment.start + getPathDIndex(svgFileContent)
|
segment.start + getPathDIndex(ast.source)
|
||||||
}`;
|
}`;
|
||||||
reporter.error(
|
reporter.error(
|
||||||
`Maximum precision should not be greater than ${iconMaxFloatPrecision}; ${errorMsg}`,
|
'Maximum precision should not be greater than' +
|
||||||
|
` ${iconMaxFloatPrecision}; ${errorMsg}`,
|
||||||
);
|
);
|
||||||
if (updateIgnoreFile) {
|
|
||||||
ignoreIcon(reporter.name, iconPath, $);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
},
|
},
|
||||||
(reporter, $, ast) => {
|
(reporter, $, ast) => {
|
||||||
reporter.name = 'ineffective-segments';
|
reporter.name = 'ineffective-segments';
|
||||||
|
|
||||||
const iconPath = $.find('path').attr('d');
|
const iconPath = $.find('path').attr('d');
|
||||||
if (!updateIgnoreFile && isIgnored(reporter.name, iconPath)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const segments = parsePath(iconPath);
|
const segments = parsePath(iconPath);
|
||||||
const absSegments = svgpath(iconPath).abs().unshort().segments;
|
const absSegments = svgpath(iconPath).abs().unshort().segments;
|
||||||
@ -451,6 +444,7 @@ export default {
|
|||||||
const isInvalidSegment = (
|
const isInvalidSegment = (
|
||||||
[command, x1Coord, y1Coord, ...rest],
|
[command, x1Coord, y1Coord, ...rest],
|
||||||
index,
|
index,
|
||||||
|
previousSegmentIsZ,
|
||||||
) => {
|
) => {
|
||||||
if (commands.includes(command)) {
|
if (commands.includes(command)) {
|
||||||
// Relative directions (h or v) having a length of 0
|
// Relative directions (h or v) having a length of 0
|
||||||
@ -464,7 +458,9 @@ export default {
|
|||||||
x1Coord === 0 &&
|
x1Coord === 0 &&
|
||||||
y1Coord === 0
|
y1Coord === 0
|
||||||
) {
|
) {
|
||||||
return true;
|
// When the path is closed (z), the new segment can start with
|
||||||
|
// a relative placement (m) as if it were absolute (M)
|
||||||
|
return command.toLowerCase() === 'm' ? !previousSegmentIsZ : true;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
lowerCurveCommands.includes(command) &&
|
lowerCurveCommands.includes(command) &&
|
||||||
@ -487,7 +483,8 @@ export default {
|
|||||||
let [yPrevCoord, xPrevCoord] = [
|
let [yPrevCoord, xPrevCoord] = [
|
||||||
...absSegments[index - 1],
|
...absSegments[index - 1],
|
||||||
].reverse();
|
].reverse();
|
||||||
// If the previous command was a direction one, we need to iterate back until we find the missing coordinates
|
// If the previous command was a direction one,
|
||||||
|
// we need to iterate back until we find the missing coordinates
|
||||||
if (upperDirectionCommands.includes(xPrevCoord)) {
|
if (upperDirectionCommands.includes(xPrevCoord)) {
|
||||||
xPrevCoord = undefined;
|
xPrevCoord = undefined;
|
||||||
yPrevCoord = undefined;
|
yPrevCoord = undefined;
|
||||||
@ -499,12 +496,14 @@ export default {
|
|||||||
let [yPrevCoordDeep, xPrevCoordDeep] = [
|
let [yPrevCoordDeep, xPrevCoordDeep] = [
|
||||||
...absSegments[idx],
|
...absSegments[idx],
|
||||||
].reverse();
|
].reverse();
|
||||||
// If the previous command was a horizontal movement, we need to consider the single coordinate as x
|
// If the previous command was a horizontal movement,
|
||||||
|
// we need to consider the single coordinate as x
|
||||||
if (upperHorDirectionCommand === xPrevCoordDeep) {
|
if (upperHorDirectionCommand === xPrevCoordDeep) {
|
||||||
xPrevCoordDeep = yPrevCoordDeep;
|
xPrevCoordDeep = yPrevCoordDeep;
|
||||||
yPrevCoordDeep = undefined;
|
yPrevCoordDeep = undefined;
|
||||||
}
|
}
|
||||||
// If the previous command was a vertical movement, we need to consider the single coordinate as y
|
// If the previous command was a vertical movement,
|
||||||
|
// we need to consider the single coordinate as y
|
||||||
if (upperVerDirectionCommand === xPrevCoordDeep) {
|
if (upperVerDirectionCommand === xPrevCoordDeep) {
|
||||||
xPrevCoordDeep = undefined;
|
xPrevCoordDeep = undefined;
|
||||||
}
|
}
|
||||||
@ -525,7 +524,9 @@ export default {
|
|||||||
|
|
||||||
if (upperCurveCommands.includes(command)) {
|
if (upperCurveCommands.includes(command)) {
|
||||||
const [x2Coord, y2Coord, xCoord, yCoord] = rest;
|
const [x2Coord, y2Coord, xCoord, yCoord] = rest;
|
||||||
// Absolute shorthand curve (S) having the same coordinate as the previous segment and a control point equal to the ending point
|
// Absolute shorthand curve (S) having
|
||||||
|
// the same coordinate as the previous segment
|
||||||
|
// and a control point equal to the ending point
|
||||||
if (
|
if (
|
||||||
upperShorthandCurveCommand === command &&
|
upperShorthandCurveCommand === command &&
|
||||||
x1Coord === xPrevCoord &&
|
x1Coord === xPrevCoord &&
|
||||||
@ -535,7 +536,9 @@ export default {
|
|||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Absolute bézier curve (C) having the same coordinate as the previous segment and last control point equal to the ending point
|
// Absolute bézier curve (C) having
|
||||||
|
// the same coordinate as the previous segment
|
||||||
|
// and last control point equal to the ending point
|
||||||
if (
|
if (
|
||||||
upperCurveCommand === command &&
|
upperCurveCommand === command &&
|
||||||
x1Coord === xPrevCoord &&
|
x1Coord === xPrevCoord &&
|
||||||
@ -548,13 +551,16 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// Absolute horizontal direction (H) having the same x coordinate as the previous segment
|
// Absolute horizontal direction (H) having
|
||||||
|
// the same x coordinate as the previous segment
|
||||||
(upperHorDirectionCommand === command &&
|
(upperHorDirectionCommand === command &&
|
||||||
x1Coord === xPrevCoord) ||
|
x1Coord === xPrevCoord) ||
|
||||||
// Absolute vertical direction (V) having the same y coordinate as the previous segment
|
// Absolute vertical direction (V) having
|
||||||
|
// the same y coordinate as the previous segment
|
||||||
(upperVerDirectionCommand === command &&
|
(upperVerDirectionCommand === command &&
|
||||||
x1Coord === yPrevCoord) ||
|
x1Coord === yPrevCoord) ||
|
||||||
// Absolute movement (M or L) having the same coordinate as the previous segment
|
// Absolute movement (M or L) having the same
|
||||||
|
// coordinate as the previous segment
|
||||||
(upperMovementCommands.includes(command) &&
|
(upperMovementCommands.includes(command) &&
|
||||||
x1Coord === xPrevCoord &&
|
x1Coord === xPrevCoord &&
|
||||||
y1Coord === yPrevCoord)
|
y1Coord === yPrevCoord)
|
||||||
@ -563,10 +569,12 @@ export default {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const svgFileContent = $.html();
|
for (let index = 0; index < segments.length; index++) {
|
||||||
|
const segment = segments[index];
|
||||||
|
const previousSegmentIsZ =
|
||||||
|
index > 0 && segments[index - 1].params[0].toLowerCase() === 'z';
|
||||||
|
|
||||||
segments.forEach((segment, index) => {
|
if (isInvalidSegment(segment.params, index, previousSegmentIsZ)) {
|
||||||
if (isInvalidSegment(segment.params, index)) {
|
|
||||||
const [command, x1, y1, ...rest] = segment.params;
|
const [command, x1, y1, ...rest] = segment.params;
|
||||||
|
|
||||||
let errorMsg = `Innefective segment "${iconPath.substring(
|
let errorMsg = `Innefective segment "${iconPath.substring(
|
||||||
@ -604,34 +612,23 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (segment.chained) {
|
if (segment.chained) {
|
||||||
let readableChain = iconPath.substring(
|
const readableChain = maybeShortenedWithEllipsis(
|
||||||
segment.chainStart,
|
iconPath.substring(segment.chainStart, segment.chainEnd),
|
||||||
segment.chainEnd,
|
|
||||||
);
|
);
|
||||||
if (readableChain.length > 20) {
|
|
||||||
readableChain = `${readableChain.substring(0, 20)}...`;
|
|
||||||
}
|
|
||||||
errorMsg += ` in chain "${readableChain}"`;
|
errorMsg += ` in chain "${readableChain}"`;
|
||||||
}
|
}
|
||||||
errorMsg += ` at index ${
|
errorMsg += ` at index ${
|
||||||
segment.start + getPathDIndex(svgFileContent)
|
segment.start + getPathDIndex(ast.source)
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
reporter.error(`${errorMsg} (${resolutionTip})`);
|
reporter.error(`${errorMsg} (${resolutionTip})`);
|
||||||
|
|
||||||
if (updateIgnoreFile) {
|
|
||||||
ignoreIcon(reporter.name, iconPath, $);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
},
|
},
|
||||||
(reporter, $, ast) => {
|
(reporter, $, ast) => {
|
||||||
reporter.name = 'collinear-segments';
|
reporter.name = 'collinear-segments';
|
||||||
|
|
||||||
const iconPath = $.find('path').attr('d');
|
const iconPath = $.find('path').attr('d');
|
||||||
if (!updateIgnoreFile && isIgnored(reporter.name, iconPath)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts collinear coordinates from SVG path straight lines
|
* Extracts collinear coordinates from SVG path straight lines
|
||||||
@ -640,8 +637,7 @@ export default {
|
|||||||
const getCollinearSegments = (iconPath) => {
|
const getCollinearSegments = (iconPath) => {
|
||||||
const segments = parsePath(iconPath),
|
const segments = parsePath(iconPath),
|
||||||
collinearSegments = [],
|
collinearSegments = [],
|
||||||
straightLineCommands = 'HhVvLlMm',
|
straightLineCommands = 'HhVvLlMm';
|
||||||
zCommands = 'Zz';
|
|
||||||
|
|
||||||
let currLine = [],
|
let currLine = [],
|
||||||
currAbsCoord = [undefined, undefined],
|
currAbsCoord = [undefined, undefined],
|
||||||
@ -655,80 +651,103 @@ export default {
|
|||||||
cmd = seg[0],
|
cmd = seg[0],
|
||||||
nextCmd = s + 1 < segments.length ? segments[s + 1][0] : null;
|
nextCmd = s + 1 < segments.length ? segments[s + 1][0] : null;
|
||||||
|
|
||||||
if (cmd === 'L') {
|
switch (cmd) {
|
||||||
currAbsCoord[0] = seg[1];
|
// Next switch cases have been ordered by frequency
|
||||||
currAbsCoord[1] = seg[2];
|
// of occurrence in the SVG paths of the icons
|
||||||
} else if (cmd === 'l') {
|
case 'M':
|
||||||
currAbsCoord[0] =
|
currAbsCoord[0] = seg[1];
|
||||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
|
currAbsCoord[1] = seg[2];
|
||||||
currAbsCoord[1] =
|
startPoint = undefined;
|
||||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2];
|
break;
|
||||||
} else if (cmd === 'm') {
|
case 'm':
|
||||||
currAbsCoord[0] =
|
currAbsCoord[0] =
|
||||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
|
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
|
||||||
currAbsCoord[1] =
|
currAbsCoord[1] =
|
||||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2];
|
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2];
|
||||||
startPoint = undefined;
|
startPoint = undefined;
|
||||||
} else if (cmd === 'M') {
|
break;
|
||||||
currAbsCoord[0] = seg[1];
|
case 'H':
|
||||||
currAbsCoord[1] = seg[2];
|
currAbsCoord[0] = seg[1];
|
||||||
startPoint = undefined;
|
break;
|
||||||
} else if (cmd === 'H') {
|
case 'h':
|
||||||
currAbsCoord[0] = seg[1];
|
currAbsCoord[0] =
|
||||||
} else if (cmd === 'h') {
|
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
|
||||||
currAbsCoord[0] =
|
break;
|
||||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
|
case 'V':
|
||||||
} else if (cmd === 'V') {
|
currAbsCoord[1] = seg[1];
|
||||||
currAbsCoord[1] = seg[1];
|
break;
|
||||||
} else if (cmd === 'v') {
|
case 'v':
|
||||||
currAbsCoord[1] =
|
currAbsCoord[1] =
|
||||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[1];
|
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[1];
|
||||||
} else if (cmd === 'C') {
|
break;
|
||||||
currAbsCoord[0] = seg[5];
|
case 'L':
|
||||||
currAbsCoord[1] = seg[6];
|
currAbsCoord[0] = seg[1];
|
||||||
} else if (cmd === 'a') {
|
currAbsCoord[1] = seg[2];
|
||||||
currAbsCoord[0] =
|
break;
|
||||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[6];
|
case 'l':
|
||||||
currAbsCoord[1] =
|
currAbsCoord[0] =
|
||||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[7];
|
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
|
||||||
} else if (cmd === 'A') {
|
currAbsCoord[1] =
|
||||||
currAbsCoord[0] = seg[6];
|
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2];
|
||||||
currAbsCoord[1] = seg[7];
|
break;
|
||||||
} else if (cmd === 's') {
|
case 'Z':
|
||||||
currAbsCoord[0] =
|
case 'z':
|
||||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
|
// Overlapping in Z should be handled in another rule
|
||||||
currAbsCoord[1] =
|
currAbsCoord = [startPoint[0], startPoint[1]];
|
||||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2];
|
_resetStartPoint = true;
|
||||||
} else if (cmd === 'S') {
|
break;
|
||||||
currAbsCoord[0] = seg[1];
|
case 'C':
|
||||||
currAbsCoord[1] = seg[2];
|
currAbsCoord[0] = seg[5];
|
||||||
} else if (cmd === 't') {
|
currAbsCoord[1] = seg[6];
|
||||||
currAbsCoord[0] =
|
break;
|
||||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
|
case 'c':
|
||||||
currAbsCoord[1] =
|
currAbsCoord[0] =
|
||||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2];
|
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[5];
|
||||||
} else if (cmd === 'T') {
|
currAbsCoord[1] =
|
||||||
currAbsCoord[0] = seg[1];
|
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[6];
|
||||||
currAbsCoord[1] = seg[2];
|
break;
|
||||||
} else if (cmd === 'c') {
|
case 'A':
|
||||||
currAbsCoord[0] =
|
currAbsCoord[0] = seg[6];
|
||||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[5];
|
currAbsCoord[1] = seg[7];
|
||||||
currAbsCoord[1] =
|
break;
|
||||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[6];
|
case 'a':
|
||||||
} else if (cmd === 'Q') {
|
currAbsCoord[0] =
|
||||||
currAbsCoord[0] = seg[3];
|
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[6];
|
||||||
currAbsCoord[1] = seg[4];
|
currAbsCoord[1] =
|
||||||
} else if (cmd === 'q') {
|
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[7];
|
||||||
currAbsCoord[0] =
|
break;
|
||||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[3];
|
case 's':
|
||||||
currAbsCoord[1] =
|
currAbsCoord[0] =
|
||||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[4];
|
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
|
||||||
} else if (zCommands.includes(cmd)) {
|
currAbsCoord[1] =
|
||||||
// Overlapping in Z should be handled in another rule
|
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2];
|
||||||
currAbsCoord = [startPoint[0], startPoint[1]];
|
break;
|
||||||
_resetStartPoint = true;
|
case 'S':
|
||||||
} else {
|
currAbsCoord[0] = seg[1];
|
||||||
throw new Error(`"${cmd}" command not handled`);
|
currAbsCoord[1] = seg[2];
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
currAbsCoord[0] =
|
||||||
|
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
|
||||||
|
currAbsCoord[1] =
|
||||||
|
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2];
|
||||||
|
break;
|
||||||
|
case 'T':
|
||||||
|
currAbsCoord[0] = seg[1];
|
||||||
|
currAbsCoord[1] = seg[2];
|
||||||
|
break;
|
||||||
|
case 'Q':
|
||||||
|
currAbsCoord[0] = seg[3];
|
||||||
|
currAbsCoord[1] = seg[4];
|
||||||
|
break;
|
||||||
|
case 'q':
|
||||||
|
currAbsCoord[0] =
|
||||||
|
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[3];
|
||||||
|
currAbsCoord[1] =
|
||||||
|
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[4];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`"${cmd}" command not handled`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startPoint === undefined) {
|
if (startPoint === undefined) {
|
||||||
@ -774,32 +793,22 @@ export default {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const collinearSegments = getCollinearSegments(iconPath),
|
const collinearSegments = getCollinearSegments(iconPath),
|
||||||
pathDIndex = getPathDIndex($.html());
|
pathDIndex = getPathDIndex(ast.source);
|
||||||
collinearSegments.forEach((segment) => {
|
for (const segment of collinearSegments) {
|
||||||
let errorMsg = `Collinear segment "${iconPath.substring(
|
let errorMsg = `Collinear segment "${iconPath.substring(
|
||||||
segment.start,
|
segment.start,
|
||||||
segment.end,
|
segment.end,
|
||||||
)}" found`;
|
)}" found`;
|
||||||
if (segment.chained) {
|
if (segment.chained) {
|
||||||
let readableChain = iconPath.substring(
|
let readableChain = maybeShortenedWithEllipsis(
|
||||||
segment.chainStart,
|
iconPath.substring(segment.chainStart, segment.chainEnd),
|
||||||
segment.chainEnd,
|
|
||||||
);
|
);
|
||||||
if (readableChain.length > 20) {
|
|
||||||
readableChain = `${readableChain.substring(0, 20)}...`;
|
|
||||||
}
|
|
||||||
errorMsg += ` in chain "${readableChain}"`;
|
errorMsg += ` in chain "${readableChain}"`;
|
||||||
}
|
}
|
||||||
errorMsg += ` at index ${
|
errorMsg += ` at index ${
|
||||||
segment.start + pathDIndex
|
segment.start + pathDIndex
|
||||||
} (should be removed)`;
|
} (should be removed)`;
|
||||||
reporter.error(errorMsg);
|
reporter.error(errorMsg);
|
||||||
});
|
|
||||||
|
|
||||||
if (collinearSegments.length) {
|
|
||||||
if (updateIgnoreFile) {
|
|
||||||
ignoreIcon(reporter.name, iconPath, $);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(reporter, $, ast) => {
|
(reporter, $, ast) => {
|
||||||
@ -812,7 +821,8 @@ export default {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
reporter.error(
|
reporter.error(
|
||||||
'Unexpected character(s), most likely extraneous whitespace, detected in SVG markup',
|
'Unexpected character(s), most likely extraneous' +
|
||||||
|
' whitespace, detected in SVG markup',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -821,9 +831,6 @@ export default {
|
|||||||
reporter.name = 'negative-zeros';
|
reporter.name = 'negative-zeros';
|
||||||
|
|
||||||
const iconPath = $.find('path').attr('d');
|
const iconPath = $.find('path').attr('d');
|
||||||
if (!updateIgnoreFile && isIgnored(reporter.name, iconPath)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find negative zeros inside path
|
// Find negative zeros inside path
|
||||||
const negativeZeroMatches = Array.from(
|
const negativeZeroMatches = Array.from(
|
||||||
@ -831,22 +838,22 @@ export default {
|
|||||||
);
|
);
|
||||||
if (negativeZeroMatches.length) {
|
if (negativeZeroMatches.length) {
|
||||||
// Calculate the index for each match in the file
|
// Calculate the index for each match in the file
|
||||||
const svgFileContent = $.html();
|
const pathDIndex = getPathDIndex(ast.source);
|
||||||
const pathDIndex = getPathDIndex(svgFileContent);
|
|
||||||
|
|
||||||
negativeZeroMatches.forEach((match) => {
|
for (const match of negativeZeroMatches) {
|
||||||
const negativeZeroFileIndex = match.index + pathDIndex;
|
const negativeZeroFileIndex = match.index + pathDIndex;
|
||||||
const previousChar = svgFileContent[negativeZeroFileIndex - 1];
|
const previousChar = ast.source[negativeZeroFileIndex - 1];
|
||||||
const replacement = '0123456789'.includes(previousChar)
|
const replacement = '0123456789'.includes(previousChar)
|
||||||
? ' 0'
|
? ' 0'
|
||||||
: '0';
|
: '0';
|
||||||
reporter.error(
|
reporter.error(
|
||||||
`Found "-0" at index ${negativeZeroFileIndex} (should be "${replacement}")`,
|
`Found "-0" at index ${negativeZeroFileIndex} (should` +
|
||||||
|
` be "${replacement}")`,
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(reporter, $, ast) => {
|
(reporter, $) => {
|
||||||
reporter.name = 'icon-centered';
|
reporter.name = 'icon-centered';
|
||||||
|
|
||||||
const iconPath = $.find('path').attr('d');
|
const iconPath = $.find('path').attr('d');
|
||||||
@ -866,7 +873,8 @@ export default {
|
|||||||
Math.abs(devianceY) > iconTolerance
|
Math.abs(devianceY) > iconTolerance
|
||||||
) {
|
) {
|
||||||
reporter.error(
|
reporter.error(
|
||||||
`<path> must be centered at (${targetCenter}, ${targetCenter}); the center is currently (${centerX}, ${centerY})`,
|
`<path> must be centered at (${targetCenter}, ${targetCenter});` +
|
||||||
|
` the center is currently (${centerX}, ${centerY})`,
|
||||||
);
|
);
|
||||||
if (updateIgnoreFile) {
|
if (updateIgnoreFile) {
|
||||||
ignoreIcon(reporter.name, iconPath, $);
|
ignoreIcon(reporter.name, iconPath, $);
|
||||||
@ -878,20 +886,24 @@ export default {
|
|||||||
|
|
||||||
const iconPath = $.find('path').attr('d');
|
const iconPath = $.find('path').attr('d');
|
||||||
|
|
||||||
const validPathFormatRegex = /^[Mm][MmZzLlHhVvCcSsQqTtAaEe0-9-,.\s]+$/;
|
if (!svgPathRegexp.test(iconPath)) {
|
||||||
if (!validPathFormatRegex.test(iconPath)) {
|
|
||||||
let errorMsg = 'Invalid path format',
|
let errorMsg = 'Invalid path format',
|
||||||
reason;
|
reason;
|
||||||
|
|
||||||
if (!/^[Mm]/.test(iconPath)) {
|
if (!/^[Mm]/.test(iconPath)) {
|
||||||
// doesn't start with moveto
|
// doesn't start with moveto
|
||||||
reason = `should start with \"moveto\" command (\"M\" or \"m\"), but starts with \"${iconPath[0]}\"`;
|
reason =
|
||||||
|
'should start with "moveto" command ("M" or "m"),' +
|
||||||
|
` but starts with \"${iconPath[0]}\"`;
|
||||||
reporter.error(`${errorMsg}: ${reason}`);
|
reporter.error(`${errorMsg}: ${reason}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const validPathCharacters = 'MmZzLlHhVvCcSsQqTtAaEe0123456789-,. ',
|
const validPathCharacters = svgPathRegexp.source.replace(
|
||||||
|
/[\[\]+^$]/g,
|
||||||
|
'',
|
||||||
|
),
|
||||||
invalidCharactersMsgs = [],
|
invalidCharactersMsgs = [],
|
||||||
pathDIndex = getPathDIndex($.html());
|
pathDIndex = getPathDIndex(ast.source);
|
||||||
|
|
||||||
for (let [i, char] of Object.entries(iconPath)) {
|
for (let [i, char] of Object.entries(iconPath)) {
|
||||||
if (validPathCharacters.indexOf(char) === -1) {
|
if (validPathCharacters.indexOf(char) === -1) {
|
||||||
@ -919,8 +931,8 @@ export default {
|
|||||||
const reason =
|
const reason =
|
||||||
`found a closing "path" tag at index ${ast.source.indexOf(
|
`found a closing "path" tag at index ${ast.source.indexOf(
|
||||||
'</path>',
|
'</path>',
|
||||||
)}.` +
|
)}. The path should be self-closing,` +
|
||||||
" The path should be self-closing, use '/>' instead of '></path>'.";
|
' use "/>" instead of "></path>".';
|
||||||
reporter.error(`Invalid SVG content format: ${reason}`);
|
reporter.error(`Invalid SVG content format: ${reason}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -34,6 +34,7 @@ const generateSdkMts = async () => {
|
|||||||
' --declaration --emitDeclarationOnly --allowJs --removeComments',
|
' --declaration --emitDeclarationOnly --allowJs --removeComments',
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
await fs.writeFile(sdkMjs, originalSdkMjsContent);
|
||||||
console.log(
|
console.log(
|
||||||
`Error ${error.status} generating Typescript` +
|
`Error ${error.status} generating Typescript` +
|
||||||
` definitions: '${error.message}'`,
|
` definitions: '${error.message}'`,
|
||||||
|
2
sdk.mjs
2
sdk.mjs
@ -112,7 +112,7 @@ export const titleToHtmlFriendly = (brandTitle) =>
|
|||||||
*/
|
*/
|
||||||
export const htmlFriendlyToTitle = (htmlFriendlyTitle) =>
|
export const htmlFriendlyToTitle = (htmlFriendlyTitle) =>
|
||||||
htmlFriendlyTitle
|
htmlFriendlyTitle
|
||||||
.replace(/&#([0-9]+);/g, (_, num) => String.fromCharCode(parseInt(num)))
|
.replace(/&#([0-9]+);/g, (_, num) => String.fromCodePoint(parseInt(num)))
|
||||||
.replace(
|
.replace(
|
||||||
/&(quot|amp|lt|gt);/g,
|
/&(quot|amp|lt|gt);/g,
|
||||||
(_, ref) => ({ quot: '"', amp: '&', lt: '<', gt: '>' }[ref]),
|
(_, ref) => ({ quot: '"', amp: '&', lt: '<', gt: '>' }[ref]),
|
||||||
|
Loading…
Reference in New Issue
Block a user