Files
Bubberstation/tgui/packages/common/string.js
T
Aleksej Komarov 602270d178 tgui: MIT License (#50969)
* MIT license headers

* various tweaks

- Tweak jsdoc headers a bit
- Use an old-school format with SPDX-License-Identifier for SCSS
- Add headers to tgui dmcode

* Simplify the license section

* Rebuild tgui, small tweaks

Co-authored-by: ZeWaka <zewakagamer@gmail.com>

About The Pull Request

All relevant source code now contains copyright headers, that explicitly assert copyright and license for every file.

This has been done to prepare TGUI for wider adoption. Goon station devs are interested in using TGUI, and hopefully this will result in a nice collaboration and improvements to both codebases.

The following files were relicensed under MIT:

    code/controllers/subsystem/tgui.dm
    code/modules/tgui/*.dm
    tgui/**/*.js
    tgui/**/*.scss

The following files were kept untouched:

    tgui/packages/tgui/interfaces/**/*.js
    tgui/packages/tgui/styles/interfaces/**/*.scss

Project is still basically AGPL-3.0 under /tg/station's parent license (with added MIT texts), but allows importing MIT code into MIT-compatible codebases.
2020-05-11 10:32:52 +12:00

163 lines
4.0 KiB
JavaScript

/**
* @file
* @copyright 2020 Aleksej Komarov
* @license MIT
*/
/**
* Removes excess whitespace and indentation from the string.
*/
export const multiline = str => {
if (Array.isArray(str)) {
// Small stub to allow usage as a template tag
return multiline(str.join(''));
}
const lines = str.split('\n');
// Determine base indentation
let minIndent;
for (let line of lines) {
for (let indent = 0; indent < line.length; indent++) {
const char = line[indent];
if (char !== ' ') {
if (minIndent === undefined || indent < minIndent) {
minIndent = indent;
}
break;
}
}
}
if (!minIndent) {
minIndent = 0;
}
// Remove this base indentation and trim the resulting string
// from both ends.
return lines
.map(line => line.substr(minIndent).trimRight())
.join('\n')
.trim();
};
/**
* Creates a glob pattern matcher.
*
* Matches strings with wildcards.
*
* Example: createGlobPattern('*@domain')('user@domain') === true
*/
export const createGlobPattern = pattern => {
const escapeString = str => str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
const regex = new RegExp('^'
+ pattern.split(/\*+/).map(escapeString).join('.*')
+ '$');
return str => regex.test(str);
};
/**
* Creates a search terms matcher.
*
* Returns true if given string matches the search text.
*
* @template T
* @param {string} searchText
* @param {(obj: T) => string} stringifier
* @returns {(obj: T) => boolean}
*/
export const createSearch = (searchText, stringifier) => {
const preparedSearchText = searchText.toLowerCase().trim();
return obj => {
if (!preparedSearchText) {
return true;
}
const str = stringifier ? stringifier(obj) : obj;
if (!str) {
return false;
}
return str
.toLowerCase()
.includes(preparedSearchText);
};
};
export const capitalize = str => {
// Handle array
if (Array.isArray(str)) {
return str.map(capitalize);
}
// Handle string
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
};
export const toTitleCase = str => {
// Handle array
if (Array.isArray(str)) {
return str.map(toTitleCase);
}
// Pass non-string
if (typeof str !== 'string') {
return str;
}
// Handle string
const WORDS_UPPER = ['Id', 'Tv'];
const WORDS_LOWER = [
'A', 'An', 'And', 'As', 'At', 'But', 'By', 'For', 'For', 'From', 'In',
'Into', 'Near', 'Nor', 'Of', 'On', 'Onto', 'Or', 'The', 'To', 'With',
];
let currentStr = str.replace(/([^\W_]+[^\s-]*) */g, str => {
return str.charAt(0).toUpperCase() + str.substr(1).toLowerCase();
});
for (let word of WORDS_LOWER) {
const regex = new RegExp('\\s' + word + '\\s', 'g');
currentStr = currentStr.replace(regex, str => str.toLowerCase());
}
for (let word of WORDS_UPPER) {
const regex = new RegExp('\\b' + word + '\\b', 'g');
currentStr = currentStr.replace(regex, str => str.toLowerCase());
}
return currentStr;
};
/**
* Decodes HTML entities, and removes unnecessary HTML tags.
*
* @param {String} str Encoded HTML string
* @return {String} Decoded HTML string
*/
export const decodeHtmlEntities = str => {
if (!str) {
return str;
}
const translate_re = /&(nbsp|amp|quot|lt|gt|apos);/g;
const translate = {
nbsp: ' ',
amp: '&',
quot: '"',
lt: '<',
gt: '>',
apos: '\'',
};
return str
// Newline tags
.replace(/<br>/gi, '\n')
.replace(/<\/?[a-z0-9-_]+[^>]*>/gi, '')
// Basic entities
.replace(translate_re, (match, entity) => translate[entity])
// Decimal entities
.replace(/&#?([0-9]+);/gi, (match, numStr) => {
const num = parseInt(numStr, 10);
return String.fromCharCode(num);
})
// Hex entities
.replace(/&#x?([0-9a-f]+);/gi, (match, numStr) => {
const num = parseInt(numStr, 16);
return String.fromCharCode(num);
});
};
/**
* Converts an object into a query string,
*/
export const buildQueryString = obj => Object.keys(obj)
.map(key => encodeURIComponent(key)
+ '=' + encodeURIComponent(obj[key]))
.join('&');