mirror of
https://github.com/Lissy93/dashy.git
synced 2025-01-01 13:32:35 +03:00
⚡ Reliability improvements for icon fallbacks
This commit is contained in:
parent
c9f2483c3e
commit
57554ddcdf
@ -452,7 +452,7 @@ export default {
|
|||||||
height: 2rem;
|
height: 2rem;
|
||||||
padding-top: 4px;
|
padding-top: 4px;
|
||||||
max-width: 14rem;
|
max-width: 14rem;
|
||||||
div img, div svg.missing-image {
|
div img {
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
}
|
}
|
||||||
.tile-title {
|
.tile-title {
|
||||||
@ -473,7 +473,7 @@ export default {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: auto;
|
height: auto;
|
||||||
div img, div svg.missing-image {
|
div img {
|
||||||
width: 2.5rem;
|
width: 2.5rem;
|
||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="`item-icon wrapper-${size}`">
|
<div v-if="icon" :class="`item-icon wrapper-${size}`">
|
||||||
<!-- Font-Awesome Icon -->
|
<!-- Font-Awesome Icon -->
|
||||||
<i v-if="iconType === 'font-awesome'" :class="`${icon} ${size}`" ></i>
|
<i v-if="iconType === 'font-awesome'" :class="`${icon} ${size}`" ></i>
|
||||||
<!-- Emoji Icon -->
|
<!-- Emoji Icon -->
|
||||||
<i v-else-if="iconType === 'emoji'" :class="`emoji-icon ${size}`" >{{getEmoji(iconPath)}}</i>
|
<i v-else-if="iconType === 'emoji'" :class="`emoji-icon ${size}`" >{{getEmoji(iconPath)}}</i>
|
||||||
<!-- Material Design Icon -->
|
<!-- Material Design Icon -->
|
||||||
<span v-else-if="iconType === 'mdi'" :class=" `mdi ${icon} ${size}`"></span>
|
<span v-else-if="iconType === 'mdi'" :class=" `mdi ${icon} ${size}`"></span>
|
||||||
<!-- Simple-Icons -->
|
<!-- Simple-Icons -->
|
||||||
<svg v-else-if="iconType === 'si'" :class="`simple-icons ${size}`" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<svg v-else-if="iconType === 'si' && !broken" :class="`simple-icons ${size}`"
|
||||||
|
role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path :d="getSimpleIcon(icon)" />
|
<path :d="getSimpleIcon(icon)" />
|
||||||
</svg>
|
</svg>
|
||||||
<!-- Standard image asset icon -->
|
<!-- Standard image asset icon -->
|
||||||
@ -15,7 +16,7 @@
|
|||||||
:class="`tile-icon ${size} ${broken ? 'broken' : ''}`"
|
:class="`tile-icon ${size} ${broken ? 'broken' : ''}`"
|
||||||
/>
|
/>
|
||||||
<!-- Icon could not load/ broken url -->
|
<!-- Icon could not load/ broken url -->
|
||||||
<BrokenImage v-if="broken" class="missing-image" />
|
<BrokenImage v-if="broken" :class="`missing-image ${size}`" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -25,8 +26,8 @@ import BrokenImage from '@/assets/interface-icons/broken-icon.svg';
|
|||||||
import ErrorHandler from '@/utils/ErrorHandler';
|
import ErrorHandler from '@/utils/ErrorHandler';
|
||||||
import EmojiUnicodeRegex from '@/utils/EmojiUnicodeRegex';
|
import EmojiUnicodeRegex from '@/utils/EmojiUnicodeRegex';
|
||||||
import emojiLookup from '@/utils/emojis.json';
|
import emojiLookup from '@/utils/emojis.json';
|
||||||
import { faviconApi as defaultFaviconApi, faviconApiEndpoints, iconCdns } from '@/utils/defaults';
|
|
||||||
import { asciiHash } from '@/utils/MiscHelpers';
|
import { asciiHash } from '@/utils/MiscHelpers';
|
||||||
|
import { faviconApi as defaultFaviconApi, faviconApiEndpoints, iconCdns } from '@/utils/defaults';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Icon',
|
name: 'Icon',
|
||||||
@ -36,7 +37,7 @@ export default {
|
|||||||
size: String, // Either small, medium or large
|
size: String, // Either small, medium or large
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
BrokenImage,
|
BrokenImage, // Used when the desired image returns a 404
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
/* Get appConfig from store */
|
/* Get appConfig from store */
|
||||||
@ -60,6 +61,39 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/* Determine icon type, e.g. local or remote asset, SVG, favicon, font-awesome, etc */
|
||||||
|
determineImageType(img) {
|
||||||
|
let imgType = '';
|
||||||
|
if (!img) imgType = 'none';
|
||||||
|
else if (this.isUrl(img)) imgType = 'url';
|
||||||
|
else if (this.isImage(img)) imgType = 'img';
|
||||||
|
else if (img.includes('fa-')) imgType = 'font-awesome';
|
||||||
|
else if (img.includes('mdi-')) imgType = 'mdi';
|
||||||
|
else if (img.includes('si-')) imgType = 'si';
|
||||||
|
else if (img.includes('hl-')) imgType = 'home-lab-icons';
|
||||||
|
else if (img.includes('favicon-')) imgType = 'custom-favicon';
|
||||||
|
else if (img === 'favicon') imgType = 'favicon';
|
||||||
|
else if (img === 'generative') imgType = 'generative';
|
||||||
|
else if (this.isEmoji(img).isEmoji) imgType = 'emoji';
|
||||||
|
else imgType = 'none';
|
||||||
|
return imgType;
|
||||||
|
},
|
||||||
|
/* Return the path to icon asset, depending on icon type */
|
||||||
|
getIconPath(img, url) {
|
||||||
|
switch (this.determineImageType(img)) {
|
||||||
|
case 'url': return img;
|
||||||
|
case 'img': return this.getLocalImagePath(img);
|
||||||
|
case 'favicon': return this.getFavicon(url);
|
||||||
|
case 'custom-favicon': return this.getCustomFavicon(url, img);
|
||||||
|
case 'generative': return this.getGenerativeIcon(url);
|
||||||
|
case 'mdi': return img; // Material design icons
|
||||||
|
case 'simple-icons': return this.getSimpleIcon(img);
|
||||||
|
case 'home-lab-icons': return this.getHomeLabIcon(img);
|
||||||
|
case 'svg': return img; // Local SVG icon
|
||||||
|
case 'emoji': return img; // Emoji/ unicode
|
||||||
|
default: return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
/* Check if a string is in a URL format. Used to identify tile icon source */
|
/* Check if a string is in a URL format. Used to identify tile icon source */
|
||||||
isUrl(str) {
|
isUrl(str) {
|
||||||
const pattern = new RegExp(/(http|https):\/\/(\w+:{0,1}\w*)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%!\-/]))?/);
|
const pattern = new RegExp(/(http|https):\/\/(\w+:{0,1}\w*)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%!\-/]))?/);
|
||||||
@ -73,7 +107,7 @@ export default {
|
|||||||
if (splitPath.length >= 1) return validImgExtensions.includes(splitPath[1]);
|
if (splitPath.length >= 1) return validImgExtensions.includes(splitPath[1]);
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
/* Determins if a given string is an emoji, and if so what type it is */
|
/* Determines if a given string is an emoji, and if so what type it is */
|
||||||
isEmoji(img) {
|
isEmoji(img) {
|
||||||
if (EmojiUnicodeRegex.test(img) && img.match(/./gu).length) { // Is a unicode emoji
|
if (EmojiUnicodeRegex.test(img) && img.match(/./gu).length) { // Is a unicode emoji
|
||||||
return { isEmoji: true, emojiType: 'glyph' };
|
return { isEmoji: true, emojiType: 'glyph' };
|
||||||
@ -84,15 +118,27 @@ export default {
|
|||||||
}
|
}
|
||||||
return { isEmoji: false, emojiType: '' };
|
return { isEmoji: false, emojiType: '' };
|
||||||
},
|
},
|
||||||
/* Formats and gets emoji from unicode or shortcode */
|
/* Returns the corresponding emoji for a shortcode, or shows error if not found */
|
||||||
|
getShortCodeEmoji(emojiCode) {
|
||||||
|
if (emojiLookup[emojiCode]) {
|
||||||
|
return emojiLookup[emojiCode];
|
||||||
|
} else {
|
||||||
|
this.imageNotFound(`No emoji found with name '${emojiCode}'`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* Formats and gets emoji from either unicode, shortcode or glyph */
|
||||||
getEmoji(emojiCode) {
|
getEmoji(emojiCode) {
|
||||||
const { emojiType } = this.isEmoji(emojiCode);
|
const { emojiType } = this.isEmoji(emojiCode);
|
||||||
if (emojiType === 'shortcode') {
|
if (emojiType === 'shortcode') { // Short code emoji
|
||||||
if (emojiLookup[emojiCode]) return emojiLookup[emojiCode];
|
return this.getShortCodeEmoji(emojiCode);
|
||||||
} else if (emojiType === 'unicode') {
|
} else if (emojiType === 'unicode') { // Unicode emoji
|
||||||
return String.fromCodePoint(parseInt(emojiCode.substr(2), 16));
|
return String.fromCodePoint(parseInt(emojiCode.substr(2), 16));
|
||||||
|
} else if (emojiType === 'glyph') { // Emoji is a glyph
|
||||||
|
return emojiCode;
|
||||||
}
|
}
|
||||||
return emojiCode; // Emoji is a glyph already, just return
|
this.imageNotFound(`Unrecognized emoji: '${emojiCode}'`);
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
/* Get favicon URL, for items which use the favicon as their icon */
|
/* Get favicon URL, for items which use the favicon as their icon */
|
||||||
getFavicon(fullUrl, specificApi) {
|
getFavicon(fullUrl, specificApi) {
|
||||||
@ -109,16 +155,17 @@ export default {
|
|||||||
},
|
},
|
||||||
/* Get the URL for a favicon, but using the non-default favicon API */
|
/* Get the URL for a favicon, but using the non-default favicon API */
|
||||||
getCustomFavicon(fullUrl, faviconIdentifier) {
|
getCustomFavicon(fullUrl, faviconIdentifier) {
|
||||||
|
let errorMsg = '';
|
||||||
const faviconApi = faviconIdentifier.split('favicon-')[1];
|
const faviconApi = faviconIdentifier.split('favicon-')[1];
|
||||||
if (!faviconApi) {
|
if (!faviconApi) {
|
||||||
ErrorHandler('Favicon API not specified');
|
errorMsg = 'Favicon API not specified';
|
||||||
} else if (!Object.keys(faviconApiEndpoints).includes(faviconApi)) {
|
} else if (!Object.keys(faviconApiEndpoints).includes(faviconApi)) {
|
||||||
ErrorHandler(`The specified favicon API, '${faviconApi}' cannot be found.`);
|
errorMsg = `The specified favicon API, '${faviconApi}' cannot be found.`;
|
||||||
} else {
|
} else {
|
||||||
return this.getFavicon(fullUrl, faviconApi);
|
return this.getFavicon(fullUrl, faviconApi);
|
||||||
}
|
}
|
||||||
// Error encountered, favicon service not found
|
// Error encountered, favicon service not found
|
||||||
this.broken = true;
|
this.imageNotFound(errorMsg);
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
/* If using favicon for icon, and if service is running locally (determined by local IP) */
|
/* If using favicon for icon, and if service is running locally (determined by local IP) */
|
||||||
@ -140,69 +187,53 @@ export default {
|
|||||||
getSimpleIcon(img) {
|
getSimpleIcon(img) {
|
||||||
const imageName = img.replace('si-', '');
|
const imageName = img.replace('si-', '');
|
||||||
const icon = simpleIcons.Get(imageName);
|
const icon = simpleIcons.Get(imageName);
|
||||||
|
if (!icon) {
|
||||||
|
this.imageNotFound(`No icon was found for '${imageName}' in Simple Icons`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return icon.path;
|
return icon.path;
|
||||||
},
|
},
|
||||||
/* Gets home-lab icon from GitHub */
|
/* Gets home-lab icon from GitHub */
|
||||||
getHomeLabIcon(img) {
|
getHomeLabIcon(img, cdn) {
|
||||||
const imageName = img.replace('hl-', '').toLocaleLowerCase();
|
const imageName = img.replace('hl-', '').toLocaleLowerCase();
|
||||||
return iconCdns.homeLabIcons.replace('{icon}', imageName);
|
return (cdn || iconCdns.homeLabIcons).replace('{icon}', imageName);
|
||||||
},
|
|
||||||
/* Checks if the icon is from a local image, remote URL, SVG or font-awesome */
|
|
||||||
getIconPath(img, url) {
|
|
||||||
switch (this.determineImageType(img)) {
|
|
||||||
case 'url': return img;
|
|
||||||
case 'img': return this.getLocalImagePath(img);
|
|
||||||
case 'favicon': return this.getFavicon(url);
|
|
||||||
case 'custom-favicon': return this.getCustomFavicon(url, img);
|
|
||||||
case 'generative': return this.getGenerativeIcon(url);
|
|
||||||
case 'mdi': return img; // Material design icons
|
|
||||||
case 'simple-icons': return this.getSimpleIcon(img);
|
|
||||||
case 'home-lab-icons': return this.getHomeLabIcon(img);
|
|
||||||
case 'svg': return img; // Local SVG icon
|
|
||||||
case 'emoji': return img; // Emoji/ unicode
|
|
||||||
default: return '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/* Checks if the icon is from a local image, remote URL, SVG or font-awesome */
|
|
||||||
determineImageType(img) {
|
|
||||||
let imgType = '';
|
|
||||||
if (!img) imgType = 'none';
|
|
||||||
else if (this.isUrl(img)) imgType = 'url';
|
|
||||||
else if (this.isImage(img)) imgType = 'img';
|
|
||||||
else if (img.includes('fa-')) imgType = 'font-awesome';
|
|
||||||
else if (img.includes('mdi-')) imgType = 'mdi';
|
|
||||||
else if (img.includes('si-')) imgType = 'si';
|
|
||||||
else if (img.includes('hl-')) imgType = 'home-lab-icons';
|
|
||||||
else if (img.includes('favicon-')) imgType = 'custom-favicon';
|
|
||||||
else if (img === 'favicon') imgType = 'favicon';
|
|
||||||
else if (img === 'generative') imgType = 'generative';
|
|
||||||
else if (this.isEmoji(img).isEmoji) imgType = 'emoji';
|
|
||||||
else imgType = 'none';
|
|
||||||
return imgType;
|
|
||||||
},
|
},
|
||||||
/* For a given URL, return the hostname only. Used for favicon and generative icons */
|
/* For a given URL, return the hostname only. Used for favicon and generative icons */
|
||||||
getHostName(url) {
|
getHostName(url) {
|
||||||
try { return new URL(url).hostname; } catch (e) { return url; }
|
try {
|
||||||
|
return new URL(url).hostname.split('.').slice(-2).join('.');
|
||||||
|
} catch (e) {
|
||||||
|
ErrorHandler('Unable to format URL');
|
||||||
|
return url;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
/* Called when the path to the image cannot be resolved */
|
/* Called when the path to the image cannot be resolved */
|
||||||
imageNotFound() {
|
imageNotFound(errorMsg) {
|
||||||
|
let outputMessage = '';
|
||||||
|
if (errorMsg && typeof errorMsg === 'string') {
|
||||||
|
outputMessage = errorMsg;
|
||||||
|
} else if (!this.icon) {
|
||||||
|
outputMessage = 'Icon not specified';
|
||||||
|
} else {
|
||||||
|
outputMessage = `The path to '${this.icon}' could not be resolved`;
|
||||||
|
}
|
||||||
|
ErrorHandler(outputMessage);
|
||||||
this.broken = true;
|
this.broken = true;
|
||||||
ErrorHandler(`The path to '${this.icon}' could not be resolved`);
|
|
||||||
},
|
},
|
||||||
/* Called when initial icon has resulted in 404. Attempts to find new icon */
|
/* Called when initial icon has resulted in 404. Attempts to find new icon */
|
||||||
getFallbackIcon() {
|
getFallbackIcon() {
|
||||||
if (this.attemptedFallback) return undefined; // If this is second attempt, then give up
|
if (this.attemptedFallback) return undefined; // If this is second attempt, then give up
|
||||||
const { iconType } = this;
|
const { iconType } = this;
|
||||||
const markAsSttempted = () => {
|
const markAsAttempted = () => { this.broken = false; this.attemptedFallback = true; };
|
||||||
this.broken = false;
|
|
||||||
this.attemptedFallback = true;
|
|
||||||
};
|
|
||||||
if (iconType.includes('favicon')) { // Specify fallback for favicon-based icons
|
if (iconType.includes('favicon')) { // Specify fallback for favicon-based icons
|
||||||
markAsSttempted();
|
markAsAttempted();
|
||||||
return this.getFavicon(this.url, 'local');
|
return this.getFavicon(this.url, 'local');
|
||||||
} else if (iconType === 'generative') {
|
} else if (iconType === 'generative') {
|
||||||
markAsSttempted();
|
markAsAttempted();
|
||||||
return this.getGenerativeIcon(this.url, iconCdns.generativeFallback);
|
return this.getGenerativeIcon(this.url, iconCdns.generativeFallback);
|
||||||
|
} else if (iconType === 'home-lab-icons') {
|
||||||
|
markAsAttempted();
|
||||||
|
return this.getHomeLabIcon(this.icon, iconCdns.homeLabIconsFallback);
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
@ -290,7 +321,13 @@ export default {
|
|||||||
}
|
}
|
||||||
/* Icon Not Found */
|
/* Icon Not Found */
|
||||||
.missing-image {
|
.missing-image {
|
||||||
width: 3.5rem;
|
width: 2rem;
|
||||||
|
&.small {
|
||||||
|
width: 1.5rem !important;
|
||||||
|
}
|
||||||
|
&.large {
|
||||||
|
width: 2.5rem;
|
||||||
|
}
|
||||||
path {
|
path {
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,8 @@ module.exports = {
|
|||||||
faviconApi: 'allesedv',
|
faviconApi: 'allesedv',
|
||||||
/* The default sort order for sections */
|
/* The default sort order for sections */
|
||||||
sortOrder: 'default',
|
sortOrder: 'default',
|
||||||
|
/* If no 'target' specified, this is the default opening method */
|
||||||
|
openingMethod: 'newtab',
|
||||||
/* The page paths for each route within the app for the router */
|
/* The page paths for each route within the app for the router */
|
||||||
routePaths: {
|
routePaths: {
|
||||||
home: '/home',
|
home: '/home',
|
||||||
@ -74,6 +76,18 @@ module.exports = {
|
|||||||
'high-contrast-dark',
|
'high-contrast-dark',
|
||||||
'high-contrast-light',
|
'high-contrast-light',
|
||||||
],
|
],
|
||||||
|
/* Default color options for the theme configurator swatches */
|
||||||
|
swatches: [
|
||||||
|
['#eb5cad', '#985ceb', '#5346f3', '#5c90eb'],
|
||||||
|
['#5cdfeb', '#00CCB4', '#5ceb8d', '#afeb5c'],
|
||||||
|
['#eff961', '#ebb75c', '#eb615c', '#eb2d6c'],
|
||||||
|
['#060913', '#141b33', '#1c2645', '#263256'],
|
||||||
|
['#2b2d42', '#1a535c', '#372424', '#312437'],
|
||||||
|
['#f5f5f5', '#d9d9d9', '#bfbfbf', '#9a9a9a'],
|
||||||
|
['#636363', '#363636', '#313941', '#0d0d0d'],
|
||||||
|
],
|
||||||
|
/* Which CSS variables to show in the first view of theme configurator */
|
||||||
|
mainCssVars: ['primary', 'background', 'background-darker'],
|
||||||
/* Which structural components should be visible by default */
|
/* Which structural components should be visible by default */
|
||||||
visibleComponents: {
|
visibleComponents: {
|
||||||
splashScreen: false,
|
splashScreen: false,
|
||||||
@ -88,8 +102,6 @@ module.exports = {
|
|||||||
'minimal',
|
'minimal',
|
||||||
'login',
|
'login',
|
||||||
'download',
|
'download',
|
||||||
'landing-page-minimal',
|
|
||||||
// '404',
|
|
||||||
],
|
],
|
||||||
/* Key names for local storage identifiers */
|
/* Key names for local storage identifiers */
|
||||||
localStorageKeys: {
|
localStorageKeys: {
|
||||||
@ -138,17 +150,14 @@ module.exports = {
|
|||||||
PAGE_INFO: 'pageInfo',
|
PAGE_INFO: 'pageInfo',
|
||||||
APP_CONFIG: 'appConfig',
|
APP_CONFIG: 'appConfig',
|
||||||
SECTIONS: 'sections',
|
SECTIONS: 'sections',
|
||||||
|
WIDGETS: 'widgets',
|
||||||
},
|
},
|
||||||
/* Which CSS variables to show in the first view of theme configurator */
|
|
||||||
mainCssVars: ['primary', 'background', 'background-darker'],
|
|
||||||
/* Amount of time to show splash screen, when enabled, in milliseconds */
|
/* Amount of time to show splash screen, when enabled, in milliseconds */
|
||||||
splashScreenTime: 1900,
|
splashScreenTime: 1000,
|
||||||
/* Page meta-data, rendered in the header of each view */
|
/* Page meta-data, rendered in the header of each view */
|
||||||
metaTagData: [
|
metaTagData: [
|
||||||
{ name: 'description', content: 'A simple static homepage for you\'re server' },
|
{ name: 'description', content: 'A simple static homepage for you\'re server' },
|
||||||
],
|
],
|
||||||
/* If no 'target' specified, this is the default opening method */
|
|
||||||
openingMethod: 'newtab',
|
|
||||||
/* Default option for Toast messages */
|
/* Default option for Toast messages */
|
||||||
toastedOptions: {
|
toastedOptions: {
|
||||||
position: 'bottom-center',
|
position: 'bottom-center',
|
||||||
@ -192,6 +201,7 @@ module.exports = {
|
|||||||
localPath: './item-icons',
|
localPath: './item-icons',
|
||||||
faviconName: 'favicon.ico',
|
faviconName: 'favicon.ico',
|
||||||
homeLabIcons: 'https://raw.githubusercontent.com/WalkxCode/dashboard-icons/master/png/{icon}.png',
|
homeLabIcons: 'https://raw.githubusercontent.com/WalkxCode/dashboard-icons/master/png/{icon}.png',
|
||||||
|
homeLabIconsFallback: 'https://raw.githubusercontent.com/NX211/homer-icons/master/png/{icon}.png',
|
||||||
},
|
},
|
||||||
/* URLs for web search engines */
|
/* URLs for web search engines */
|
||||||
searchEngineUrls: {
|
searchEngineUrls: {
|
||||||
@ -233,16 +243,6 @@ module.exports = {
|
|||||||
'/so': 'stackoverflow',
|
'/so': 'stackoverflow',
|
||||||
'/wa': 'wolframalpha',
|
'/wa': 'wolframalpha',
|
||||||
},
|
},
|
||||||
/* Available built-in colors for the theme builder */
|
|
||||||
swatches: [
|
|
||||||
['#eb5cad', '#985ceb', '#5346f3', '#5c90eb'],
|
|
||||||
['#5cdfeb', '#00CCB4', '#5ceb8d', '#afeb5c'],
|
|
||||||
['#eff961', '#ebb75c', '#eb615c', '#eb2d6c'],
|
|
||||||
['#060913', '#141b33', '#1c2645', '#263256'],
|
|
||||||
['#2b2d42', '#1a535c', '#372424', '#312437'],
|
|
||||||
['#f5f5f5', '#d9d9d9', '#bfbfbf', '#9a9a9a'],
|
|
||||||
['#636363', '#363636', '#313941', '#0d0d0d'],
|
|
||||||
],
|
|
||||||
/* Use your own self-hosted Sentry instance. Only used if error reporting is turned on */
|
/* Use your own self-hosted Sentry instance. Only used if error reporting is turned on */
|
||||||
sentryDsn: 'https://3138ea85f15a4fa883a5b27a4dc8ee28@o937511.ingest.sentry.io/5887934',
|
sentryDsn: 'https://3138ea85f15a4fa883a5b27a4dc8ee28@o937511.ingest.sentry.io/5887934',
|
||||||
/* A JS enum for indicating the user state, when guest mode + authentication is enabled */
|
/* A JS enum for indicating the user state, when guest mode + authentication is enabled */
|
||||||
|
Loading…
Reference in New Issue
Block a user