Converted app/javascript/mastodon/utils/ folder to TypeScript (#27895)

main
Josh Goldberg ✨ 2023-11-28 18:47:55 +01:00 committed by GitHub
parent cdc7894243
commit 1142f4c79e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 84 additions and 75 deletions

View File

@ -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 || '';
}
});

View File

@ -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<HTMLMetaElement>(
'meta[name=cdn-host]',
);
if (cdnHost) {
assetHost = cdnHost.content || '';
}
});

View File

@ -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(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n').replace(/<[^>]*>/g, '');
return wrapper.textContent;
};

View File

@ -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(/<br\s*\/?>/g, '\n')
.replace(/<\/p><p>/g, '\n\n')
.replace(/<[^>]*>/g, '');
return wrapper.textContent;
};

View File

@ -1,13 +1,23 @@
// Copied from emoji-mart for consistency with emoji picker and since // Copied from emoji-mart for consistency with emoji picker and since
// they don't export the icons in the package // they don't export the icons in the package
export const loupeIcon = ( export const loupeIcon = (
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' width='13' height='13'> <svg
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 20 20'
width='13'
height='13'
>
<path d='M12.9 14.32a8 8 0 1 1 1.41-1.41l5.35 5.33-1.42 1.42-5.33-5.34zM8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12z' /> <path d='M12.9 14.32a8 8 0 1 1 1.41-1.41l5.35 5.33-1.42 1.42-5.33-5.34zM8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12z' />
</svg> </svg>
); );
export const deleteIcon = ( export const deleteIcon = (
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' width='13' height='13'> <svg
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 20 20'
width='13'
height='13'
>
<path d='M10 8.586L2.929 1.515 1.515 2.929 8.586 10l-7.071 7.071 1.414 1.414L10 11.414l7.071 7.071 1.414-1.414L11.414 10l7.071-7.071-1.414-1.414L10 8.586z' /> <path d='M10 8.586L2.929 1.515 1.515 2.929 8.586 10l-7.071 7.071 1.414 1.414L10 11.414l7.071 7.071 1.414-1.414L11.414 10l7.071-7.071-1.414-1.414L10 8.586z' />
</svg> </svg>
); );

View File

@ -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));
}
};

View File

@ -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);
}
};

View File

@ -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 = { export const WithRouterPropTypes = {
match: PropTypes.object.isRequired, match: PropTypes.object.isRequired,
@ -16,31 +16,37 @@ export const WithOptionalRouterPropTypes = {
history: PropTypes.object, 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 // 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 // but does not fail if called outside of a React Router context
export function withOptionalRouter(Component) { export function withOptionalRouter<
const displayName = `withRouter(${Component.displayName || Component.name})`; ComponentType extends React.ComponentType<OptionalRouterProps>,
const C = props => { >(Component: ComponentType) {
const displayName = `withRouter(${Component.displayName ?? Component.name})`;
const C = (props: React.ComponentProps<ComponentType>) => {
const { wrappedComponentRef, ...remainingProps } = props; const { wrappedComponentRef, ...remainingProps } = props;
return ( return (
<__RouterContext.Consumer> <__RouterContext.Consumer>
{context => { {(context) => {
if(context) // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (context) {
return ( return (
// @ts-expect-error - Dynamic covariant generic components are tough to type.
<Component <Component
{...remainingProps} {...remainingProps}
{...context} {...context}
ref={wrappedComponentRef} ref={wrappedComponentRef}
/> />
); );
else } else {
return ( // @ts-expect-error - Dynamic covariant generic components are tough to type.
<Component return <Component {...remainingProps} ref={wrappedComponentRef} />;
{...remainingProps} }
ref={wrappedComponentRef}
/>
);
}} }}
</__RouterContext.Consumer> </__RouterContext.Consumer>
); );
@ -53,8 +59,8 @@ export function withOptionalRouter(Component) {
wrappedComponentRef: PropTypes.oneOfType([ wrappedComponentRef: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.func, PropTypes.func,
PropTypes.object PropTypes.object,
]) ]),
}; };
return hoistStatics(C, Component); return hoistStatics(C, Component);

View File

@ -1,11 +1,7 @@
import { isMobile } from '../is_mobile'; import { isMobile } from '../is_mobile';
/** @type {number | null} */ let cachedScrollbarWidth: number | null = null;
let cachedScrollbarWidth = null;
/**
* @returns {number}
*/
const getActualScrollbarWidth = () => { const getActualScrollbarWidth = () => {
const outer = document.createElement('div'); const outer = document.createElement('div');
outer.style.visibility = 'hidden'; outer.style.visibility = 'hidden';
@ -16,20 +12,19 @@ const getActualScrollbarWidth = () => {
outer.appendChild(inner); outer.appendChild(inner);
const scrollbarWidth = outer.offsetWidth - inner.offsetWidth; const scrollbarWidth = outer.offsetWidth - inner.offsetWidth;
outer.parentNode.removeChild(outer); outer.remove();
return scrollbarWidth; return scrollbarWidth;
}; };
/**
* @returns {number}
*/
export const getScrollbarWidth = () => { export const getScrollbarWidth = () => {
if (cachedScrollbarWidth !== null) { if (cachedScrollbarWidth !== null) {
return cachedScrollbarWidth; return cachedScrollbarWidth;
} }
const scrollbarWidth = isMobile(window.innerWidth) ? 0 : getActualScrollbarWidth(); const scrollbarWidth = isMobile(window.innerWidth)
? 0
: getActualScrollbarWidth();
cachedScrollbarWidth = scrollbarWidth; cachedScrollbarWidth = scrollbarWidth;
return scrollbarWidth; return scrollbarWidth;

View File

@ -161,6 +161,7 @@
"@types/object-assign": "^4.0.30", "@types/object-assign": "^4.0.30",
"@types/prop-types": "^15.7.5", "@types/prop-types": "^15.7.5",
"@types/punycode": "^2.1.0", "@types/punycode": "^2.1.0",
"@types/rails__ujs": "^6.0.4",
"@types/react": "^18.2.7", "@types/react": "^18.2.7",
"@types/react-dom": "^18.2.4", "@types/react-dom": "^18.2.4",
"@types/react-helmet": "^6.1.6", "@types/react-helmet": "^6.1.6",

View File

@ -2317,6 +2317,7 @@ __metadata:
"@types/object-assign": "npm:^4.0.30" "@types/object-assign": "npm:^4.0.30"
"@types/prop-types": "npm:^15.7.5" "@types/prop-types": "npm:^15.7.5"
"@types/punycode": "npm:^2.1.0" "@types/punycode": "npm:^2.1.0"
"@types/rails__ujs": "npm:^6.0.4"
"@types/react": "npm:^18.2.7" "@types/react": "npm:^18.2.7"
"@types/react-dom": "npm:^18.2.4" "@types/react-dom": "npm:^18.2.4"
"@types/react-helmet": "npm:^6.1.6" "@types/react-helmet": "npm:^6.1.6"
@ -3349,6 +3350,13 @@ __metadata:
languageName: node languageName: node
linkType: hard 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:*": "@types/range-parser@npm:*":
version: 1.2.7 version: 1.2.7
resolution: "@types/range-parser@npm:1.2.7" resolution: "@types/range-parser@npm:1.2.7"