Lukáš Tinkl e947f81cd2 fix: Chat input: can't send html text from the clipboard
We always take the plain text from the clipboard but even the plaintext
can contain HTML tags, so escape those and wrap the result in a `<div
style='white-space: pre-wrap'>foo</div>` container. That way we preserve
not only the linebreaks but also any whitespace or tags.

Fixes #8919
2023-01-05 12:32:53 +01:00

817 lines
28 KiB
QML

pragma Singleton
import QtQuick 2.13
import shared 1.0
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
QtObject {
property var mainModuleInst: typeof mainModule !== "undefined" ? mainModule : null
property var globalUtilsInst: typeof globalUtils !== "undefined" ? globalUtils : null
function isDigit(value) {
return /^\d$/.test(value);
}
function isHex(value) {
return /^(-0x|0x)?[0-9a-fA-F]*$/i.test(value)
}
function startsWith0x(value) {
return value.startsWith('0x')
}
function isChatKey(value) {
return (startsWith0x(value) && isHex(value) && value.length === 132) || globalUtilsInst.isCompressedPubKey(value)
}
function isCompressedPubKey(pubKey) {
return globalUtilsInst.isCompressedPubKey(pubKey)
}
function isValidETHNamePrefix(value) {
return !(value.trim() === "" || value.endsWith(".") || value.indexOf("..") > -1)
}
function isAddress(value) {
return startsWith0x(value) && isHex(value) && value.length === 42
}
function isPrivateKey(value) {
return isHex(value) && ((startsWith0x(value) && value.length === 66) ||
(!startsWith0x(value) && value.length === 64))
}
function getCurrentThemeAccountColor(color) {
const upperCaseColor = color.toUpperCase()
if (Style.current.accountColors.indexOf(upperCaseColor) > -1) {
return upperCaseColor
}
let colorIndex
if (Style.current.name === Constants.lightThemeName) {
colorIndex = Style.darkTheme.accountColors.indexOf(upperCaseColor)
} else {
colorIndex = Style.lightTheme.accountColors.indexOf(upperCaseColor)
}
if (colorIndex === -1) {
// Unknown color
return false
}
return Style.current.accountColors[colorIndex]
}
function getLinkStyle(link, hoveredLink, textColor) {
return `<style type="text/css">` +
`a {` +
`color: ${textColor};` +
`text-decoration: none;` +
`}` +
(hoveredLink !== "" ? `a[href="${hoveredLink}"] { text-decoration: underline; }` : "") +
`</style>` +
`<a href="${link}">${link}</a>`
}
function isMnemonic(value) {
if(!value.match(/^([a-z\s]+)$/)){
return false;
}
return Utils.seedPhraseValidWordCount(value);
}
function compactAddress(addr, numberOfChars) {
if(addr.length <= 5 + (numberOfChars * 2)){ // 5 represents these chars 0x...
return addr;
}
return addr.substring(0, 2 + numberOfChars) + "..." + addr.substring(addr.length - numberOfChars);
}
function toLocaleString(val, locale, options) {
return NumberPolyFill.toLocaleString(val, locale, options)
}
function isOnlyEmoji(inputText) {
var emoji_regex = /^(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|[\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|[\ud83c[\ude32-\ude3a]|[\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff]|\s)+$/;
return emoji_regex.test(inputText);
}
function isValidAddress(inputValue) {
return inputValue !== "0x" && /^0x[a-fA-F0-9]{40}$/.test(inputValue)
}
function isValidEns(inputValue) {
if (!inputValue) {
return false
}
const isEmail = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/.test(inputValue)
const isDomain = /(?:(?:(?<thld>[\w\-]*)(?:\.))?(?<sld>[\w\-]*))\.(?<tld>[\w\-]*)/.test(inputValue)
return isEmail || isDomain || (inputValue.startsWith("@") && inputValue.length > 1)
}
/**
* Removes trailing zeros from a string-representation of a number. Throws
* if parameter is not a string
*/
function stripTrailingZeros(strNumber) {
if (!(typeof strNumber === "string")) {
try {
strNumber = strNumber.toString()
} catch(e) {
throw "[Utils.stripTrailingZeros] input parameter must be a string"
}
}
return strNumber.replace(/(\.[0-9]*[1-9])0+$|\.0*$/,'$1')
}
/**
* Removes starting zeros from a string-representation of a number. Throws
* if parameter is not a string
*/
function stripStartingZeros(strNumber) {
if (!(typeof strNumber === "string")) {
try {
strNumber = strNumber.toString()
} catch(e) {
throw "[Utils.stripStartingZeros] input parameter must be a string"
}
}
return strNumber.replace(/^(0*)([0-9\.]+)/, "$2")
}
function setColorAlpha(color, alpha) {
return Qt.hsla(color.hslHue, color.hslSaturation, color.hslLightness, alpha)
}
function checkTimestamp(value, errorLocation) {
if(Number.isInteger(value) && value > 0) {
return true;
}
return false;
}
function formatTime(value, is24hTimeFormat) {
const format24h = "hh:mm:ss t"
const format12h = "h:mm:ss AP t"
const currentTimeFormat = is24hTimeFormat ? format24h : format12h
return checkTimestamp(value, "formatTime") ? Qt.formatTime(new Date(value), currentTimeFormat) :
Qt.formatTime(new Date(), currentTimeFormat)
}
function formatShortTime(value, is24hTimeFormat) {
const format24h = "hh:mm"
const format12h = "h:mm AP"
const currentTimeFormat = is24hTimeFormat ? format24h : format12h
return checkTimestamp(value, "formatShortTime") ? Qt.formatTime(new Date(value), currentTimeFormat) :
Qt.formatTime(new Date(), currentTimeFormat)
}
function formatShortDateStr(longStr) {
const dmKeys = {
// Days
Sunday: qsTr("Sun"),
Monday: qsTr("Mon"),
Tuesday: qsTr("Tue"),
Wednesday: qsTr("Wed"),
Thursday: qsTr("Thu"),
Friday: qsTr("Fri"),
Saturday: qsTr("Sat"),
// Months
January: qsTr("Jan"),
February: qsTr("Feb"),
March: qsTr("Mar"),
April: qsTr("Apr"),
May: qsTr("May"),
June: qsTr("Jun"),
July: qsTr("Jul"),
August: qsTr("Aug"),
September: qsTr("Sep"),
October: qsTr("Oct"),
November: qsTr("Nov"),
December: qsTr("Dec")
};
let shortStr = longStr;
for (const [key, value] of Object.entries(dmKeys)) {
shortStr = shortStr.replace(key, value);
shortStr = shortStr.replace(key.toLowerCase(), value);
shortStr = shortStr.replace(key.toUpperCase(), value);
}
return shortStr;
}
function formatLongDate(value, isDDMMYYDateFormat) {
const formatDDMMYY = "dddd d MMMM yyyy"
const formatMMDDYY = "dddd, MMMM d, yyyy"
const currentFormat = isDDMMYYDateFormat ? formatDDMMYY : formatMMDDYY
return checkTimestamp(value, "formatLongDate") ? Qt.formatDate(new Date(value), currentFormat) :
Qt.formatDate(new Date(), currentFormat)
}
function formatLongDateTime(value, isDDMMYYDateFormat, is24hTimeFormat) {
const formatDDMMYY = "dddd d MMMM yyyy"
const formatMMDDYY = "dddd, MMMM d, yyyy"
const format24h = "hh:mm:ss t"
const format12h = "h:mm:ss AP t"
const currentDateFormat = isDDMMYYDateFormat ? formatDDMMYY : formatMMDDYY
const currentTimeFormat = is24hTimeFormat ? format24h : format12h
return checkTimestamp(value, "formatLongDateTime") ? Qt.formatDateTime(new Date(value), currentDateFormat + " " + currentTimeFormat) :
Qt.formatDateTime(new Date(), currentDateFormat + " " + currentTimeFormat)
}
// WARN: It is not used!! TO BE REMOVE??
function formatDateTime(timestamp, locale) {
let now = new Date()
let yesterday = new Date()
yesterday.setDate(now.getDate()-1)
let messageDate = new Date(Math.floor(timestamp))
let lastWeek = new Date()
lastWeek.setDate(now.getDate()-7)
let minutes = messageDate.getMinutes();
let hours = messageDate.getHours();
if (now.toDateString() === messageDate.toDateString()) {
return (hours < 10 ? "0" + hours : hours) + ":" + (minutes < 10 ? "0" + minutes : minutes)
} else if (yesterday.toDateString() === messageDate.toDateString()) {
return qsTr("Yesterday")
} else if (lastWeek.getTime() < messageDate.getTime()) {
let days = [qsTr("Sunday"),
qsTr("Monday"),
qsTr("Tuesday"),
qsTr("Wednesday"),
qsTr("Thursday"),
qsTr("Friday"),
qsTr("Saturday")];
return days[messageDate.getDay()];
} else {
return formatShortDateStr(new Date().toLocaleDateString(Qt.locale(locale)))
}
}
// WARN: It is not used!! TO BE REMOVE??
function formatAgeFromTime(timestamp, epoch) {
epoch++ // pretending the parameter is not unused
const now = new Date()
const messageDate = new Date(Math.floor(timestamp))
const diffMs = now - messageDate
const diffMin = Math.floor(diffMs / 60000)
if (diffMin < 1) {
return qsTr("NOW")
}
const diffHour = Math.floor(diffMin / 60)
if (diffHour < 1) {
return qsTr("%1M").arg(diffMin)
}
const diffDay = Math.floor(diffHour / 24)
if (diffDay < 1) {
return qsTr("%1H").arg(diffHour)
}
return qsTr("%1D").arg(diffDay)
}
function getDayMonth(value, isDDMMYYDateFormat) {
const formatDDMMYY = "d MMMM"
const formatMMDDYY = "MMMM d"
const currentFormat = isDDMMYYDateFormat ? formatDDMMYY : formatMMDDYY
var timeStamp = checkTimestamp(value, "formatLongDate") ? Qt.formatDate(new Date(value), currentFormat) :
Qt.formatDate(new Date(), currentFormat)
return formatShortDateStr(timeStamp)
}
function getMonthYear(value) {
const formatDDMMYY = "MMMM yyyy"
var timeStamp = checkTimestamp(value, "formatLongDate") ? Qt.formatDate(new Date(value), formatDDMMYY) :
Qt.formatDate(new Date(), formatDDMMYY)
return formatShortDateStr(timeStamp)
}
function formatShortDate(value, isDDMMYYDateFormat) {
const formatDDMMYY = "d MMMM yyyy"
const formatMMDDYY = "MMMM d yyyy"
const currentFormat = isDDMMYYDateFormat ? formatDDMMYY : formatMMDDYY
var timeStamp = checkTimestamp(value, "formatLongDate") ? Qt.formatDate(new Date(value), currentFormat) :
Qt.formatDate(new Date(), currentFormat)
return formatShortDateStr(timeStamp)
}
// To-do move to Wallet Store, this should not be under Utils.
function findAssetByChainAndSymbol(chainIdToFind, assets, symbolToFind) {
for(var i=0; i<assets.rowCount(); i++) {
const symbol = assets.rowData(i, "symbol")
if (symbol.toLowerCase() === symbolToFind.toLowerCase() && assets.hasChain(i, parseInt(chainIdToFind))) {
return {
name: assets.rowData(i, "name"),
symbol,
totalBalance: assets.rowData(i, "totalBalance"),
totalCurrencyBalance: assets.rowData(i, "totalCurrencyBalance"),
fiatBalance: assets.rowData(i, "totalCurrencyBalance"),
chainId: chainIdToFind,
}
}
}
}
function isValidChannelName(channelName) {
return (/^[a-z0-9\-]+$/.test(channelName))
}
function isURL(text) {
return (/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}(\.[a-zA-Z0-9()]{1,6})?\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/.test(text))
}
function isURLWithOptionalProtocol(text) {
return (/^(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/.test(text))
}
function isHexColor(c) {
return (/^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})$/i.test(c))
}
function isSpace(c) {
return (/( |\t|\n|\r)/.test(c))
}
function getTick(wordCount) {
return (wordCount === 12 || wordCount === 15 ||
wordCount === 18 || wordCount === 21 || wordCount === 24)
? "✓ " : "";
}
function isValidNumberOfWords(wordCount) {
return !!getTick(wordCount);
}
function countWords(text) {
if (text.trim() === "")
return 0;
return text.trim().replace(/ +/g, " ").split(" ").length;
}
function seedPhraseValidWordCount(text) {
return isValidNumberOfWords(countWords(text))
}
/**
* Returns text in the format "✓ 12 words" for seed phrases input boxes
*/
function seedPhraseWordCountText(text) {
let wordCount = countWords(text);
return getTick(wordCount) + wordCount.toString() + " " + qsTr("words")
}
function uuid() {
return Date.now().toString(36) + Math.random().toString(36).substr(2, 5)
}
function validatePasswords(item, firstPasswordField, repeatPasswordField) {
switch (item) {
case "first":
if (firstPasswordField.text === "") {
return [false, qsTr("You need to enter a password")];
} else if (firstPasswordField.text.length < Constants.minPasswordLength) {
return [false, qsTr("Password needs to be %n character(s) or more", "", Constants.minPasswordLength)];
}
return [true, ""];
case "repeat":
if (repeatPasswordField.text === "") {
return [false, qsTr("You need to repeat your password")];
} else if (repeatPasswordField.text !== firstPasswordField.text) {
return [false, qsTr("Passwords don't match")];
}
return [true, ""];
default:
return [false, ""];
}
}
function validatePINs(item, firstPINField, repeatPINField) {
switch (item) {
case "first":
if (firstPINField.pinInput === "") {
return [false, qsTr("You need to enter a PIN")];
} else if (!/^\d+$/.test(firstPINField.pinInput)) {
return [false, qsTr("The PIN must contain only digits")];
} else if (firstPINField.pinInput.length != Constants.keycard.general.keycardPinLength) {
return [false, qsTr("The PIN must be exactly %1 digits").arg(Constants.keycard.general.keycardPinLength)];
}
return [true, ""];
case "repeat":
if (repeatPINField.pinInput === "") {
return [false, qsTr("You need to repeat your PIN")];
} else if (repeatPINField.pinInput !== firstPINField.pinInput) {
return [false, qsTr("PIN don't match")];
}
return [true, ""];
default:
return [false, ""];
}
}
function getHostname(url) {
const rgx = /\:\/\/(?:[a-zA-Z0-9\-]*\.{1,}){1,}[a-zA-Z0-9]*/i
const matches = rgx.exec(url)
if (!matches || !matches.length) {
if (url.includes(Constants.deepLinkPrefix)) {
return Constants.deepLinkPrefix
}
return ""
}
return matches[0].substring(3)
}
function isStatusDeepLink(link) {
return link.includes(Constants.deepLinkPrefix) || link.includes(Constants.joinStatusLink)
}
function hasImageExtension(url) {
return Constants.acceptedImageExtensions.some(ext => url.toLowerCase().includes(ext))
}
function removeGifUrls(message) {
return message.replace(/(?:https?|ftp):\/\/[\n\S]*(\.gif)+/gm, '');
}
function hasDragNDropImageExtension(url) {
return Constants.acceptedDragNDropImageExtensions.some(ext => url.toLowerCase().includes(ext))
}
function deduplicate(array) {
return Array.from(new Set(array))
}
function hasUpperCaseLetter(str) {
return (/[A-Z]/.test(str))
}
function convertSpacesToDashesAndUpperToLowerCase(str)
{
if (str.includes(" "))
str = str.replace(/ /g, "-")
if(hasUpperCaseLetter(str))
str = str.toLowerCase()
return str
}
/* Validation section start */
enum Validate {
NoEmpty = 0x01,
TextLength = 0x02,
TextHexColor = 0x04,
TextLowercaseLettersNumberAndDashes = 0x08
}
function validateAndReturnError(str, validation, fieldName = "field", limit = 0)
{
let errMsg = ""
if(validation & Utils.Validate.NoEmpty && str === "") {
errMsg = qsTr("You need to enter a %1").arg(fieldName)
}
if(validation & Utils.Validate.TextLength && str.length > limit) {
errMsg = qsTr("The %1 cannot exceed %2 characters").arg(fieldName, limit)
}
if(validation & Utils.Validate.TextHexColor && !isHexColor(str)) {
errMsg = qsTr("Must be an hexadecimal color (eg: #4360DF)")
}
if(validation & Utils.Validate.TextLowercaseLettersNumberAndDashes && !isValidChannelName(str)) {
errMsg = qsTr("Use only lowercase letters (a to z), numbers & dashes (-). Do not use chat keys.")
}
return errMsg
}
function getErrorMessage(errors, fieldName) {
if (errors) {
if (errors.minLength) {
return errors.minLength.min === 1 ?
qsTr("You need to enter a %1").arg(fieldName) :
qsTr("Value has to be at least %1 characters long").arg(errors.minLength.min)
}
}
return ""
}
/* Validation section end */
function getContactDetailsAsJson(publicKey, getVerificationRequest=true) {
const defaultValue = {
displayName: "",
displayIcon: "",
publicKey: publicKey,
name: "",
ensVerified: false,
alias: "",
lastUpdated: 0,
lastUpdatedLocally: 0,
localNickname: "",
thumbnailImage: "",
largeImage: "",
isContact: false,
isAdded: false,
isBlocked: false,
requestReceived: false,
isSyncing: false,
removed: false,
trustStatus: Constants.trustStatus.unknown,
verificationStatus: Constants.verificationStatus.unverified,
incomingVerificationStatus: Constants.verificationStatus.unverified
}
if (!mainModuleInst)
return defaultValue
const jsonObj = mainModuleInst.getContactDetailsAsJson(publicKey, getVerificationRequest)
try {
return JSON.parse(jsonObj)
}
catch (e) {
// This log is available only in debug mode, if it's annoying we can remove it
console.warn("error parsing contact details for public key: ", publicKey, " error: ", e.message)
return defaultValue
}
}
function isEnsVerified(publicKey) {
if (publicKey === "" || !isChatKey(publicKey) )
return
return getContactDetailsAsJson(publicKey, false).ensVerified
}
function getEmojiHashAsJson(publicKey) {
if (publicKey === "" || !isChatKey(publicKey)) {
return ""
}
let jsonObj = globalUtilsInst.getEmojiHashAsJson(publicKey)
return JSON.parse(jsonObj)
}
function getColorHashAsJson(publicKey, skipEnsVerification=false) {
if (publicKey === "" || !isChatKey(publicKey))
return
if (skipEnsVerification) // we know already the user is ENS verified -> no color ring
return
if (isEnsVerified(publicKey)) // ENS verified -> no color ring
return
let jsonObj = globalUtilsInst.getColorHashAsJson(publicKey)
return JSON.parse(jsonObj)
}
function colorIdForPubkey(publicKey) {
if (publicKey === "" || !isChatKey(publicKey)) {
return 0
}
return globalUtilsInst.getColorId(publicKey)
}
function colorForColorId(colorId) {
if (colorId < 0 || colorId >= Theme.palette.userCustomizationColors.length) {
console.warn("Utils.colorForColorId : colorId is out of bounds")
return StatusColors.colors['blue']
}
return Theme.palette.userCustomizationColors[colorId]
}
function colorForPubkey(publicKey) {
const pubKeyColorId = colorIdForPubkey(publicKey)
return colorForColorId(pubKeyColorId)
}
function getCommunityShareLink(communityId, elided = false) {
if (communityId === "") {
return ""
}
let compressedPk = communityId
if (!globalUtilsInst.isCompressedPubKey(compressedPk)) {
compressedPk = globalUtilsInst.changeCommunityKeyCompression(compressedPk)
}
return Constants.communityLinkPrefix +
(elided ? StatusQUtils.Utils.elideText(compressedPk, 4, 2) : compressedPk)
}
function getChatKeyFromShareLink(link) {
let index = link.lastIndexOf("/u/")
if (index === -1) {
return link
}
return link.substring(index + 3)
}
function getCommunityIdFromShareLink(link) {
let index = link.lastIndexOf("/c/")
if (index === -1) {
return ""
}
const communityKey = link.substring(index + 3)
if (globalUtilsInst.isCompressedPubKey(communityKey)) {
// is zQ.., need to be converted to standard compression
return globalUtilsInst.changeCommunityKeyCompression(communityKey)
}
return communityKey
}
function changeCommunityKeyCompression(communityKey) {
return globalUtilsInst.changeCommunityKeyCompression(communityKey)
}
function getCompressedPk(publicKey) {
if (publicKey === "") {
return ""
}
if (!isChatKey(publicKey))
return publicKey
return globalUtilsInst.getCompressedPk(publicKey)
}
function getElidedPk(publicKey) {
if (publicKey === "") {
return ""
}
return StatusQUtils.Utils.elideText(publicKey, 5, 3)
}
function getElidedCommunityPK(publicKey) {
if (publicKey === "") {
return ""
}
return StatusQUtils.Utils.elideText(publicKey, 16)
}
function getElidedCompressedPk(publicKey) {
if (publicKey === "") {
return ""
}
let compressedPk = getCompressedPk(publicKey)
return getElidedPk(compressedPk, 6, 3)
}
function getTimeDifference(d1, d2) {
var timeString = ""
var day1Year = d1.getFullYear()
var day1Month = d1.getMonth()
var day1Time = d1.getTime()
var day2Year = d2.getFullYear()
var day2Month = d2.getMonth()
var day2Time = d2.getTime()
var inYears = day2Year-day1Year
if(inYears > 0) {
timeString = inYears > 1 ? qsTr("years ago") : qsTr("year ago")
return inYears + " " + timeString
}
var inMonths = (day2Month+12*day2Year)-(day1Month+12*day1Year)
if(inMonths > 0) {
timeString = inMonths > 1 ? qsTr("months ago") : qsTr("month ago")
return inMonths + " " + timeString
}
var inWeeks = parseInt((day2Time-day2Time)/(24*3600*1000*7))
if(inWeeks > 0) {
timeString = inWeeks > 1 ? qsTr("weeks ago") : qsTr("week ago")
return inWeeks + " " + timeString
}
var inDays = parseInt((day2Time-day1Time)/(24*3600*1000))
if(inDays > 0) {
timeString = inDays > 1 ? qsTr("days ago") : qsTr("day ago")
return inDays + " " + timeString
}
var inHours = parseInt((day2Time-day1Time)/(3600*1000));
if(inHours > 0) {
timeString = inHours > 1 ? qsTr("hours ago") : qsTr("hour ago")
return inHours + " " + timeString
}
var inMins = parseInt((day2Time-day1Time)/(60*1000))
if(inMins > 0) {
timeString = inMins > 1 ? qsTr("mins ago") : qsTr("min ago")
return inMins + " " + timeString
}
var inSecs = parseInt((day2Time-day1Time)/(1000));
if(inSecs > 0) {
timeString = inSecs > 1 ? qsTr("secs ago") : qsTr("sec ago")
return inSecs + " " + timeString
}
return qsTr("now")
}
function elideIfTooLong(str, maxLength) {
return (str.length > maxLength) ? str.substr(0, maxLength-4) + '...' : str;
}
function escapeHtml(unsafeStr) {
return globalUtilsInst.escapeHtml(unsafeStr)
}
function plainText(text) {
return globalUtilsInst.plainText(text)
}
function isInvalidPasswordMessage(msg) {
return (
msg.includes("could not decrypt key with given password") ||
msg.includes("invalid password")
);
}
function isInvalidPrivateKey(msg) {
return msg.includes("invalid private key");
}
function isInvalidPath(msg) {
return msg.includes(Constants.wrongDerivationPathError)
}
function accountAlreadyExistsError(msg) {
return msg.includes(Constants.existingAccountError)
}
// See also: backend/interpret/cropped_image.nim
function getImageAndCropInfoJson(imgPath, cropRect) {
return JSON.stringify({imagePath: String(imgPath).replace("file://", ""), cropRect: cropRect})
}
function containsOnlyDigits(msg) {
var reg = new RegExp('[+-]?([0-9]*[.])?[0-9]+')
return reg.test(msg)
}
function addSocialLinkPrefix(link, type) {
const prefix = Constants.socialLinkPrefixesByType[type]
if (link.startsWith(prefix))
return link
return prefix + link
}
function stripSocialLinkPrefix(link, type) {
return link.replace(Constants.socialLinkPrefixesByType[type], "")
}
// handle translations for section names coming from app_sections_config.nim
function translatedSectionName(sectionType, fallback) {
switch(sectionType) {
case Constants.appSection.chat:
return qsTr("Messages")
case Constants.appSection.wallet:
return qsTr("Wallet")
case Constants.appSection.browser:
return qsTr("Browser")
case Constants.appSection.profile:
return qsTr("Settings")
case Constants.appSection.node:
return qsTr("Node Management")
case Constants.appSection.communitiesPortal:
return qsTr("Communities Portal")
default:
return fallback
}
}
function getFontSizeBasedOnLetterCount(text) {
if(text.length >= 12)
return 18
if(text.length >= 10)
return 24
if(text.length > 6)
return 28
else
return 34
}
// Leave this function at the bottom of the file as QT Creator messes up the code color after this
function isPunct(c) {
return /(!|\@|#|\$|%|\^|&|\*|\(|\)|\+|\||-|=|\\|{|}|[|]|"|;|'|<|>|\?|,|\.|\/)/.test(c)
}
}