diff --git a/package.json b/package.json index 4118e8876..7a50ff3c4 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "afterSign": "scripts/notarize.js" }, "dependencies": { + "@discordapp/twemoji": "15.0.3", "@electron/remote": "2.1.2", "@primer/octicons-react": "19.11.0", "axios": "1.7.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 498ff03b5..ee46f7ede 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@discordapp/twemoji': + specifier: 15.0.3 + version: 15.0.3 '@electron/remote': specifier: 2.1.2 version: 2.1.2(electron@31.4.0) @@ -396,6 +399,9 @@ packages: resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==} engines: {node: '>= 8.9.0'} + '@discordapp/twemoji@15.0.3': + resolution: {integrity: sha512-5t0LLrNaSqViG0cSaomWwfR0+3fWqok+xLq40M8hJHxNX7s8gIoyNZYybQJo+s5/rGMjgdldpt8Ox8MapGvBUA==} + '@discoveryjs/json-ext@0.5.7': resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} @@ -619,6 +625,9 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@twemoji/parser@15.0.0': + resolution: {integrity: sha512-lh9515BNsvKSNvyUqbj5yFu83iIDQ77SwVcsN/SnEGawczhsKU6qWuogewN1GweTi5Imo5ToQ9s+nNTf97IXvg==} + '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -2106,6 +2115,9 @@ packages: jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + jsonfile@5.0.0: + resolution: {integrity: sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==} + jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} @@ -3511,6 +3523,13 @@ snapshots: ajv: 6.12.6 ajv-keywords: 3.5.2(ajv@6.12.6) + '@discordapp/twemoji@15.0.3': + dependencies: + '@twemoji/parser': 15.0.0 + fs-extra: 8.1.0 + jsonfile: 5.0.0 + universalify: 0.1.2 + '@discoveryjs/json-ext@0.5.7': {} '@electron/asar@3.2.9': @@ -3865,6 +3884,8 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@twemoji/parser@15.0.0': {} + '@types/aria-query@5.0.4': {} '@types/babel__core@7.20.5': @@ -5726,6 +5747,12 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonfile@5.0.0: + dependencies: + universalify: 0.1.2 + optionalDependencies: + graceful-fs: 4.2.11 + jsonfile@6.1.0: dependencies: universalify: 2.0.1 diff --git a/src/__mocks__/utils.ts b/src/__mocks__/utils.ts index cf1c57cb9..0e42e61a2 100644 --- a/src/__mocks__/utils.ts +++ b/src/__mocks__/utils.ts @@ -1,7 +1,7 @@ -export function setPlatform(platform: NodeJS.Platform) { - Object.defineProperty(process, 'platform', { - value: platform, - }); +import * as helpers from '../utils/helpers'; + +export function mockDirectoryPath() { + jest.spyOn(helpers, 'getDirectoryPath').mockReturnValue('/mocked/dir/name'); } /** diff --git a/src/components/AccountNotifications.test.tsx b/src/components/AccountNotifications.test.tsx index e5e844d4d..1c7d7e2e6 100644 --- a/src/components/AccountNotifications.test.tsx +++ b/src/components/AccountNotifications.test.tsx @@ -1,6 +1,6 @@ import { act, fireEvent, render, screen } from '@testing-library/react'; import { mockGitHubCloudAccount, mockSettings } from '../__mocks__/state-mocks'; -import { ensureStableEmojis } from '../__mocks__/utils'; +import { ensureStableEmojis, mockDirectoryPath } from '../__mocks__/utils'; import { AppContext } from '../context/App'; import { GroupBy } from '../types'; import { mockGitHubNotifications } from '../utils/api/__mocks__/response-mocks'; @@ -14,6 +14,7 @@ jest.mock('./RepositoryNotifications', () => ({ describe('components/AccountNotifications.tsx', () => { beforeEach(() => { ensureStableEmojis(); + mockDirectoryPath(); }); it('should render itself - group notifications by repositories', () => { diff --git a/src/components/AllRead.test.tsx b/src/components/AllRead.test.tsx index c58becdd6..d5067b0f3 100644 --- a/src/components/AllRead.test.tsx +++ b/src/components/AllRead.test.tsx @@ -1,10 +1,11 @@ import { render } from '@testing-library/react'; -import { ensureStableEmojis } from '../__mocks__/utils'; +import { ensureStableEmojis, mockDirectoryPath } from '../__mocks__/utils'; import { AllRead } from './AllRead'; describe('components/AllRead.tsx', () => { beforeEach(() => { ensureStableEmojis(); + mockDirectoryPath(); }); it('should render itself & its children', () => { diff --git a/src/components/AllRead.tsx b/src/components/AllRead.tsx index b34c932b9..206c31c43 100644 --- a/src/components/AllRead.tsx +++ b/src/components/AllRead.tsx @@ -1,5 +1,6 @@ import { type FC, useMemo } from 'react'; import { Constants } from '../utils/constants'; +import { EmojiText } from './EmojiText'; export const AllRead: FC = () => { const emoji = useMemo( @@ -12,9 +13,11 @@ export const AllRead: FC = () => { return (
-

{emoji}

+
+ +
-

No new notifications.

+
No new notifications.
); }; diff --git a/src/components/EmojiText.test.tsx b/src/components/EmojiText.test.tsx new file mode 100644 index 000000000..8387e3d5d --- /dev/null +++ b/src/components/EmojiText.test.tsx @@ -0,0 +1,17 @@ +import { render } from '@testing-library/react'; +import { mockDirectoryPath } from '../__mocks__/utils'; +import { EmojiText, type IEmojiText } from './EmojiText'; + +describe('components/icons/Emoji.tsx', () => { + beforeEach(() => { + mockDirectoryPath(); + }); + + it('should render', () => { + const props: IEmojiText = { + text: '🍺', + }; + const tree = render(); + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/src/components/EmojiText.tsx b/src/components/EmojiText.tsx new file mode 100644 index 000000000..4c7e1d355 --- /dev/null +++ b/src/components/EmojiText.tsx @@ -0,0 +1,37 @@ +import path from 'node:path'; +import twemoji from '@discordapp/twemoji'; +import { type FC, useEffect, useRef } from 'react'; +import { getDirectoryPath } from '../utils/helpers'; + +export interface IEmojiText { + text: string; +} + +type TwemojiOptions = { + base: string; + size: string; + ext: string; +}; + +export const EmojiText: FC = ({ text }) => { + const ref = useRef(null); + + useEffect(() => { + if (ref.current) { + ref.current.innerHTML = twemoji.parse(text, { + folder: 'svg', + ext: '.svg', + callback: (icon: string, options: TwemojiOptions, _variant: string) => { + const source = path.resolve( + getDirectoryPath(), + '../../node_modules/@discordapp/twemoji/dist', + ); + + return ''.concat(source, '/', options.size, '/', icon, options.ext); + }, + }); + } + }, [text]); + + return ; +}; diff --git a/src/components/Oops.test.tsx b/src/components/Oops.test.tsx index 128055d3d..730d029bd 100644 --- a/src/components/Oops.test.tsx +++ b/src/components/Oops.test.tsx @@ -1,7 +1,12 @@ import { render } from '@testing-library/react'; +import { mockDirectoryPath } from '../__mocks__/utils'; import { Oops } from './Oops'; describe('components/Oops.tsx', () => { + beforeEach(() => { + mockDirectoryPath(); + }); + it('should render itself & its children', () => { const mockError = { title: 'Error title', diff --git a/src/components/Oops.tsx b/src/components/Oops.tsx index 7f7b21ea4..c5ac1e724 100644 --- a/src/components/Oops.tsx +++ b/src/components/Oops.tsx @@ -1,5 +1,6 @@ import { type FC, useMemo } from 'react'; import type { GitifyError } from '../types'; +import { EmojiText } from './EmojiText'; interface IOops { error: GitifyError; @@ -13,9 +14,11 @@ export const Oops: FC = ({ error }: IOops) => { return (
-

{emoji}

+
+ +
-

{error.title}

+
{error.title}
{error.descriptions.map((description, i) => { return ( // biome-ignore lint/suspicious/noArrayIndexKey: using index for key to keep the error constants clean diff --git a/src/components/__snapshots__/AccountNotifications.test.tsx.snap b/src/components/__snapshots__/AccountNotifications.test.tsx.snap index 295295cda..17a0b271c 100644 --- a/src/components/__snapshots__/AccountNotifications.test.tsx.snap +++ b/src/components/__snapshots__/AccountNotifications.test.tsx.snap @@ -121,16 +121,24 @@ exports[`components/AccountNotifications.tsx should render itself - account erro
-

- 🔥 -

-

+ 🔥 + foo + +

+
Error title - +
@@ -256,16 +264,24 @@ exports[`components/AccountNotifications.tsx should render itself - account erro
-

- 🔥 -

-

+ 🔥 + foo + +

+
Error title - +
@@ -1764,16 +1780,23 @@ exports[`components/AccountNotifications.tsx should render itself - no notificat
-

- 🎊 -

-

+ 🎊 + +

+
No new notifications. - +
, @@ -1894,16 +1917,23 @@ exports[`components/AccountNotifications.tsx should render itself - no notificat
-

- 🎊 -

-

+ 🎊 + +

+
No new notifications. - +
, "debug": [Function], diff --git a/src/components/__snapshots__/AllRead.test.tsx.snap b/src/components/__snapshots__/AllRead.test.tsx.snap index 484584b50..f2b294a6b 100644 --- a/src/components/__snapshots__/AllRead.test.tsx.snap +++ b/src/components/__snapshots__/AllRead.test.tsx.snap @@ -8,16 +8,23 @@ exports[`components/AllRead.tsx should render itself & its children 1`] = `
-

- 🎊 -

-

+ 🎊 + +

+
No new notifications. - +
, @@ -25,16 +32,23 @@ exports[`components/AllRead.tsx should render itself & its children 1`] = `
-

- 🎊 -

-

+ 🎊 + +

+
No new notifications. - +
, "debug": [Function], diff --git a/src/components/__snapshots__/EmojiText.test.tsx.snap b/src/components/__snapshots__/EmojiText.test.tsx.snap new file mode 100644 index 000000000..5117ddb54 --- /dev/null +++ b/src/components/__snapshots__/EmojiText.test.tsx.snap @@ -0,0 +1,80 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`components/icons/Emoji.tsx should render 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+ + 🍺 + +
+ , + "container":
+ + 🍺 + +
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/src/components/__snapshots__/Oops.test.tsx.snap b/src/components/__snapshots__/Oops.test.tsx.snap index 654158f4c..428ab8e5a 100644 --- a/src/components/__snapshots__/Oops.test.tsx.snap +++ b/src/components/__snapshots__/Oops.test.tsx.snap @@ -8,16 +8,24 @@ exports[`components/Oops.tsx should render itself & its children 1`] = `
-

- 🔥 -

-

+ 🔥 + foo + +

+
Error title - +
@@ -30,16 +38,24 @@ exports[`components/Oops.tsx should render itself & its children 1`] = `
-

- 🔥 -

-

+ 🔥 + foo + +

+
Error title - +
diff --git a/src/electron/index.html b/src/electron/index.html index 89b4b9064..99a1d2ef8 100644 --- a/src/electron/index.html +++ b/src/electron/index.html @@ -79,5 +79,16 @@ width: 100%; height: 2px; } + + /** + * Set emoji size according to surrounding text. + * Ref: https://github.com/jdecked/twemoji?tab=readme-ov-file#inline-styles + */ + img.emoji { + height: 1em; + width: 1em; + margin: 0 0.05em 0 0.1em; + vertical-align: -0.1em; + } diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 10094b417..eb688437d 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -201,3 +201,7 @@ export function getFilterCount(settings: SettingsState): number { return count; } + +export function getDirectoryPath(): string { + return `${__dirname}`; +}