diff --git a/app/javascript/mastodon/utils/__tests__/base64-test.js b/app/javascript/mastodon/utils/__tests__/base64-test.ts similarity index 100% rename from app/javascript/mastodon/utils/__tests__/base64-test.js rename to app/javascript/mastodon/utils/__tests__/base64-test.ts diff --git a/app/javascript/mastodon/utils/__tests__/html-test.js b/app/javascript/mastodon/utils/__tests__/html-test.s similarity index 100% rename from app/javascript/mastodon/utils/__tests__/html-test.js rename to app/javascript/mastodon/utils/__tests__/html-test.s diff --git a/app/javascript/mastodon/utils/config.js b/app/javascript/mastodon/utils/config.js deleted file mode 100644 index 932cd0cbf..000000000 --- a/app/javascript/mastodon/utils/config.js +++ /dev/null @@ -1,10 +0,0 @@ -import ready from '../ready'; - -export let assetHost = ''; - -ready(() => { - const cdnHost = document.querySelector('meta[name=cdn-host]'); - if (cdnHost) { - assetHost = cdnHost.content || ''; - } -}); diff --git a/app/javascript/mastodon/utils/config.ts b/app/javascript/mastodon/utils/config.ts new file mode 100644 index 000000000..9222c89d1 --- /dev/null +++ b/app/javascript/mastodon/utils/config.ts @@ -0,0 +1,13 @@ +import ready from '../ready'; + +export let assetHost = ''; + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +ready(() => { + const cdnHost = document.querySelector( + 'meta[name=cdn-host]', + ); + if (cdnHost) { + assetHost = cdnHost.content || ''; + } +}); diff --git a/app/javascript/mastodon/utils/html.js b/app/javascript/mastodon/utils/html.js deleted file mode 100644 index 247e98c88..000000000 --- a/app/javascript/mastodon/utils/html.js +++ /dev/null @@ -1,6 +0,0 @@ -// NB: This function can still return unsafe HTML -export const unescapeHTML = (html) => { - const wrapper = document.createElement('div'); - wrapper.innerHTML = html.replace(//g, '\n').replace(/<\/p>

/g, '\n\n').replace(/<[^>]*>/g, ''); - return wrapper.textContent; -}; diff --git a/app/javascript/mastodon/utils/html.ts b/app/javascript/mastodon/utils/html.ts new file mode 100644 index 000000000..0145a0455 --- /dev/null +++ b/app/javascript/mastodon/utils/html.ts @@ -0,0 +1,9 @@ +// NB: This function can still return unsafe HTML +export const unescapeHTML = (html: string) => { + const wrapper = document.createElement('div'); + wrapper.innerHTML = html + .replace(//g, '\n') + .replace(/<\/p>

/g, '\n\n') + .replace(/<[^>]*>/g, ''); + return wrapper.textContent; +}; diff --git a/app/javascript/mastodon/utils/icons.jsx b/app/javascript/mastodon/utils/icons.tsx similarity index 69% rename from app/javascript/mastodon/utils/icons.jsx rename to app/javascript/mastodon/utils/icons.tsx index be566032e..6e432e32f 100644 --- a/app/javascript/mastodon/utils/icons.jsx +++ b/app/javascript/mastodon/utils/icons.tsx @@ -1,13 +1,23 @@ // Copied from emoji-mart for consistency with emoji picker and since // they don't export the icons in the package export const loupeIcon = ( - + ); export const deleteIcon = ( - + ); diff --git a/app/javascript/mastodon/utils/log_out.js b/app/javascript/mastodon/utils/log_out.ts similarity index 100% rename from app/javascript/mastodon/utils/log_out.js rename to app/javascript/mastodon/utils/log_out.ts diff --git a/app/javascript/mastodon/utils/notifications.js b/app/javascript/mastodon/utils/notifications.js deleted file mode 100644 index 42623ac7c..000000000 --- a/app/javascript/mastodon/utils/notifications.js +++ /dev/null @@ -1,30 +0,0 @@ -// Handles browser quirks, based on -// https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API - -const checkNotificationPromise = () => { - try { - // eslint-disable-next-line promise/valid-params, promise/catch-or-return - Notification.requestPermission().then(); - } catch(e) { - return false; - } - - return true; -}; - -const handlePermission = (permission, callback) => { - // Whatever the user answers, we make sure Chrome stores the information - if(!('permission' in Notification)) { - Notification.permission = permission; - } - - callback(Notification.permission); -}; - -export const requestNotificationPermission = (callback) => { - if (checkNotificationPromise()) { - Notification.requestPermission().then((permission) => handlePermission(permission, callback)).catch(console.warn); - } else { - Notification.requestPermission((permission) => handlePermission(permission, callback)); - } -}; diff --git a/app/javascript/mastodon/utils/notifications.ts b/app/javascript/mastodon/utils/notifications.ts new file mode 100644 index 000000000..08f677f8f --- /dev/null +++ b/app/javascript/mastodon/utils/notifications.ts @@ -0,0 +1,13 @@ +/** + * Tries Notification.requestPermission, console warning instead of rejecting on error. + * @param callback Runs with the permission result on completion. + */ +export const requestNotificationPermission = async ( + callback: NotificationPermissionCallback, +) => { + try { + callback(await Notification.requestPermission()); + } catch (error) { + console.warn(error); + } +}; diff --git a/app/javascript/mastodon/utils/react_router.jsx b/app/javascript/mastodon/utils/react_router.tsx similarity index 53% rename from app/javascript/mastodon/utils/react_router.jsx rename to app/javascript/mastodon/utils/react_router.tsx index fa8f0db2b..0682fee55 100644 --- a/app/javascript/mastodon/utils/react_router.jsx +++ b/app/javascript/mastodon/utils/react_router.tsx @@ -1,8 +1,8 @@ -import PropTypes from "prop-types"; +import PropTypes from 'prop-types'; -import { __RouterContext } from "react-router"; +import { __RouterContext } from 'react-router'; -import hoistStatics from "hoist-non-react-statics"; +import hoistStatics from 'hoist-non-react-statics'; export const WithRouterPropTypes = { match: PropTypes.object.isRequired, @@ -16,31 +16,37 @@ export const WithOptionalRouterPropTypes = { history: PropTypes.object, }; +export interface OptionalRouterProps { + ref: unknown; + wrappedComponentRef: unknown; +} + // This is copied from https://github.com/remix-run/react-router/blob/v5.3.4/packages/react-router/modules/withRouter.js // but does not fail if called outside of a React Router context -export function withOptionalRouter(Component) { - const displayName = `withRouter(${Component.displayName || Component.name})`; - const C = props => { +export function withOptionalRouter< + ComponentType extends React.ComponentType, +>(Component: ComponentType) { + const displayName = `withRouter(${Component.displayName ?? Component.name})`; + const C = (props: React.ComponentProps) => { const { wrappedComponentRef, ...remainingProps } = props; return ( <__RouterContext.Consumer> - {context => { - if(context) + {(context) => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (context) { return ( + // @ts-expect-error - Dynamic covariant generic components are tough to type. ); - else - return ( - - ); + } else { + // @ts-expect-error - Dynamic covariant generic components are tough to type. + return ; + } }} ); @@ -53,8 +59,8 @@ export function withOptionalRouter(Component) { wrappedComponentRef: PropTypes.oneOfType([ PropTypes.string, PropTypes.func, - PropTypes.object - ]) + PropTypes.object, + ]), }; return hoistStatics(C, Component); diff --git a/app/javascript/mastodon/utils/scrollbar.js b/app/javascript/mastodon/utils/scrollbar.ts similarity index 70% rename from app/javascript/mastodon/utils/scrollbar.js rename to app/javascript/mastodon/utils/scrollbar.ts index ca87dd76f..d505df124 100644 --- a/app/javascript/mastodon/utils/scrollbar.js +++ b/app/javascript/mastodon/utils/scrollbar.ts @@ -1,11 +1,7 @@ import { isMobile } from '../is_mobile'; -/** @type {number | null} */ -let cachedScrollbarWidth = null; +let cachedScrollbarWidth: number | null = null; -/** - * @returns {number} - */ const getActualScrollbarWidth = () => { const outer = document.createElement('div'); outer.style.visibility = 'hidden'; @@ -16,20 +12,19 @@ const getActualScrollbarWidth = () => { outer.appendChild(inner); const scrollbarWidth = outer.offsetWidth - inner.offsetWidth; - outer.parentNode.removeChild(outer); + outer.remove(); return scrollbarWidth; }; -/** - * @returns {number} - */ export const getScrollbarWidth = () => { if (cachedScrollbarWidth !== null) { return cachedScrollbarWidth; } - const scrollbarWidth = isMobile(window.innerWidth) ? 0 : getActualScrollbarWidth(); + const scrollbarWidth = isMobile(window.innerWidth) + ? 0 + : getActualScrollbarWidth(); cachedScrollbarWidth = scrollbarWidth; return scrollbarWidth; diff --git a/package.json b/package.json index 2f324f77a..543af8a4b 100644 --- a/package.json +++ b/package.json @@ -161,6 +161,7 @@ "@types/object-assign": "^4.0.30", "@types/prop-types": "^15.7.5", "@types/punycode": "^2.1.0", + "@types/rails__ujs": "^6.0.4", "@types/react": "^18.2.7", "@types/react-dom": "^18.2.4", "@types/react-helmet": "^6.1.6", diff --git a/yarn.lock b/yarn.lock index 0dd9a627c..11c2724d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2317,6 +2317,7 @@ __metadata: "@types/object-assign": "npm:^4.0.30" "@types/prop-types": "npm:^15.7.5" "@types/punycode": "npm:^2.1.0" + "@types/rails__ujs": "npm:^6.0.4" "@types/react": "npm:^18.2.7" "@types/react-dom": "npm:^18.2.4" "@types/react-helmet": "npm:^6.1.6" @@ -3349,6 +3350,13 @@ __metadata: languageName: node linkType: hard +"@types/rails__ujs@npm:^6.0.4": + version: 6.0.4 + resolution: "@types/rails__ujs@npm:6.0.4" + checksum: 7477cb03a0e1339b9cd5c8ac4a197a153e2ff48742b2f527c5a39dcdf80f01493011e368483290d3717662c63066fada3ab203a335804cbb3573cf575f37007e + languageName: node + linkType: hard + "@types/range-parser@npm:*": version: 1.2.7 resolution: "@types/range-parser@npm:1.2.7"