snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
This commit is contained in:
43
packages/favicon/CHANGELOG.md
Normal file
43
packages/favicon/CHANGELOG.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# @rocket.chat/favicon
|
||||
|
||||
## 0.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ([#38989](https://github.com/RocketChat/Rocket.Chat/pull/38989)) chore(eslint): Upgrades ESLint and its configuration
|
||||
|
||||
## 0.0.5-rc.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ([#38989](https://github.com/RocketChat/Rocket.Chat/pull/38989)) chore(eslint): Upgrades ESLint and its configuration
|
||||
|
||||
## 0.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ([#37447](https://github.com/RocketChat/Rocket.Chat/pull/37447)) Fixes a GUI crash due to an unhandled cross-origin error happening while rendering favicon badge
|
||||
|
||||
## 0.0.4-rc.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ([#37447](https://github.com/RocketChat/Rocket.Chat/pull/37447)) Fixes a GUI crash due to an unhandled cross-origin error happening while rendering favicon badge
|
||||
|
||||
## 0.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ([#37512](https://github.com/RocketChat/Rocket.Chat/pull/37512) by [@dionisio-bot](https://github.com/dionisio-bot)) Fixes a GUI crash due to an unhandled cross-origin error happening while rendering favicon badge
|
||||
|
||||
## 0.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ([#31138](https://github.com/RocketChat/Rocket.Chat/pull/31138)) feat(uikit): Move `@rocket.chat/ui-kit` package to the main monorepo
|
||||
|
||||
## 0.0.2-rc.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- b223cbde14: feat(uikit): Move `@rocket.chat/ui-kit` package to the main monorepo
|
||||
23
packages/favicon/package.json
Normal file
23
packages/favicon/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@rocket.chat/favicon",
|
||||
"version": "0.0.5",
|
||||
"private": true,
|
||||
"main": "./dist/index.js",
|
||||
"typings": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"/dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rm -rf dist && tsc -p tsconfig.json",
|
||||
"dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "~9.39.5",
|
||||
"typescript": "~5.9.3"
|
||||
},
|
||||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
87
packages/favicon/src/badge.ts
Normal file
87
packages/favicon/src/badge.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
export type Badge = number | string | null | undefined;
|
||||
|
||||
const getBadgeText = (badge: NonNullable<Badge>) => {
|
||||
if (typeof badge === 'number') {
|
||||
badge = Math.abs(badge | 0);
|
||||
|
||||
if (badge > 999) {
|
||||
return `${badge > 9999 ? 9 : Math.floor(badge / 1000)}k+`;
|
||||
}
|
||||
|
||||
return String(badge);
|
||||
}
|
||||
|
||||
return String(badge);
|
||||
};
|
||||
|
||||
const getBadgeStyle = (badge: NonNullable<Badge>) => {
|
||||
if (typeof badge === 'number' && badge > 0) {
|
||||
return { bgColor: '#ac1b1b', textColor: '#fff', fontFamily: 'sans-serif', fontStyle: 'bold' };
|
||||
}
|
||||
|
||||
if (typeof badge === 'string' && badge !== '') {
|
||||
return { bgColor: '#3d8a3a', textColor: '#fff', fontFamily: 'sans-serif', fontStyle: 'bold' };
|
||||
}
|
||||
|
||||
throw new Error('Invalid badge type');
|
||||
};
|
||||
|
||||
export const drawBadge = (badge: Badge, context: CanvasRenderingContext2D) => {
|
||||
if (!badge) {
|
||||
return;
|
||||
}
|
||||
|
||||
const text = getBadgeText(badge);
|
||||
const { fontFamily, fontStyle, bgColor, textColor } = getBadgeStyle(badge);
|
||||
|
||||
let w = 0.6;
|
||||
const h = 0.6;
|
||||
|
||||
let x = 0.4;
|
||||
const y = 0;
|
||||
|
||||
if (text.length === 2) {
|
||||
x -= w * 0.4;
|
||||
w *= 1.4;
|
||||
} else if (text.length >= 3) {
|
||||
x -= w * 0.65;
|
||||
w *= 1.65;
|
||||
}
|
||||
|
||||
context.beginPath();
|
||||
|
||||
if (text.length > 1) {
|
||||
context.moveTo(x + w / 2, y);
|
||||
context.lineTo(x + w - h / 2, y);
|
||||
context.quadraticCurveTo(x + w, y, x + w, y + h / 2);
|
||||
context.lineTo(x + w, y + h - h / 2);
|
||||
context.quadraticCurveTo(x + w, y + h, x + w - h / 2, y + h);
|
||||
context.lineTo(x + h / 2, y + h);
|
||||
context.quadraticCurveTo(x, y + h, x, y + h - h / 2);
|
||||
context.lineTo(x, y + h / 2);
|
||||
context.quadraticCurveTo(x, y, x + h / 2, y);
|
||||
} else {
|
||||
context.arc(x + w / 2, y + h / 2, h / 2, 0, 2 * Math.PI);
|
||||
}
|
||||
|
||||
context.fillStyle = bgColor;
|
||||
context.fill();
|
||||
|
||||
context.closePath();
|
||||
|
||||
context.font = `${fontStyle} ${h * (text.length > 2 ? 0.85 : 1)}px ${fontFamily}`;
|
||||
context.textAlign = 'center';
|
||||
context.fillStyle = textColor;
|
||||
|
||||
context.beginPath();
|
||||
|
||||
context.stroke();
|
||||
|
||||
if (text.length > 3) {
|
||||
context.fillText(text, x + w / 2, y + h - h * 0.2);
|
||||
} else {
|
||||
context.fillText(text, x + w / 2, y + h - h * 0.15);
|
||||
}
|
||||
|
||||
context.closePath();
|
||||
};
|
||||
118
packages/favicon/src/index.ts
Normal file
118
packages/favicon/src/index.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import type { Badge } from './badge';
|
||||
import { drawBadge } from './badge';
|
||||
|
||||
const getFavicons = () => {
|
||||
const favicons = Array.from(document.head.getElementsByTagName('link')).filter((link) =>
|
||||
/(^|\s)icon(\s|$)/i.test(link.getAttribute('rel') ?? ''),
|
||||
);
|
||||
|
||||
if (favicons.length === 0) {
|
||||
const link = document.createElement('link');
|
||||
link.setAttribute('rel', 'icon');
|
||||
document.head.appendChild(link);
|
||||
favicons.push(link);
|
||||
}
|
||||
|
||||
for (const favicon of favicons) {
|
||||
favicon.setAttribute('type', 'image/png');
|
||||
}
|
||||
|
||||
return favicons;
|
||||
};
|
||||
|
||||
const fetchFaviconImage = async (url: string | undefined) => {
|
||||
const img = new Image();
|
||||
|
||||
if (url) {
|
||||
img.crossOrigin = 'anonymous';
|
||||
img.src = url;
|
||||
} else {
|
||||
img.src = '';
|
||||
img.width = 32;
|
||||
img.height = 32;
|
||||
}
|
||||
|
||||
return new Promise<HTMLImageElement>((resolve, reject) => {
|
||||
img.onload = () => {
|
||||
resolve(img);
|
||||
};
|
||||
img.onerror = () => {
|
||||
reject(new Error('Failed to load image'));
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const renderAndUpdate = ({
|
||||
badge,
|
||||
canvas,
|
||||
favicons,
|
||||
context,
|
||||
img,
|
||||
}: {
|
||||
badge: Badge;
|
||||
canvas: HTMLCanvasElement;
|
||||
favicons: HTMLLinkElement[];
|
||||
context: CanvasRenderingContext2D;
|
||||
img: HTMLImageElement;
|
||||
}) => {
|
||||
context.scale(canvas.width, canvas.height);
|
||||
|
||||
context.clearRect(0, 0, 1, 1);
|
||||
|
||||
context.drawImage(img, 0, 0, 1, 1);
|
||||
|
||||
drawBadge(badge, context);
|
||||
|
||||
context.setTransform(1, 0, 0, 1, 0, 0);
|
||||
|
||||
const url = canvas.toDataURL('image/png');
|
||||
|
||||
for (const icon of favicons) {
|
||||
icon.setAttribute('href', url);
|
||||
}
|
||||
};
|
||||
|
||||
export const manageFavicon = () => {
|
||||
let pendingBadge: Badge;
|
||||
|
||||
let updateOrCollect = (badge: Badge) => {
|
||||
pendingBadge = badge;
|
||||
};
|
||||
|
||||
const init = async () => {
|
||||
const favicons = getFavicons();
|
||||
const lastFavicon = favicons[favicons.length - 1];
|
||||
const faviconURL = lastFavicon.getAttribute('href') ?? undefined;
|
||||
const img = await fetchFaviconImage(faviconURL);
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = img.width > 0 ? img.width : 32;
|
||||
canvas.height = img.height > 0 ? img.height : 32;
|
||||
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
if (!context) {
|
||||
throw new Error('Failed to create canvas context');
|
||||
}
|
||||
|
||||
updateOrCollect = (badge) => {
|
||||
try {
|
||||
renderAndUpdate({ badge, canvas, favicons, context, img });
|
||||
} catch (error) {
|
||||
console.error('Error rendering favicon:', error);
|
||||
}
|
||||
};
|
||||
|
||||
if (pendingBadge) {
|
||||
updateOrCollect(pendingBadge);
|
||||
pendingBadge = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
init().catch((error) => {
|
||||
console.error('Error initializing favicon manager:', error);
|
||||
});
|
||||
|
||||
return (badge: Badge) => {
|
||||
updateOrCollect(badge);
|
||||
};
|
||||
};
|
||||
8
packages/favicon/tsconfig.json
Normal file
8
packages/favicon/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@rocket.chat/tsconfig/client.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": ["./src/**/*"]
|
||||
}
|
||||
Reference in New Issue
Block a user