mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-18 02:28:08 +00:00
Merge commit '0af2c4829ff86cf84ed9e7804d023f31e805542b' into glitch-soc/merge-upstream
Conflicts: - `vite.config.mts`: Conflict because of differently-named theme plugins. Updated to go with upstream's changes.
This commit is contained in:
1
.github/renovate.json5
vendored
1
.github/renovate.json5
vendored
@@ -23,7 +23,6 @@
|
||||
matchManagers: ['npm'],
|
||||
matchPackageNames: [
|
||||
'tesseract.js', // Requires code changes
|
||||
'react-hotkeys', // Requires code changes
|
||||
|
||||
// react-router: Requires manual upgrade
|
||||
'history',
|
||||
|
||||
@@ -438,7 +438,7 @@ GEM
|
||||
mime-types (3.7.0)
|
||||
logger
|
||||
mime-types-data (~> 3.2025, >= 3.2025.0507)
|
||||
mime-types-data (3.2025.0514)
|
||||
mime-types-data (3.2025.0715)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.9)
|
||||
minitest (5.25.5)
|
||||
@@ -610,7 +610,7 @@ GEM
|
||||
pg (1.5.9)
|
||||
pghero (3.7.0)
|
||||
activerecord (>= 7.1)
|
||||
playwright-ruby-client (1.52.0)
|
||||
playwright-ruby-client (1.54.0)
|
||||
concurrent-ruby (>= 1.1.6)
|
||||
mime-types (>= 3.0)
|
||||
pp (0.6.2)
|
||||
@@ -722,7 +722,7 @@ GEM
|
||||
redlock (1.3.2)
|
||||
redis (>= 3.0.0, < 6.0)
|
||||
regexp_parser (2.10.0)
|
||||
reline (0.6.1)
|
||||
reline (0.6.2)
|
||||
io-console (~> 0.5)
|
||||
request_store (1.7.0)
|
||||
rack (>= 1.4)
|
||||
|
||||
171
app/javascript/mastodon/components/hotkeys/hotkeys.stories.tsx
Normal file
171
app/javascript/mastodon/components/hotkeys/hotkeys.stories.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import { expect } from 'storybook/test';
|
||||
|
||||
import type { HandlerMap } from '.';
|
||||
import { Hotkeys } from '.';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Hotkeys',
|
||||
component: Hotkeys,
|
||||
args: {
|
||||
global: undefined,
|
||||
focusable: undefined,
|
||||
handlers: {},
|
||||
},
|
||||
tags: ['test'],
|
||||
} satisfies Meta<typeof Hotkeys>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
const hotkeyTest: Story['play'] = async ({ canvas, userEvent }) => {
|
||||
async function confirmHotkey(name: string, shouldFind = true) {
|
||||
// 'status' is the role of the 'output' element
|
||||
const output = await canvas.findByRole('status');
|
||||
if (shouldFind) {
|
||||
await expect(output).toHaveTextContent(name);
|
||||
} else {
|
||||
await expect(output).not.toHaveTextContent(name);
|
||||
}
|
||||
}
|
||||
|
||||
const button = await canvas.findByRole('button');
|
||||
await userEvent.click(button);
|
||||
|
||||
await userEvent.keyboard('n');
|
||||
await confirmHotkey('new');
|
||||
|
||||
await userEvent.keyboard('/');
|
||||
await confirmHotkey('search');
|
||||
|
||||
await userEvent.keyboard('o');
|
||||
await confirmHotkey('open');
|
||||
|
||||
await userEvent.keyboard('{Alt>}N{/Alt}');
|
||||
await confirmHotkey('forceNew');
|
||||
|
||||
await userEvent.keyboard('gh');
|
||||
await confirmHotkey('goToHome');
|
||||
|
||||
await userEvent.keyboard('gn');
|
||||
await confirmHotkey('goToNotifications');
|
||||
|
||||
await userEvent.keyboard('gf');
|
||||
await confirmHotkey('goToFavourites');
|
||||
|
||||
/**
|
||||
* Ensure that hotkeys are not triggered when certain
|
||||
* interactive elements are focused:
|
||||
*/
|
||||
|
||||
await userEvent.keyboard('{enter}');
|
||||
await confirmHotkey('open', false);
|
||||
|
||||
const input = await canvas.findByRole('textbox');
|
||||
await userEvent.click(input);
|
||||
|
||||
await userEvent.keyboard('n');
|
||||
await confirmHotkey('new', false);
|
||||
|
||||
await userEvent.keyboard('{backspace}');
|
||||
await confirmHotkey('None', false);
|
||||
|
||||
/**
|
||||
* Reset playground:
|
||||
*/
|
||||
|
||||
await userEvent.click(button);
|
||||
await userEvent.keyboard('{backspace}');
|
||||
};
|
||||
|
||||
export const Default = {
|
||||
render: function Render() {
|
||||
const [matchedHotkey, setMatchedHotkey] = useState<keyof HandlerMap | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const handlers = {
|
||||
back: () => {
|
||||
setMatchedHotkey(null);
|
||||
},
|
||||
new: () => {
|
||||
setMatchedHotkey('new');
|
||||
},
|
||||
forceNew: () => {
|
||||
setMatchedHotkey('forceNew');
|
||||
},
|
||||
search: () => {
|
||||
setMatchedHotkey('search');
|
||||
},
|
||||
open: () => {
|
||||
setMatchedHotkey('open');
|
||||
},
|
||||
goToHome: () => {
|
||||
setMatchedHotkey('goToHome');
|
||||
},
|
||||
goToNotifications: () => {
|
||||
setMatchedHotkey('goToNotifications');
|
||||
},
|
||||
goToFavourites: () => {
|
||||
setMatchedHotkey('goToFavourites');
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<Hotkeys handlers={handlers}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 8,
|
||||
padding: '1em',
|
||||
border: '1px dashed #ccc',
|
||||
fontSize: 14,
|
||||
color: '#222',
|
||||
}}
|
||||
>
|
||||
<h1
|
||||
style={{
|
||||
fontSize: 22,
|
||||
marginBottom: '0.3em',
|
||||
}}
|
||||
>
|
||||
Hotkey playground
|
||||
</h1>
|
||||
<p>
|
||||
Last pressed hotkey: <output>{matchedHotkey ?? 'None'}</output>
|
||||
</p>
|
||||
<p>
|
||||
Click within the dashed border and press the "<kbd>n</kbd>
|
||||
" or "<kbd>/</kbd>" key. Press "
|
||||
<kbd>Backspace</kbd>" to clear the displayed hotkey.
|
||||
</p>
|
||||
<p>
|
||||
Try typing a sequence, like "<kbd>g</kbd>" shortly
|
||||
followed by "<kbd>h</kbd>", "<kbd>n</kbd>", or
|
||||
"<kbd>f</kbd>"
|
||||
</p>
|
||||
<p>
|
||||
Note that this playground doesn't support all hotkeys we use in
|
||||
the app.
|
||||
</p>
|
||||
<p>
|
||||
When a <button>Button</button> is focused, "
|
||||
<kbd>Enter</kbd>
|
||||
" should not trigger "open", but "<kbd>o</kbd>
|
||||
" should.
|
||||
</p>
|
||||
<p>
|
||||
When an input element is focused, hotkeys should not interfere with
|
||||
regular typing:
|
||||
</p>
|
||||
<input type='text' />
|
||||
</div>
|
||||
</Hotkeys>
|
||||
);
|
||||
},
|
||||
play: hotkeyTest,
|
||||
};
|
||||
282
app/javascript/mastodon/components/hotkeys/index.tsx
Normal file
282
app/javascript/mastodon/components/hotkeys/index.tsx
Normal file
@@ -0,0 +1,282 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { normalizeKey, isKeyboardEvent } from './utils';
|
||||
|
||||
/**
|
||||
* In case of multiple hotkeys matching the pressed key(s),
|
||||
* the hotkey with a higher priority is selected. All others
|
||||
* are ignored.
|
||||
*/
|
||||
const hotkeyPriority = {
|
||||
singleKey: 0,
|
||||
combo: 1,
|
||||
sequence: 2,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* This type of function receives a keyboard event and an array of
|
||||
* previously pressed keys (within the last second), and returns
|
||||
* `isMatch` (whether the pressed keys match a hotkey) and `priority`
|
||||
* (a weighting used to resolve conflicts when two hotkeys match the
|
||||
* pressed keys)
|
||||
*/
|
||||
type KeyMatcher = (
|
||||
event: KeyboardEvent,
|
||||
bufferedKeys?: string[],
|
||||
) => {
|
||||
/**
|
||||
* Whether the event.key matches the hotkey
|
||||
*/
|
||||
isMatch: boolean;
|
||||
/**
|
||||
* If there are multiple matching hotkeys, the
|
||||
* first one with the highest priority will be handled
|
||||
*/
|
||||
priority: (typeof hotkeyPriority)[keyof typeof hotkeyPriority];
|
||||
};
|
||||
|
||||
/**
|
||||
* Matches a single key
|
||||
*/
|
||||
function just(keyName: string): KeyMatcher {
|
||||
return (event) => ({
|
||||
isMatch: normalizeKey(event.key) === keyName,
|
||||
priority: hotkeyPriority.singleKey,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches any single key out of those provided
|
||||
*/
|
||||
function any(...keys: string[]): KeyMatcher {
|
||||
return (event) => ({
|
||||
isMatch: keys.some((keyName) => just(keyName)(event).isMatch),
|
||||
priority: hotkeyPriority.singleKey,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches a single key combined with the option/alt modifier
|
||||
*/
|
||||
function optionPlus(key: string): KeyMatcher {
|
||||
return (event) => ({
|
||||
// Matching against event.code here as alt combos are often
|
||||
// mapped to other characters
|
||||
isMatch: event.altKey && event.code === `Key${key.toUpperCase()}`,
|
||||
priority: hotkeyPriority.combo,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches when all provided keys are pressed in sequence.
|
||||
*/
|
||||
function sequence(...sequence: string[]): KeyMatcher {
|
||||
return (event, bufferedKeys) => {
|
||||
const lastKeyInSequence = sequence.at(-1);
|
||||
const startOfSequence = sequence.slice(0, -1);
|
||||
const relevantBufferedKeys = bufferedKeys?.slice(-startOfSequence.length);
|
||||
|
||||
const bufferMatchesStartOfSequence =
|
||||
!!relevantBufferedKeys &&
|
||||
startOfSequence.join('') === relevantBufferedKeys.join('');
|
||||
|
||||
return {
|
||||
isMatch:
|
||||
bufferMatchesStartOfSequence &&
|
||||
normalizeKey(event.key) === lastKeyInSequence,
|
||||
priority: hotkeyPriority.sequence,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a map of all global hotkeys we support.
|
||||
* To trigger a hotkey, a handler with a matching name must be
|
||||
* provided to the `useHotkeys` hook or `Hotkeys` component.
|
||||
*/
|
||||
const hotkeyMatcherMap = {
|
||||
help: just('?'),
|
||||
search: any('s', '/'),
|
||||
back: just('backspace'),
|
||||
new: just('n'),
|
||||
forceNew: optionPlus('n'),
|
||||
focusColumn: any('1', '2', '3', '4', '5', '6', '7', '8', '9'),
|
||||
reply: just('r'),
|
||||
favourite: just('f'),
|
||||
boost: just('b'),
|
||||
mention: just('m'),
|
||||
open: any('enter', 'o'),
|
||||
openProfile: just('p'),
|
||||
moveDown: any('down', 'j'),
|
||||
moveUp: any('up', 'k'),
|
||||
toggleHidden: just('x'),
|
||||
toggleSensitive: just('h'),
|
||||
toggleComposeSpoilers: optionPlus('x'),
|
||||
openMedia: just('e'),
|
||||
onTranslate: just('t'),
|
||||
goToHome: sequence('g', 'h'),
|
||||
goToNotifications: sequence('g', 'n'),
|
||||
goToLocal: sequence('g', 'l'),
|
||||
goToFederated: sequence('g', 't'),
|
||||
goToDirect: sequence('g', 'd'),
|
||||
goToStart: sequence('g', 's'),
|
||||
goToFavourites: sequence('g', 'f'),
|
||||
goToPinned: sequence('g', 'p'),
|
||||
goToProfile: sequence('g', 'u'),
|
||||
goToBlocked: sequence('g', 'b'),
|
||||
goToMuted: sequence('g', 'm'),
|
||||
goToRequests: sequence('g', 'r'),
|
||||
cheat: sequence(
|
||||
'up',
|
||||
'up',
|
||||
'down',
|
||||
'down',
|
||||
'left',
|
||||
'right',
|
||||
'left',
|
||||
'right',
|
||||
'b',
|
||||
'a',
|
||||
'enter',
|
||||
),
|
||||
} as const;
|
||||
|
||||
type HotkeyName = keyof typeof hotkeyMatcherMap;
|
||||
|
||||
export type HandlerMap = Partial<
|
||||
Record<HotkeyName, (event: KeyboardEvent) => void>
|
||||
>;
|
||||
|
||||
export function useHotkeys<T extends HTMLElement>(handlers: HandlerMap) {
|
||||
const ref = useRef<T>(null);
|
||||
const bufferedKeys = useRef<string[]>([]);
|
||||
const sequenceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
/**
|
||||
* Store the latest handlers object in a ref so we don't need to
|
||||
* add it as a dependency to the main event listener effect
|
||||
*/
|
||||
const handlersRef = useRef(handlers);
|
||||
useEffect(() => {
|
||||
handlersRef.current = handlers;
|
||||
}, [handlers]);
|
||||
|
||||
useEffect(() => {
|
||||
const element = ref.current ?? document;
|
||||
|
||||
function listener(event: Event) {
|
||||
// Ignore key presses from input, textarea, or select elements
|
||||
const tagName = (event.target as HTMLElement).tagName.toLowerCase();
|
||||
const shouldHandleEvent =
|
||||
isKeyboardEvent(event) &&
|
||||
!event.defaultPrevented &&
|
||||
!['input', 'textarea', 'select'].includes(tagName) &&
|
||||
!(
|
||||
['a', 'button'].includes(tagName) &&
|
||||
normalizeKey(event.key) === 'enter'
|
||||
);
|
||||
|
||||
if (shouldHandleEvent) {
|
||||
const matchCandidates: {
|
||||
handler: (event: KeyboardEvent) => void;
|
||||
priority: number;
|
||||
}[] = [];
|
||||
|
||||
(Object.keys(hotkeyMatcherMap) as HotkeyName[]).forEach(
|
||||
(handlerName) => {
|
||||
const handler = handlersRef.current[handlerName];
|
||||
|
||||
if (handler) {
|
||||
const hotkeyMatcher = hotkeyMatcherMap[handlerName];
|
||||
|
||||
const { isMatch, priority } = hotkeyMatcher(
|
||||
event,
|
||||
bufferedKeys.current,
|
||||
);
|
||||
|
||||
if (isMatch) {
|
||||
matchCandidates.push({ handler, priority });
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Sort all matches by priority
|
||||
matchCandidates.sort((a, b) => b.priority - a.priority);
|
||||
|
||||
const bestMatchingHandler = matchCandidates.at(0)?.handler;
|
||||
if (bestMatchingHandler) {
|
||||
bestMatchingHandler(event);
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// Add last keypress to buffer
|
||||
bufferedKeys.current.push(normalizeKey(event.key));
|
||||
|
||||
// Reset the timeout
|
||||
if (sequenceTimer.current) {
|
||||
clearTimeout(sequenceTimer.current);
|
||||
}
|
||||
sequenceTimer.current = setTimeout(() => {
|
||||
bufferedKeys.current = [];
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
element.addEventListener('keydown', listener);
|
||||
|
||||
return () => {
|
||||
element.removeEventListener('keydown', listener);
|
||||
if (sequenceTimer.current) {
|
||||
clearTimeout(sequenceTimer.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Hotkeys component allows us to globally register keyboard combinations
|
||||
* under a name and assign actions to them, either globally or scoped to a portion
|
||||
* of the app.
|
||||
*
|
||||
* ### How to use
|
||||
*
|
||||
* To add a new hotkey, add its key combination to the `hotkeyMatcherMap` object
|
||||
* and give it a name.
|
||||
*
|
||||
* Use the `<Hotkeys>` component or the `useHotkeys` hook in the part of of the app
|
||||
* where you want to handle the action, and pass in a handlers object.
|
||||
*
|
||||
* ```tsx
|
||||
* <Hotkeys handlers={{open: openStatus}} />
|
||||
* ```
|
||||
*
|
||||
* Now this function will be called when the 'open' hotkey is pressed by the user.
|
||||
*/
|
||||
export const Hotkeys: React.FC<{
|
||||
/**
|
||||
* An object containing functions to be run when a hotkey is pressed.
|
||||
* The key must be the name of a registered hotkey, e.g. "help" or "search"
|
||||
*/
|
||||
handlers: HandlerMap;
|
||||
/**
|
||||
* When enabled, hotkeys will be matched against the document root
|
||||
* rather than only inside of this component's DOM node.
|
||||
*/
|
||||
global?: boolean;
|
||||
/**
|
||||
* Allow the rendered `div` to be focused
|
||||
*/
|
||||
focusable?: boolean;
|
||||
children: React.ReactNode;
|
||||
}> = ({ handlers, global, focusable = true, children }) => {
|
||||
const ref = useHotkeys<HTMLDivElement>(handlers);
|
||||
|
||||
return (
|
||||
<div ref={global ? undefined : ref} tabIndex={focusable ? -1 : undefined}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
29
app/javascript/mastodon/components/hotkeys/utils.ts
Normal file
29
app/javascript/mastodon/components/hotkeys/utils.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export function isKeyboardEvent(event: Event): event is KeyboardEvent {
|
||||
return 'key' in event;
|
||||
}
|
||||
|
||||
export function normalizeKey(key: string): string {
|
||||
const lowerKey = key.toLowerCase();
|
||||
|
||||
switch (lowerKey) {
|
||||
case ' ':
|
||||
case 'spacebar': // for older browsers
|
||||
return 'space';
|
||||
|
||||
case 'arrowup':
|
||||
return 'up';
|
||||
case 'arrowdown':
|
||||
return 'down';
|
||||
case 'arrowleft':
|
||||
return 'left';
|
||||
case 'arrowright':
|
||||
return 'right';
|
||||
|
||||
case 'esc':
|
||||
case 'escape':
|
||||
return 'escape';
|
||||
|
||||
default:
|
||||
return lowerKey;
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,9 @@ import { Link } from 'react-router-dom';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import { Hotkeys } from 'mastodon/components/hotkeys';
|
||||
import { ContentWarning } from 'mastodon/components/content_warning';
|
||||
import { FilterWarning } from 'mastodon/components/filter_warning';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
@@ -35,7 +34,6 @@ import StatusActionBar from './status_action_bar';
|
||||
import StatusContent from './status_content';
|
||||
import { StatusThreadLabel } from './status_thread_label';
|
||||
import { VisibilityIcon } from './visibility_icon';
|
||||
|
||||
const domParser = new DOMParser();
|
||||
|
||||
export const textForScreenReader = (intl, status, rebloggedByText = false) => {
|
||||
@@ -325,11 +323,11 @@ class Status extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
handleHotkeyMoveUp = e => {
|
||||
this.props.onMoveUp(this.props.status.get('id'), e.target.getAttribute('data-featured'));
|
||||
this.props.onMoveUp?.(this.props.status.get('id'), this.node.getAttribute('data-featured'));
|
||||
};
|
||||
|
||||
handleHotkeyMoveDown = e => {
|
||||
this.props.onMoveDown(this.props.status.get('id'), e.target.getAttribute('data-featured'));
|
||||
this.props.onMoveDown?.(this.props.status.get('id'), this.node.getAttribute('data-featured'));
|
||||
};
|
||||
|
||||
handleHotkeyToggleHidden = () => {
|
||||
@@ -437,13 +435,13 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
if (hidden) {
|
||||
return (
|
||||
<HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
|
||||
<Hotkeys handlers={handlers} focusable={!unfocusable}>
|
||||
<div ref={this.handleRef} className={classNames('status__wrapper', { focusable: !this.props.muted })} tabIndex={unfocusable ? null : 0}>
|
||||
<span>{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}</span>
|
||||
{status.get('spoiler_text').length > 0 && (<span>{status.get('spoiler_text')}</span>)}
|
||||
{expanded && <span>{status.get('content')}</span>}
|
||||
</div>
|
||||
</HotKeys>
|
||||
</Hotkeys>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -543,7 +541,7 @@ class Status extends ImmutablePureComponent {
|
||||
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
|
||||
|
||||
return (
|
||||
<HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
|
||||
<Hotkeys handlers={handlers} focusable={!unfocusable}>
|
||||
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted || unfocusable ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef} data-nosnippet={status.getIn(['account', 'noindex'], true) || undefined}>
|
||||
{!skipPrepend && prepend}
|
||||
|
||||
@@ -604,7 +602,7 @@ class Status extends ImmutablePureComponent {
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</HotKeys>
|
||||
</Hotkeys>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ export default class StatusList extends ImmutablePureComponent {
|
||||
|
||||
_selectChild (index, align_top) {
|
||||
const container = this.node.node;
|
||||
// TODO: This breaks at the inline-follow-suggestions container
|
||||
const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
|
||||
|
||||
if (element) {
|
||||
|
||||
@@ -92,10 +92,29 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
this.props.onChange(e.target.value);
|
||||
};
|
||||
|
||||
handleKeyDown = (e) => {
|
||||
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
|
||||
blurOnEscape = (e) => {
|
||||
if (['esc', 'escape'].includes(e.key.toLowerCase())) {
|
||||
e.target.blur();
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDownPost = (e) => {
|
||||
if (e.key.toLowerCase() === 'enter' && (e.ctrlKey || e.metaKey)) {
|
||||
this.handleSubmit();
|
||||
}
|
||||
this.blurOnEscape(e);
|
||||
};
|
||||
|
||||
handleKeyDownSpoiler = (e) => {
|
||||
if (e.key.toLowerCase() === 'enter') {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
this.handleSubmit();
|
||||
} else {
|
||||
e.preventDefault();
|
||||
this.textareaRef.current?.focus();
|
||||
}
|
||||
}
|
||||
this.blurOnEscape(e);
|
||||
};
|
||||
|
||||
getFulltextForCharacterCounting = () => {
|
||||
@@ -248,7 +267,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
value={this.props.spoilerText}
|
||||
disabled={isSubmitting}
|
||||
onChange={this.handleChangeSpoilerText}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onKeyDown={this.handleKeyDownSpoiler}
|
||||
ref={this.setSpoilerText}
|
||||
suggestions={this.props.suggestions}
|
||||
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
||||
@@ -273,7 +292,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
onChange={this.handleChange}
|
||||
suggestions={this.props.suggestions}
|
||||
onFocus={this.handleFocus}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onKeyDown={this.handleKeyDownPost}
|
||||
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||
onSuggestionSelected={this.onSuggestionSelected}
|
||||
|
||||
@@ -10,15 +10,13 @@ import { createSelector } from '@reduxjs/toolkit';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
import { replyCompose } from 'mastodon/actions/compose';
|
||||
import { markConversationRead, deleteConversation } from 'mastodon/actions/conversations';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { muteStatus, unmuteStatus, toggleStatusSpoilers } from 'mastodon/actions/statuses';
|
||||
import { Hotkeys } from 'mastodon/components/hotkeys';
|
||||
import AttachmentList from 'mastodon/components/attachment_list';
|
||||
import AvatarComposite from 'mastodon/components/avatar_composite';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
@@ -169,7 +167,7 @@ export const Conversation = ({ conversation, scrollKey, onMoveUp, onMoveDown })
|
||||
};
|
||||
|
||||
return (
|
||||
<HotKeys handlers={handlers}>
|
||||
<Hotkeys handlers={handlers}>
|
||||
<div className={classNames('conversation focusable muted', { unread })} tabIndex={0}>
|
||||
<div className='conversation__avatar' onClick={handleClick} role='presentation'>
|
||||
<AvatarComposite accounts={accounts} size={48} />
|
||||
@@ -219,7 +217,7 @@ export const Conversation = ({ conversation, scrollKey, onMoveUp, onMoveDown })
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</HotKeys>
|
||||
</Hotkeys>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import { Link, withRouter } from 'react-router-dom';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
|
||||
import FlagIcon from '@/material-icons/400-24px/flag-fill.svg?react';
|
||||
@@ -20,6 +19,7 @@ import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { Hotkeys } from 'mastodon/components/hotkeys';
|
||||
import { StatusQuoteManager } from 'mastodon/components/status_quoted';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
@@ -137,7 +137,7 @@ class Notification extends ImmutablePureComponent {
|
||||
const { intl, unread } = this.props;
|
||||
|
||||
return (
|
||||
<HotKeys handlers={this.getHandlers()}>
|
||||
<Hotkeys handlers={this.getHandlers()}>
|
||||
<div className={classNames('notification notification-follow focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.follow, { name: account.get('acct') }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<Icon id='user-plus' icon={PersonAddIcon} />
|
||||
@@ -149,7 +149,7 @@ class Notification extends ImmutablePureComponent {
|
||||
|
||||
<Account id={account.get('id')} hidden={this.props.hidden} />
|
||||
</div>
|
||||
</HotKeys>
|
||||
</Hotkeys>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ class Notification extends ImmutablePureComponent {
|
||||
const { intl, unread } = this.props;
|
||||
|
||||
return (
|
||||
<HotKeys handlers={this.getHandlers()}>
|
||||
<Hotkeys handlers={this.getHandlers()}>
|
||||
<div className={classNames('notification notification-follow-request focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.follow_request', defaultMessage: '{name} has requested to follow you' }, { name: account.get('acct') }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<Icon id='user' icon={PersonIcon} />
|
||||
@@ -169,7 +169,7 @@ class Notification extends ImmutablePureComponent {
|
||||
|
||||
<FollowRequestContainer id={account.get('id')} hidden={this.props.hidden} />
|
||||
</div>
|
||||
</HotKeys>
|
||||
</Hotkeys>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ class Notification extends ImmutablePureComponent {
|
||||
const { intl, unread } = this.props;
|
||||
|
||||
return (
|
||||
<HotKeys handlers={this.getHandlers()}>
|
||||
<Hotkeys handlers={this.getHandlers()}>
|
||||
<div className={classNames('notification notification-favourite focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.favourite, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<Icon id='star' icon={StarIcon} className='star-icon' />
|
||||
@@ -217,7 +217,7 @@ class Notification extends ImmutablePureComponent {
|
||||
cacheMediaWidth={this.props.cacheMediaWidth}
|
||||
/>
|
||||
</div>
|
||||
</HotKeys>
|
||||
</Hotkeys>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ class Notification extends ImmutablePureComponent {
|
||||
const { intl, unread } = this.props;
|
||||
|
||||
return (
|
||||
<HotKeys handlers={this.getHandlers()}>
|
||||
<Hotkeys handlers={this.getHandlers()}>
|
||||
<div className={classNames('notification notification-reblog focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.reblog, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<Icon id='retweet' icon={RepeatIcon} />
|
||||
@@ -247,7 +247,7 @@ class Notification extends ImmutablePureComponent {
|
||||
cacheMediaWidth={this.props.cacheMediaWidth}
|
||||
/>
|
||||
</div>
|
||||
</HotKeys>
|
||||
</Hotkeys>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ class Notification extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<HotKeys handlers={this.getHandlers()}>
|
||||
<Hotkeys handlers={this.getHandlers()}>
|
||||
<div className={classNames('notification notification-status focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.status, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<Icon id='home' icon={HomeIcon} />
|
||||
@@ -282,7 +282,7 @@ class Notification extends ImmutablePureComponent {
|
||||
cacheMediaWidth={this.props.cacheMediaWidth}
|
||||
/>
|
||||
</div>
|
||||
</HotKeys>
|
||||
</Hotkeys>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ class Notification extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<HotKeys handlers={this.getHandlers()}>
|
||||
<Hotkeys handlers={this.getHandlers()}>
|
||||
<div className={classNames('notification notification-update focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.update, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<Icon id='pencil' icon={EditIcon} />
|
||||
@@ -317,7 +317,7 @@ class Notification extends ImmutablePureComponent {
|
||||
cacheMediaWidth={this.props.cacheMediaWidth}
|
||||
/>
|
||||
</div>
|
||||
</HotKeys>
|
||||
</Hotkeys>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -331,7 +331,7 @@ class Notification extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<HotKeys handlers={this.getHandlers()}>
|
||||
<Hotkeys handlers={this.getHandlers()}>
|
||||
<div className={classNames('notification notification-poll focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, message, notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<Icon id='tasks' icon={InsertChartIcon} />
|
||||
@@ -358,7 +358,7 @@ class Notification extends ImmutablePureComponent {
|
||||
cacheMediaWidth={this.props.cacheMediaWidth}
|
||||
/>
|
||||
</div>
|
||||
</HotKeys>
|
||||
</Hotkeys>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -371,7 +371,7 @@ class Notification extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<HotKeys handlers={this.getHandlers()}>
|
||||
<Hotkeys handlers={this.getHandlers()}>
|
||||
<div className={classNames('notification notification-severed-relationships focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.relationshipsSevered, { name: notification.getIn(['event', 'target_name']) }), notification.get('created_at'))}>
|
||||
<RelationshipsSeveranceEvent
|
||||
type={event.get('type')}
|
||||
@@ -381,7 +381,7 @@ class Notification extends ImmutablePureComponent {
|
||||
hidden={hidden}
|
||||
/>
|
||||
</div>
|
||||
</HotKeys>
|
||||
</Hotkeys>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -394,7 +394,7 @@ class Notification extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<HotKeys handlers={this.getHandlers()}>
|
||||
<Hotkeys handlers={this.getHandlers()}>
|
||||
<div className={classNames('notification notification-moderation-warning focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.moderationWarning), notification.get('created_at'))}>
|
||||
<ModerationWarning
|
||||
action={warning.get('action')}
|
||||
@@ -402,7 +402,7 @@ class Notification extends ImmutablePureComponent {
|
||||
hidden={hidden}
|
||||
/>
|
||||
</div>
|
||||
</HotKeys>
|
||||
</Hotkeys>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -410,7 +410,7 @@ class Notification extends ImmutablePureComponent {
|
||||
const { intl, unread } = this.props;
|
||||
|
||||
return (
|
||||
<HotKeys handlers={this.getHandlers()}>
|
||||
<Hotkeys handlers={this.getHandlers()}>
|
||||
<div className={classNames('notification notification-admin-sign-up focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.adminSignUp, { name: account.get('acct') }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<Icon id='user-plus' icon={PersonAddIcon} />
|
||||
@@ -422,7 +422,7 @@ class Notification extends ImmutablePureComponent {
|
||||
|
||||
<Account id={account.get('id')} hidden={this.props.hidden} />
|
||||
</div>
|
||||
</HotKeys>
|
||||
</Hotkeys>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -438,7 +438,7 @@ class Notification extends ImmutablePureComponent {
|
||||
const targetLink = <bdi><Link className='notification__display-name' title={targetAccount.get('acct')} data-hover-card-account={targetAccount.get('id')} to={`/@${targetAccount.get('acct')}`} dangerouslySetInnerHTML={targetDisplayNameHtml} /></bdi>;
|
||||
|
||||
return (
|
||||
<HotKeys handlers={this.getHandlers()}>
|
||||
<Hotkeys handlers={this.getHandlers()}>
|
||||
<div className={classNames('notification notification-admin-report focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.adminReport, { name: account.get('acct'), target: notification.getIn(['report', 'target_account', 'acct']) }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<Icon id='flag' icon={FlagIcon} />
|
||||
@@ -450,7 +450,7 @@ class Notification extends ImmutablePureComponent {
|
||||
|
||||
<Report account={account} report={notification.get('report')} hidden={this.props.hidden} />
|
||||
</div>
|
||||
</HotKeys>
|
||||
</Hotkeys>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
import { navigateToProfile } from 'mastodon/actions/accounts';
|
||||
import { mentionComposeById } from 'mastodon/actions/compose';
|
||||
import { Hotkeys } from 'mastodon/components/hotkeys';
|
||||
import type { NotificationGroup as NotificationGroupModel } from 'mastodon/models/notification_group';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
@@ -156,5 +155,5 @@ export const NotificationGroup: React.FC<{
|
||||
return null;
|
||||
}
|
||||
|
||||
return <HotKeys handlers={handlers}>{content}</HotKeys>;
|
||||
return <Hotkeys handlers={handlers}>{content}</Hotkeys>;
|
||||
};
|
||||
|
||||
@@ -3,12 +3,11 @@ import type { JSX } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
import { replyComposeById } from 'mastodon/actions/compose';
|
||||
import { navigateToStatus } from 'mastodon/actions/statuses';
|
||||
import { Avatar } from 'mastodon/components/avatar';
|
||||
import { AvatarGroup } from 'mastodon/components/avatar_group';
|
||||
import { Hotkeys } from 'mastodon/components/hotkeys';
|
||||
import type { IconProp } from 'mastodon/components/icon';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
||||
@@ -91,7 +90,7 @@ export const NotificationGroupWithStatus: React.FC<{
|
||||
);
|
||||
|
||||
return (
|
||||
<HotKeys handlers={handlers}>
|
||||
<Hotkeys handlers={handlers}>
|
||||
<div
|
||||
role='button'
|
||||
className={classNames(
|
||||
@@ -149,6 +148,6 @@ export const NotificationGroupWithStatus: React.FC<{
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</HotKeys>
|
||||
</Hotkeys>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,14 +2,13 @@ import { useMemo } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
import { replyComposeById } from 'mastodon/actions/compose';
|
||||
import { toggleReblog, toggleFavourite } from 'mastodon/actions/interactions';
|
||||
import {
|
||||
navigateToStatus,
|
||||
toggleStatusSpoilers,
|
||||
} from 'mastodon/actions/statuses';
|
||||
import { Hotkeys } from 'mastodon/components/hotkeys';
|
||||
import type { IconProp } from 'mastodon/components/icon';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { StatusQuoteManager } from 'mastodon/components/status_quoted';
|
||||
@@ -83,7 +82,7 @@ export const NotificationWithStatus: React.FC<{
|
||||
if (!statusId || isFiltered) return null;
|
||||
|
||||
return (
|
||||
<HotKeys handlers={handlers}>
|
||||
<Hotkeys handlers={handlers}>
|
||||
<div
|
||||
role='button'
|
||||
className={classNames(
|
||||
@@ -111,6 +110,6 @@ export const NotificationWithStatus: React.FC<{
|
||||
unfocusable
|
||||
/>
|
||||
</div>
|
||||
</HotKeys>
|
||||
</Hotkeys>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,10 +10,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
import VisibilityIcon from '@/material-icons/400-24px/visibility.svg?react';
|
||||
import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react';
|
||||
import { Hotkeys } from 'mastodon/components/hotkeys';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||
import { TimelineHint } from 'mastodon/components/timeline_hint';
|
||||
@@ -616,7 +615,7 @@ class Status extends ImmutablePureComponent {
|
||||
<div className={classNames('scrollable', { fullscreen })} ref={this.setContainerRef}>
|
||||
{ancestors}
|
||||
|
||||
<HotKeys handlers={handlers}>
|
||||
<Hotkeys handlers={handlers}>
|
||||
<div className={classNames('focusable', 'detailed-status__wrapper', `detailed-status__wrapper-${status.get('visibility')}`)} tabIndex={0} aria-label={textForScreenReader(intl, status, false)} ref={this.setStatusRef}>
|
||||
<DetailedStatus
|
||||
key={`details-${status.get('id')}`}
|
||||
@@ -654,7 +653,7 @@ class Status extends ImmutablePureComponent {
|
||||
onEmbed={this.handleEmbed}
|
||||
/>
|
||||
</div>
|
||||
</HotKeys>
|
||||
</Hotkeys>
|
||||
|
||||
{descendants}
|
||||
{remoteHint}
|
||||
|
||||
@@ -9,13 +9,13 @@ import { Redirect, Route, withRouter } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { debounce } from 'lodash';
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app';
|
||||
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers';
|
||||
import { fetchNotifications } from 'mastodon/actions/notification_groups';
|
||||
import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
|
||||
import { AlertsController } from 'mastodon/components/alerts_controller';
|
||||
import { Hotkeys } from 'mastodon/components/hotkeys';
|
||||
import { HoverCardController } from 'mastodon/components/hover_card_controller';
|
||||
import { PictureInPicture } from 'mastodon/features/picture_in_picture';
|
||||
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
||||
@@ -98,40 +98,6 @@ const mapStateToProps = state => ({
|
||||
username: state.getIn(['accounts', me, 'username']),
|
||||
});
|
||||
|
||||
const keyMap = {
|
||||
help: '?',
|
||||
new: 'n',
|
||||
search: ['s', '/'],
|
||||
forceNew: 'option+n',
|
||||
toggleComposeSpoilers: 'option+x',
|
||||
focusColumn: ['1', '2', '3', '4', '5', '6', '7', '8', '9'],
|
||||
reply: 'r',
|
||||
favourite: 'f',
|
||||
boost: 'b',
|
||||
mention: 'm',
|
||||
open: ['enter', 'o'],
|
||||
openProfile: 'p',
|
||||
moveDown: ['down', 'j'],
|
||||
moveUp: ['up', 'k'],
|
||||
back: 'backspace',
|
||||
goToHome: 'g h',
|
||||
goToNotifications: 'g n',
|
||||
goToLocal: 'g l',
|
||||
goToFederated: 'g t',
|
||||
goToDirect: 'g d',
|
||||
goToStart: 'g s',
|
||||
goToFavourites: 'g f',
|
||||
goToPinned: 'g p',
|
||||
goToProfile: 'g u',
|
||||
goToBlocked: 'g b',
|
||||
goToMuted: 'g m',
|
||||
goToRequests: 'g r',
|
||||
toggleHidden: 'x',
|
||||
toggleSensitive: 'h',
|
||||
openMedia: 'e',
|
||||
onTranslate: 't',
|
||||
};
|
||||
|
||||
class SwitchingColumnsArea extends PureComponent {
|
||||
static propTypes = {
|
||||
identity: identityContextPropShape,
|
||||
@@ -400,6 +366,10 @@ class UI extends PureComponent {
|
||||
}
|
||||
};
|
||||
|
||||
handleDonate = () => {
|
||||
location.href = 'https://joinmastodon.org/sponsors#donate'
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { signedIn } = this.props.identity;
|
||||
|
||||
@@ -426,10 +396,6 @@ class UI extends PureComponent {
|
||||
|
||||
setTimeout(() => this.props.dispatch(fetchServer()), 3000);
|
||||
}
|
||||
|
||||
this.hotkeys.__mousetrap__.stopCallback = (e, element) => {
|
||||
return ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName);
|
||||
};
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
@@ -509,10 +475,6 @@ class UI extends PureComponent {
|
||||
}
|
||||
};
|
||||
|
||||
setHotkeysRef = c => {
|
||||
this.hotkeys = c;
|
||||
};
|
||||
|
||||
handleHotkeyToggleHelp = () => {
|
||||
if (this.props.location.pathname === '/keyboard-shortcuts') {
|
||||
this.props.history.goBack();
|
||||
@@ -593,10 +555,11 @@ class UI extends PureComponent {
|
||||
goToBlocked: this.handleHotkeyGoToBlocked,
|
||||
goToMuted: this.handleHotkeyGoToMuted,
|
||||
goToRequests: this.handleHotkeyGoToRequests,
|
||||
cheat: this.handleDonate,
|
||||
};
|
||||
|
||||
return (
|
||||
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused>
|
||||
<Hotkeys global handlers={handlers}>
|
||||
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef}>
|
||||
<SwitchingColumnsArea identity={this.props.identity} location={location} singleColumn={layout === 'mobile' || layout === 'single-column'} forceOnboarding={firstLaunch && newAccount}>
|
||||
{children}
|
||||
@@ -611,7 +574,7 @@ class UI extends PureComponent {
|
||||
<ModalContainer />
|
||||
<UploadArea active={draggingOver} onClose={this.closeUploadModal} />
|
||||
</div>
|
||||
</HotKeys>
|
||||
</Hotkeys>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
{
|
||||
"about.contact": "ਸੰਪਰਕ:",
|
||||
"about.default_locale": "ਮੂਲ",
|
||||
"about.disclaimer": "ਮਸਟੋਡੋਨ ਇੱਕ ਆਜ਼ਾਦ, ਖੁੱਲ੍ਹੇ ਸਰੋਤ ਵਾਲਾ ਸਾਫਟਵੇਅਰ ਹੈ ਅਤੇ Mastodon gGmbH ਦਾ ਮਾਰਕਾ ਹੈ।",
|
||||
"about.domain_blocks.no_reason_available": "ਕਾਰਨ ਮੌਜੂਦ ਨਹੀਂ ਹੈ",
|
||||
"about.domain_blocks.silenced.title": "ਸੀਮਿਤ",
|
||||
"about.domain_blocks.suspended.title": "ਮੁਅੱਤਲ ਕੀਤੀ",
|
||||
"about.domain_blocks.suspended.title": "ਸਸਪੈਂਡ ਕੀਤਾ",
|
||||
"about.language_label": "ਭਾਸ਼ਾ",
|
||||
"about.not_available": "ਇਹ ਜਾਣਕਾਰੀ ਨੂੰ ਇਸ ਸਰਵਰ ਉੱਤੇ ਉਪਲੱਬਧ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ।",
|
||||
"about.rules": "ਸਰਵਰ ਨਿਯਮ",
|
||||
"account.account_note_header": "ਨਿੱਜੀ ਨੋਟ",
|
||||
"account.add_or_remove_from_list": "ਸੂਚੀ ਵਿੱਚ ਜੋੜੋ ਜਾਂ ਹਟਾਓ",
|
||||
@@ -12,21 +16,33 @@
|
||||
"account.block_domain": "{domain} ਡੋਮੇਨ ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਓ",
|
||||
"account.block_short": "ਪਾਬੰਦੀ",
|
||||
"account.blocked": "ਪਾਬੰਦੀਸ਼ੁਦਾ",
|
||||
"account.blocking": "ਪਾਬੰਦੀ ਲਾਉਣੀ",
|
||||
"account.cancel_follow_request": "ਫ਼ਾਲੋ ਕਰਨ ਨੂੰ ਰੱਦ ਕਰੋ",
|
||||
"account.copy": "ਪਰੋਫਾਇਲ ਲਈ ਲਿੰਕ ਕਾਪੀ ਕਰੋ",
|
||||
"account.direct": "ਨਿੱਜੀ ਜ਼ਿਕਰ @{name}",
|
||||
"account.disable_notifications": "ਜਦੋਂ {name} ਕੋਈ ਪੋਸਟ ਕਰੇ ਤਾਂ ਮੈਨੂੰ ਸੂਚਨਾ ਨਾ ਦਿਓ",
|
||||
"account.domain_blocking": "ਡੋਮੇਨ ਉੱਤੇ ਪਾਬੰਦੀ",
|
||||
"account.edit_profile": "ਪਰੋਫਾਈਲ ਨੂੰ ਸੋਧੋ",
|
||||
"account.enable_notifications": "ਜਦੋਂ {name} ਪੋਸਟ ਕਰੇ ਤਾਂ ਮੈਨੂੰ ਸੂਚਨਾ ਦਿਓ",
|
||||
"account.endorse": "ਪਰੋਫਾਇਲ ਉੱਤੇ ਫ਼ੀਚਰ",
|
||||
"account.familiar_followers_one": "{name1} ਵਲੋਂ ਫ਼ਾਲੋ ਕੀਤਾ",
|
||||
"account.familiar_followers_two": "{name1} ਅਤੇ {name2} ਵਲੋਂ ਫ਼ਾਲੋ ਕੀਤਾ",
|
||||
"account.featured": "ਫ਼ੀਚਰ",
|
||||
"account.featured.accounts": "ਪਰੋਫਾਈਲ",
|
||||
"account.featured.hashtags": "ਹੈਸ਼ਟੈਗ",
|
||||
"account.featured_tags.last_status_at": "{date} ਨੂੰ ਆਖਰੀ ਪੋਸਟ",
|
||||
"account.featured_tags.last_status_never": "ਕੋਈ ਪੋਸਟ ਨਹੀਂ",
|
||||
"account.follow": "ਫ਼ਾਲੋ",
|
||||
"account.follow_back": "ਵਾਪਸ ਫਾਲ਼ੋ ਕਰੋ",
|
||||
"account.followers": "ਫ਼ਾਲੋਅਰ",
|
||||
"account.followers.empty": "ਇਸ ਵਰਤੋਂਕਾਰ ਨੂੰ ਹਾਲੇ ਕੋਈ ਫ਼ਾਲੋ ਨਹੀਂ ਕਰਦਾ ਹੈ।",
|
||||
"account.followers_counter": "{count, plural, one {{counter} ਫ਼ਾਲੋਅਰ} other {{counter} ਫ਼ਾਲੋਅਰ}}",
|
||||
"account.followers_you_know_counter": "{counter} ਤੁਸੀਂ ਜਾਣਦੇ ਹੋ",
|
||||
"account.following": "ਫ਼ਾਲੋ ਕੀਤਾ",
|
||||
"account.follows.empty": "ਇਹ ਵਰਤੋਂਕਾਰ ਹਾਲੇ ਕਿਸੇ ਨੂੰ ਫ਼ਾਲੋ ਨਹੀਂ ਕਰਦਾ ਹੈ।",
|
||||
"account.follows_you": "ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕਰਦੇ ਹਨ",
|
||||
"account.go_to_profile": "ਪਰੋਫਾਇਲ ਉੱਤੇ ਜਾਓ",
|
||||
"account.hide_reblogs": "{name} ਵਲੋਂ ਬੂਸਟ ਨੂੰ ਲੁਕਾਓ",
|
||||
"account.joined_short": "ਜੁਆਇਨ ਕੀਤਾ",
|
||||
"account.media": "ਮੀਡੀਆ",
|
||||
"account.mention": "@{name} ਦਾ ਜ਼ਿਕਰ",
|
||||
@@ -34,6 +50,7 @@
|
||||
"account.mute_notifications_short": "ਨੋਟਫਿਕੇਸ਼ਨਾਂ ਨੂੰ ਮੌਨ ਕਰੋ",
|
||||
"account.mute_short": "ਮੌਨ ਕਰੋ",
|
||||
"account.muted": "ਮੌਨ ਕੀਤੀਆਂ",
|
||||
"account.mutual": "ਤੁਸੀਂ ਇੱਕ ਦੂਜੇ ਨੂੰ ਫ਼ਾਲੋ ਕਰਦੇ ਹੋ",
|
||||
"account.no_bio": "ਕੋਈ ਵਰਣਨ ਨਹੀਂ ਦਿੱਤਾ।",
|
||||
"account.open_original_page": "ਅਸਲ ਸਫ਼ੇ ਨੂੰ ਖੋਲ੍ਹੋ",
|
||||
"account.posts": "ਪੋਸਟਾਂ",
|
||||
@@ -42,8 +59,10 @@
|
||||
"account.requested": "ਮਨਜ਼ੂਰੀ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ। ਫ਼ਾਲੋ ਬੇਨਤੀਆਂ ਨੂੰ ਰੱਦ ਕਰਨ ਲਈ ਕਲਿੱਕ ਕਰੋ",
|
||||
"account.requested_follow": "{name} ਨੇ ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕਰਨ ਦੀ ਬੇਨਤੀ ਕੀਤੀ ਹੈ",
|
||||
"account.share": "{name} ਦਾ ਪਰੋਫ਼ਾਇਲ ਸਾਂਝਾ ਕਰੋ",
|
||||
"account.statuses_counter": "{count, plural, one {{counter} ਪੋਸਟ} other {{counter} ਪੋਸਟਾਂ}}",
|
||||
"account.unblock": "@{name} ਤੋਂ ਪਾਬੰਦੀ ਹਟਾਓ",
|
||||
"account.unblock_domain": "{domain} ਡੋਮੇਨ ਤੋਂ ਪਾਬੰਦੀ ਹਟਾਓ",
|
||||
"account.unblock_domain_short": "ਪਾਬੰਦੀ ਹਟਾਓ",
|
||||
"account.unblock_short": "ਪਾਬੰਦੀ ਹਟਾਓ",
|
||||
"account.unendorse": "ਪਰੋਫਾਇਲ ਉੱਤੇ ਫ਼ੀਚਰ ਨਾ ਕਰੋ",
|
||||
"account.unfollow": "ਅਣ-ਫ਼ਾਲੋ",
|
||||
@@ -148,6 +167,8 @@
|
||||
"confirmations.missing_alt_text.secondary": "ਕਿਵੇਂ ਵੀ ਪੋਸਟ ਕਰੋ",
|
||||
"confirmations.mute.confirm": "ਮੌਨ ਕਰੋ",
|
||||
"confirmations.redraft.confirm": "ਹਟਾਓ ਤੇ ਮੁੜ-ਡਰਾਫਟ",
|
||||
"confirmations.remove_from_followers.confirm": "ਫ਼ਾਲੋਅਰ ਨੂੰ ਹਟਾਓ",
|
||||
"confirmations.remove_from_followers.title": "ਫ਼ਾਲੋਅਰ ਨੂੰ ਹਟਾਉਣਾ ਹੈ?",
|
||||
"confirmations.unfollow.confirm": "ਅਣ-ਫ਼ਾਲੋ",
|
||||
"confirmations.unfollow.message": "ਕੀ ਤੁਸੀਂ {name} ਨੂੰ ਅਣ-ਫ਼ਾਲੋ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?",
|
||||
"confirmations.unfollow.title": "ਵਰਤੋਂਕਾਰ ਨੂੰ ਅਣ-ਫ਼ਾਲੋ ਕਰਨਾ ਹੈ?",
|
||||
@@ -182,7 +203,9 @@
|
||||
"emoji_button.custom": "ਕਸਟਮ",
|
||||
"emoji_button.flags": "ਝੰਡੀਆਂ",
|
||||
"emoji_button.food": "ਖਾਣਾ-ਪੀਣਾ",
|
||||
"emoji_button.label": "ਇਮੋਜੀ ਪਾਓ",
|
||||
"emoji_button.nature": "ਕੁਦਰਤ",
|
||||
"emoji_button.not_found": "ਕੋਈ ਮਿਲਦਾ ਇਮੋਜ਼ੀ ਨਹੀਂ ਲੱਭਿਆ",
|
||||
"emoji_button.objects": "ਇਕਾਈ",
|
||||
"emoji_button.people": "ਲੋਕ",
|
||||
"emoji_button.recent": "ਅਕਸਰ ਵਰਤੇ",
|
||||
@@ -199,9 +222,15 @@
|
||||
"empty_column.list": "ਇਸ ਸੂਚੀ ਵਿੱਚ ਹਾਲੇ ਕੁਝ ਵੀ ਨਹੀਂ ਹੈ। ਜਦੋਂ ਇਸ ਸੂਚੀ ਦੇ ਮੈਂਬਰ ਨਵੀਆਂ ਪੋਸਟਾਂ ਪਾਉਂਦੇ ਹਨ ਤਾਂ ਉਹ ਇੱਥੇ ਦਿਖਾਈ ਦੇਣਗੀਆਂ।",
|
||||
"errors.unexpected_crash.report_issue": "ਮੁੱਦੇ ਦੀ ਰਿਪੋਰਟ ਕਰੋ",
|
||||
"explore.suggested_follows": "ਲੋਕ",
|
||||
"explore.title": "ਰੁਝਾਨ",
|
||||
"explore.trending_links": "ਖ਼ਬਰਾਂ",
|
||||
"explore.trending_statuses": "ਪੋਸਟਾਂ",
|
||||
"explore.trending_tags": "ਹੈਸ਼ਟੈਗ",
|
||||
"featured_carousel.header": "{count, plural, one {ਟੰਗੀ ਹੋਈ ਪੋਸਟ} other {ਟੰਗੀਆਂ ਹੋਈਆਂ ਪੋਸਟਾਂ}}",
|
||||
"featured_carousel.next": "ਅੱਗੇ",
|
||||
"featured_carousel.post": "ਪੋਸਟ",
|
||||
"featured_carousel.previous": "ਪਿੱਛੇ",
|
||||
"featured_carousel.slide": "{total} ਵਿੱਚੋਂ {index}",
|
||||
"filter_modal.added.expired_title": "ਫਿਲਟਰ ਦੀ ਮਿਆਦ ਪੁੱਗੀ!",
|
||||
"filter_modal.added.review_and_configure_title": "ਫਿਲਟਰ ਸੈਟਿੰਗਾਂ",
|
||||
"filter_modal.added.settings_link": "ਸੈਟਿੰਗਾਂ ਸਫ਼ਾ",
|
||||
@@ -252,6 +281,8 @@
|
||||
"home.column_settings.show_replies": "ਜਵਾਬਾਂ ਨੂੰ ਵੇਖੋ",
|
||||
"home.hide_announcements": "ਐਲਾਨਾਂ ਨੂੰ ਓਹਲੇ ਕਰੋ",
|
||||
"home.pending_critical_update.link": "ਅੱਪਡੇਟ ਵੇਖੋ",
|
||||
"home.pending_critical_update.title": "ਗੰਭੀਰ ਸੁਰੱਖਿਆ ਅੱਪਡੇਟ ਮੌਜੂਦ ਹੈ!",
|
||||
"home.show_announcements": "ਐਲਾਨਾਂ ਨੂੰ ਵੇਖਾਓ",
|
||||
"ignore_notifications_modal.ignore": "ਨੋਟਫਿਕੇਸ਼ਨਾਂ ਨੂੰ ਅਣਡਿੱਠਾ ਕਰੋ",
|
||||
"info_button.label": "ਮਦਦ",
|
||||
"interaction_modal.go": "ਜਾਓ",
|
||||
@@ -332,9 +363,12 @@
|
||||
"media_gallery.hide": "ਲੁਕਾਓ",
|
||||
"mute_modal.hide_from_notifications": "ਨੋਟੀਫਿਕੇਸ਼ਨਾਂ ਵਿੱਚੋਂ ਲੁਕਾਓ",
|
||||
"mute_modal.show_options": "ਚੋਣਾਂ ਨੂੰ ਵੇਖਾਓ",
|
||||
"mute_modal.title": "ਵਰਤੋਂਕਾਰ ਨੂੰ ਮੌਨ ਕਰਨਾ ਹੈ?",
|
||||
"navigation_bar.about": "ਇਸ ਬਾਰੇ",
|
||||
"navigation_bar.account_settings": "ਪਾਸਵਰਡ ਅਤੇ ਸੁਰੱਖਿਆ",
|
||||
"navigation_bar.administration": "ਪਰਸ਼ਾਸ਼ਨ",
|
||||
"navigation_bar.advanced_interface": "ਤਕਨੀਕੀ ਵੈੱਬ ਇੰਟਰਫੇਸ ਵਿੱਚ ਖੋਲ੍ਹੋ",
|
||||
"navigation_bar.automated_deletion": "ਆਪਣੇ-ਆਪ ਹਟਾਈ ਪੋਸਟ",
|
||||
"navigation_bar.blocks": "ਪਾਬੰਦੀ ਲਾਏ ਵਰਤੋਂਕਾਰ",
|
||||
"navigation_bar.bookmarks": "ਬੁੱਕਮਾਰਕ",
|
||||
"navigation_bar.direct": "ਨਿੱਜੀ ਜ਼ਿਕਰ",
|
||||
@@ -346,11 +380,16 @@
|
||||
"navigation_bar.follows_and_followers": "ਫ਼ਾਲੋ ਅਤੇ ਫ਼ਾਲੋ ਕਰਨ ਵਾਲੇ",
|
||||
"navigation_bar.lists": "ਸੂਚੀਆਂ",
|
||||
"navigation_bar.logout": "ਲਾਗ ਆਉਟ",
|
||||
"navigation_bar.more": "ਹੋਰ",
|
||||
"navigation_bar.mutes": "ਮੌਨ ਕੀਤੇ ਵਰਤੋਂਕਾਰ",
|
||||
"navigation_bar.preferences": "ਪਸੰਦਾਂ",
|
||||
"navigation_bar.privacy_and_reach": "ਪਰਦੇਦਾਰੀ ਅਤੇ ਪਹੁੰਚ",
|
||||
"navigation_bar.search": "ਖੋਜੋ",
|
||||
"navigation_bar.search_trends": "ਖੋਜ / ਰੁਝਾਨ",
|
||||
"not_signed_in_indicator.not_signed_in": "ਇਹ ਸਰੋਤ ਵਰਤਣ ਲਈ ਤੁਹਾਨੂੰ ਲਾਗਇਨ ਕਰਨ ਦੀ ਲੋੜ ਹੈ।",
|
||||
"notification.admin.sign_up": "{name} ਨੇ ਸਾਈਨ ਅੱਪ ਕੀਤਾ",
|
||||
"notification.favourite": "{name} ਨੇ ਤੁਹਾਡੀ ਪੋਸਟ ਨੂੰ ਪਸੰਦ ਕੀਤਾ",
|
||||
"notification.favourite_pm": "{name} ਨੇ ਤੁਹਾਡੇ ਨਿੱਜੀ ਜ਼ਿਕਰ ਨੂੰ ਪਸੰਦ ਕੀਤਾ",
|
||||
"notification.follow": "{name} ਨੇ ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕੀਤਾ",
|
||||
"notification.follow.name_and_others": "{name} ਅਤੇ <a>{count, plural, one {# ਹੋਰ} other {# ਹੋਰਾਂ}}</a> ਨੇ ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕੀਤਾ",
|
||||
"notification.follow_request": "{name} ਨੇ ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕਰਨ ਦੀ ਬੇਨਤੀ ਕੀਤੀ ਹੈ",
|
||||
@@ -365,6 +404,7 @@
|
||||
"notification.moderation_warning.action_silence": "ਤੁਹਾਡੇ ਖਾਤੇ ਨੂੰ ਸੀਮਿਤ ਕੀਤਾ ਗਿਆ ਹੈ।",
|
||||
"notification.moderation_warning.action_suspend": "ਤੁਹਾਡੇ ਖਾਤੇ ਨੂੰ ਮੁਅੱਤਲ ਕੀਤਾ ਗਿਆ ਹੈ।",
|
||||
"notification.reblog": "{name} boosted your status",
|
||||
"notification.relationships_severance_event": "{name} ਨਾਲ ਕਨੈਕਸ਼ਨ ਗੁਆਚੇ",
|
||||
"notification.relationships_severance_event.learn_more": "ਹੋਰ ਜਾਣੋ",
|
||||
"notification.status": "{name} ਨੇ ਹੁਣੇ ਪੋਸਟ ਕੀਤਾ",
|
||||
"notification.update": "{name} ਨੋ ਪੋਸਟ ਨੂੰ ਸੋਧਿਆ",
|
||||
@@ -540,7 +580,10 @@
|
||||
"status.unpin": "ਪਰੋਫਾਈਲ ਤੋਂ ਲਾਹੋ",
|
||||
"subscribed_languages.save": "ਤਬਦੀਲੀਆਂ ਸੰਭਾਲੋ",
|
||||
"tabs_bar.home": "ਘਰ",
|
||||
"tabs_bar.menu": "ਮੇਨੂ",
|
||||
"tabs_bar.notifications": "ਸੂਚਨਾਵਾਂ",
|
||||
"tabs_bar.publish": "ਨਵੀਂ ਪੋਸਟ",
|
||||
"tabs_bar.search": "ਖੋਜੋ",
|
||||
"terms_of_service.title": "ਸੇਵਾ ਦੀਆਂ ਸ਼ਰਤਾਂ",
|
||||
"time_remaining.days": "{number, plural, one {# ਦਿਨ} other {# ਦਿਨ}} ਬਾਕੀ",
|
||||
"time_remaining.hours": "{number, plural, one {# ਘੰਟਾ} other {# ਘੰਟੇ}} ਬਾਕੀ",
|
||||
@@ -561,6 +604,9 @@
|
||||
"video.expand": "ਵੀਡੀਓ ਨੂੰ ਫੈਲਾਓ",
|
||||
"video.fullscreen": "ਪੂਰੀ ਸਕਰੀਨ",
|
||||
"video.hide": "ਵੀਡੀਓ ਨੂੰ ਲੁਕਾਓ",
|
||||
"video.mute": "ਮੌਨ",
|
||||
"video.pause": "ਠਹਿਰੋ",
|
||||
"video.play": "ਚਲਾਓ"
|
||||
"video.play": "ਚਲਾਓ",
|
||||
"video.volume_down": "ਅਵਾਜ਼ ਘਟਾਓ",
|
||||
"video.volume_up": "ਅਵਾਜ਼ ਵਧਾਓ"
|
||||
}
|
||||
|
||||
@@ -537,8 +537,10 @@
|
||||
"mute_modal.you_wont_see_mentions": "你看不到提及对方的嘟文。",
|
||||
"mute_modal.you_wont_see_posts": "对方可以看到你的嘟文,但是你看不到对方的。",
|
||||
"navigation_bar.about": "关于",
|
||||
"navigation_bar.account_settings": "密码与安全",
|
||||
"navigation_bar.administration": "管理",
|
||||
"navigation_bar.advanced_interface": "在高级网页界面中打开",
|
||||
"navigation_bar.automated_deletion": "自动删除嘟文",
|
||||
"navigation_bar.blocks": "已屏蔽的用户",
|
||||
"navigation_bar.bookmarks": "书签",
|
||||
"navigation_bar.direct": "私下提及",
|
||||
|
||||
@@ -318,6 +318,8 @@ et:
|
||||
new:
|
||||
create: Loo teadaanne
|
||||
title: Uus teadaanne
|
||||
preview:
|
||||
title: Info teavituse üle vaatamine
|
||||
publish: Postita
|
||||
published_msg: Teadaande avaldamine õnnestus!
|
||||
scheduled_for: Kavandatud ajaks %{time}
|
||||
@@ -485,6 +487,7 @@ et:
|
||||
request_body: Päringu sisu
|
||||
providers:
|
||||
active: Aktiivne
|
||||
base_url: Baas-URL
|
||||
callback: Pöördliiklus
|
||||
delete: Kustuta
|
||||
edit: Muuda teenusepakkujat
|
||||
@@ -499,6 +502,7 @@ et:
|
||||
reject: Keeldu
|
||||
title: Kinnita FASP-i registreerimine
|
||||
save: Salvesta
|
||||
select_capabilities: Vali oskused
|
||||
sign_in: Logi sisse
|
||||
status: Olek
|
||||
title: Täiendavad teenusepakkujad Födiversumis (FASP - Fediverse Auxiliary Service Providers)
|
||||
|
||||
@@ -1220,9 +1220,9 @@ ga:
|
||||
confirmation_dialogs: Dialóga deimhnithe
|
||||
discovery: Fionnachtain
|
||||
localization:
|
||||
body: Aistríonn oibrithe deonacha Mastodon.
|
||||
body: Oibrithe deonacha a dhéanann aistriúchán Mastodon.
|
||||
guide_link: https://crowdin.com/project/mastodon
|
||||
guide_link_text: Is féidir le gach duine rannchuidiú.
|
||||
guide_link_text: Is féidir le gach duine cur leis.
|
||||
sensitive_content: Ábhar íogair
|
||||
application_mailer:
|
||||
notification_preferences: Athraigh roghanna ríomhphoist
|
||||
|
||||
@@ -454,6 +454,7 @@ nan:
|
||||
title: 封鎖新ê電子phue網域
|
||||
no_email_domain_block_selected: 因為無揀任何電子phue域名封鎖,所以lóng無改變
|
||||
not_permitted: 無允准
|
||||
resolved_dns_records_hint_html: 域名解析做下kha ê MX域名,tsiah ê域名上後負責收電子phue。封鎖MX域名ē封任何有siâng款MX域名ê電子郵件ê註冊,就算通看見ê域名無kâng,mā án-ne。<strong>Tio̍h細膩,m̄通封鎖主要ê電子phue提供者。</strong>
|
||||
resolved_through_html: 通過 %{domain} 解析
|
||||
title: 封鎖ê電子phue網域
|
||||
export_domain_allows:
|
||||
@@ -468,9 +469,78 @@ nan:
|
||||
private_comment_template: 佇 %{date} tuì %{source} 輸入
|
||||
title: 輸入域名封鎖
|
||||
invalid_domain_block: 因為下kha ê錯誤,làng過tsi̍t ê以上ê域名封鎖:%{error}
|
||||
new:
|
||||
title: 輸入域名封鎖
|
||||
no_file: Iáu bē揀檔案
|
||||
fasp:
|
||||
debug:
|
||||
callbacks:
|
||||
created_at: 建立佇
|
||||
delete: Thâi掉
|
||||
ip: IP地址
|
||||
request_body: 請求主文
|
||||
title: 除蟲callback
|
||||
providers:
|
||||
active: 有效
|
||||
base_url: 基本URL
|
||||
callback: Callback
|
||||
delete: Thâi掉
|
||||
edit: 編輯提供者
|
||||
finish_registration: 完成註冊
|
||||
name: 名
|
||||
providers: 提供者
|
||||
public_key_fingerprint: 公開鎖匙ê指頭仔螺(public key fingerprint)
|
||||
registration_requested: 註冊請求ah
|
||||
registrations:
|
||||
confirm: 確認
|
||||
description: Lí收著FASP ê註冊ah。nā準lí bô啟動,請拒絕。若有啟動,請佇確認註冊以前,細膩比較名kap鎖匙ê指頭仔螺。
|
||||
reject: 拒絕
|
||||
title: 確認FASP註冊
|
||||
save: 儲存
|
||||
select_capabilities: 揀功能
|
||||
sign_in: 登入
|
||||
status: 狀態
|
||||
title: 聯邦宇宙輔助服務提供者 (FASP)
|
||||
title: FASP
|
||||
follow_recommendations:
|
||||
description_html: "<strong>跟tuè建議幫tsān新用者緊tshuē著心適ê內容</strong>。Nā使用者無hām別lâng有夠額ê互動,來形成個人化ê跟tuè建議,就ē推薦tsiah ê口座。In是佇指定語言內底,由最近上tsia̍p參與ê,kap上tsē lâng跟tuè ê口座,用ta̍k kang做基礎,相濫koh計算出來ê。"
|
||||
language: 揀語言
|
||||
status: 狀態
|
||||
suppress: Khàm掉跟tuè建議
|
||||
suppressed: Khàm掉ê
|
||||
title: 跟tuè建議
|
||||
unsuppress: 恢復跟tuè建議
|
||||
instances:
|
||||
audit_log:
|
||||
title: 最近ê審核日誌
|
||||
view_all: 看完整ê審核日誌
|
||||
availability:
|
||||
description_html:
|
||||
other: Nā佇 <strong>%{count} kang</strong>內,寄送kàu hit ê域名lóng失敗,除非收著hit ê域名<em>來ê</em>寄送,a̍h無buē koh試寄送。
|
||||
failure_threshold_reached: 佇 %{date} kàu失敗ê底限。
|
||||
failures_recorded:
|
||||
other: 連suà %{count} kang lóng寄失敗。
|
||||
no_failures_recorded: 報告內底無失敗。
|
||||
title: 可用性
|
||||
warning: 頂kái試連接tsit臺服侍器是無成功
|
||||
back_to_all: 全部
|
||||
back_to_limited: 受限制
|
||||
back_to_warning: 警告
|
||||
by_domain: 域名
|
||||
confirm_purge: Lí kám確定beh永永thâi掉tsit ê域名來ê資料?
|
||||
content_policies:
|
||||
comment: 內部ê筆記
|
||||
description_html: Lí ē當定義用tī所有tuì tsit ê域名kap伊ê子域名來ê口座ê內容政策。
|
||||
dashboard:
|
||||
instance_languages_dimension: Tsia̍p用ê語言
|
||||
invites:
|
||||
filter:
|
||||
available: 通用ê
|
||||
expired: 過期ê
|
||||
title: 過濾器
|
||||
title: 邀請
|
||||
ip_blocks:
|
||||
add_new: 建立規則
|
||||
statuses:
|
||||
language: 語言
|
||||
trends:
|
||||
|
||||
@@ -7,7 +7,13 @@ pa:
|
||||
hosted_on: "%{domain} ਉੱਤੇ ਹੋਸਟ ਕੀਤਾ ਮਸਟਾਡੋਨ"
|
||||
title: ਇਸ ਬਾਰੇ
|
||||
accounts:
|
||||
followers:
|
||||
one: ਫ਼ਾਲੋਅਰ
|
||||
other: ਫ਼ਾਲੋਅਰ
|
||||
following: ਫ਼ਾਲੋ ਕੀਤੇ ਜਾ ਰਹੇ
|
||||
posts:
|
||||
one: ਪੋਸਟ
|
||||
other: ਪੋਸਟਾਂ
|
||||
posts_tab_heading: ਪੋਸਟਾਂ
|
||||
admin:
|
||||
account_moderation_notes:
|
||||
@@ -126,6 +132,9 @@ pa:
|
||||
thread: ਗੱਲਾਂਬਾਤਾਂ
|
||||
index:
|
||||
delete: ਹਟਾਓ
|
||||
statuses:
|
||||
one: "%{count} ਪੋਸਟ"
|
||||
other: "%{count} ਪੋਸਟ"
|
||||
generic:
|
||||
all: ਸਭ
|
||||
copy: ਕਾਪੀ ਕਰੋ
|
||||
|
||||
@@ -61,7 +61,7 @@ es-MX:
|
||||
setting_display_media_default: Ocultar contenido multimedia marcado como sensible
|
||||
setting_display_media_hide_all: Siempre ocultar todo el contenido multimedia
|
||||
setting_display_media_show_all: Mostrar siempre contenido multimedia marcado como sensible
|
||||
setting_emoji_style: Cómo se mostrarán los emojis. "Auto" intentará usar emojis nativos, cambiando a Twemoji en navegadores antiguos.
|
||||
setting_emoji_style: Cómo se muestran los emojis. «Automático» intentará usar emojis nativos, pero vuelve a Twemoji para los navegadores antiguos.
|
||||
setting_system_scrollbars_ui: Solo se aplica a los navegadores de escritorio basados en Safari y Chrome
|
||||
setting_use_blurhash: Los degradados se basan en los colores de los elementos visuales ocultos, pero ocultan cualquier detalle
|
||||
setting_use_pending_items: Ocultar las publicaciones de la línea de tiempo tras un clic en lugar de desplazar automáticamente el feed
|
||||
@@ -151,8 +151,8 @@ es-MX:
|
||||
user:
|
||||
chosen_languages: Cuando se marca, solo se mostrarán las publicaciones en los idiomas seleccionados en las líneas de tiempo públicas
|
||||
date_of_birth:
|
||||
one: Tenemos que asegurarnos de que tienes al menos %{count} para usar %{domain}. No guardaremos esta información.
|
||||
other: Tenemos que asegurarnos de que tienes al menos %{count} para usar %{domain}. No guardaremos esta información.
|
||||
one: Tenemos que asegurarnos de que tienes al menos %{count} para usar %{domain}. No almacenaremos esta información.
|
||||
other: Tenemos que asegurarnos de que tienes al menos %{count} para usar %{domain}. No almacenaremos esta información.
|
||||
role: El rol controla qué permisos tiene el usuario.
|
||||
user_role:
|
||||
color: Color que se usará para el rol en toda la interfaz de usuario, como RGB en formato hexadecimal
|
||||
|
||||
84
config/vite/plugin-assets-manifest.ts
Normal file
84
config/vite/plugin-assets-manifest.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
// Heavily inspired by https://github.com/ElMassimo/vite_ruby
|
||||
|
||||
import { createHash } from 'node:crypto';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
import glob from 'fast-glob';
|
||||
import type { Plugin } from 'vite';
|
||||
|
||||
interface AssetManifestChunk {
|
||||
file: string;
|
||||
integrity: string;
|
||||
}
|
||||
|
||||
const ALGORITHM = 'sha384';
|
||||
|
||||
export function MastodonAssetsManifest(): Plugin {
|
||||
let manifest: string | boolean = true;
|
||||
let jsRoot = '';
|
||||
|
||||
return {
|
||||
name: 'mastodon-assets-manifest',
|
||||
applyToEnvironment(environment) {
|
||||
return !!environment.config.build.manifest;
|
||||
},
|
||||
configResolved(resolvedConfig) {
|
||||
manifest = resolvedConfig.build.manifest;
|
||||
jsRoot = resolvedConfig.root;
|
||||
},
|
||||
async generateBundle() {
|
||||
// Glob all assets and return an array of absolute paths.
|
||||
const assetPaths = await glob('{fonts,icons,images}/**/*', {
|
||||
cwd: jsRoot,
|
||||
absolute: true,
|
||||
});
|
||||
|
||||
const assetManifest: Record<string, AssetManifestChunk> = {};
|
||||
const excludeExts = ['', '.md'];
|
||||
for (const file of assetPaths) {
|
||||
// Exclude files like markdown or README files with no extension.
|
||||
const ext = path.extname(file);
|
||||
if (excludeExts.includes(ext)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read the file and emit it as an asset.
|
||||
const contents = await fs.readFile(file);
|
||||
const ref = this.emitFile({
|
||||
name: path.basename(file),
|
||||
type: 'asset',
|
||||
source: contents,
|
||||
});
|
||||
const hashedFilename = this.getFileName(ref);
|
||||
|
||||
// With the emitted file information, hash the contents and store in manifest.
|
||||
const name = path.relative(jsRoot, file);
|
||||
const hash = createHash(ALGORITHM)
|
||||
.update(contents)
|
||||
.digest()
|
||||
.toString('base64');
|
||||
assetManifest[name] = {
|
||||
file: hashedFilename,
|
||||
integrity: `${ALGORITHM}-${hash}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (Object.keys(assetManifest).length === 0) {
|
||||
console.warn('Asset manifest is empty');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get manifest location and emit the manifest.
|
||||
const manifestDir =
|
||||
typeof manifest === 'string' ? path.dirname(manifest) : '.vite';
|
||||
const fileName = `${manifestDir}/manifest-assets.json`;
|
||||
|
||||
this.emitFile({
|
||||
fileName,
|
||||
type: 'asset',
|
||||
source: JSON.stringify(assetManifest, null, 2),
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -90,7 +90,6 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-hotkeys": "^1.1.4",
|
||||
"react-immutable-proptypes": "^2.2.0",
|
||||
"react-immutable-pure-component": "^2.2.2",
|
||||
"react-intl": "^7.1.10",
|
||||
@@ -165,12 +164,12 @@
|
||||
"@vitest/browser": "^3.2.1",
|
||||
"@vitest/coverage-v8": "^3.2.0",
|
||||
"@vitest/ui": "^3.2.1",
|
||||
"chromatic": "^12.1.0",
|
||||
"chromatic": "^13.0.0",
|
||||
"eslint": "^9.23.0",
|
||||
"eslint-import-resolver-typescript": "^4.2.5",
|
||||
"eslint-plugin-formatjs": "^5.3.1",
|
||||
"eslint-plugin-import": "~2.31.0",
|
||||
"eslint-plugin-jsdoc": "^50.6.9",
|
||||
"eslint-plugin-jsdoc": "^51.0.0",
|
||||
"eslint-plugin-jsx-a11y": "~6.10.2",
|
||||
"eslint-plugin-promise": "~7.2.1",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
@@ -181,7 +180,7 @@
|
||||
"lint-staged": "^16.0.0",
|
||||
"msw": "^2.10.2",
|
||||
"msw-storybook-addon": "^2.0.5",
|
||||
"playwright": "^1.52.0",
|
||||
"playwright": "^1.54.1",
|
||||
"prettier": "^3.3.3",
|
||||
"react-test-renderer": "^18.2.0",
|
||||
"storybook": "^9.0.4",
|
||||
|
||||
@@ -4,7 +4,6 @@ import { readdir } from 'node:fs/promises';
|
||||
import { optimizeLodashImports } from '@optimize-lodash/rollup-plugin';
|
||||
import legacy from '@vitejs/plugin-legacy';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import glob from 'fast-glob';
|
||||
import postcssPresetEnv from 'postcss-preset-env';
|
||||
import Compress from 'rollup-plugin-gzip';
|
||||
import { visualizer } from 'rollup-plugin-visualizer';
|
||||
@@ -24,6 +23,7 @@ import { MastodonServiceWorkerLocales } from './config/vite/plugin-sw-locales';
|
||||
import { MastodonEmojiCompressed } from './config/vite/plugin-emoji-compressed';
|
||||
import { GlitchThemes } from './config/vite/plugin-glitch-themes';
|
||||
import { MastodonNameLookup } from './config/vite/plugin-name-lookup';
|
||||
import { MastodonAssetsManifest } from './config/vite/plugin-assets-manifest';
|
||||
|
||||
const jsRoot = path.resolve(__dirname, 'app/javascript');
|
||||
|
||||
@@ -120,6 +120,7 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => {
|
||||
},
|
||||
}),
|
||||
GlitchThemes(),
|
||||
MastodonAssetsManifest(),
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
@@ -144,7 +145,7 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => {
|
||||
isProdBuild && (Compress() as PluginOption),
|
||||
command === 'build' &&
|
||||
manifestSRI({
|
||||
manifestPaths: ['.vite/manifest.json', '.vite/manifest-assets.json'],
|
||||
manifestPaths: ['.vite/manifest.json'],
|
||||
}),
|
||||
VitePWA({
|
||||
srcDir: path.resolve(jsRoot, 'mastodon/service_worker'),
|
||||
@@ -211,21 +212,6 @@ async function findEntrypoints() {
|
||||
}
|
||||
}
|
||||
|
||||
// Lastly other assets
|
||||
const assetEntrypoints = await glob('{fonts,icons,images}/**/*', {
|
||||
cwd: jsRoot,
|
||||
absolute: true,
|
||||
});
|
||||
const excludeExts = ['', '.md'];
|
||||
for (const file of assetEntrypoints) {
|
||||
const ext = path.extname(file);
|
||||
if (excludeExts.includes(ext)) {
|
||||
continue;
|
||||
}
|
||||
const name = path.basename(file);
|
||||
entrypoints[name] = path.resolve(jsRoot, file);
|
||||
}
|
||||
|
||||
return entrypoints;
|
||||
}
|
||||
|
||||
|
||||
212
yarn.lock
212
yarn.lock
@@ -1932,14 +1932,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@es-joy/jsdoccomment@npm:~0.49.0":
|
||||
version: 0.49.0
|
||||
resolution: "@es-joy/jsdoccomment@npm:0.49.0"
|
||||
"@es-joy/jsdoccomment@npm:~0.52.0":
|
||||
version: 0.52.0
|
||||
resolution: "@es-joy/jsdoccomment@npm:0.52.0"
|
||||
dependencies:
|
||||
"@types/estree": "npm:^1.0.8"
|
||||
"@typescript-eslint/types": "npm:^8.34.1"
|
||||
comment-parser: "npm:1.4.1"
|
||||
esquery: "npm:^1.6.0"
|
||||
jsdoc-type-pratt-parser: "npm:~4.1.0"
|
||||
checksum: 10c0/16717507d557d37e7b59456fedeefbe0a3bc93aa2d9c043d5db91e24e076509b6fcb10ee6fd1dafcb0c5bbe50ae329b45de5b83541cb5994a98c9e862a45641e
|
||||
checksum: 10c0/4def78060ef58859f31757b9d30c4939fc33e7d9ee85637a7f568c1d209c33aa0abd2cf5a3a4f3662ec5b12b85ecff2f2035d809dc93b9382a31a6dfb200d83c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2660,7 +2662,7 @@ __metadata:
|
||||
babel-plugin-formatjs: "npm:^10.5.37"
|
||||
babel-plugin-transform-react-remove-prop-types: "npm:^0.4.24"
|
||||
blurhash: "npm:^2.0.5"
|
||||
chromatic: "npm:^12.1.0"
|
||||
chromatic: "npm:^13.0.0"
|
||||
classnames: "npm:^2.3.2"
|
||||
cocoon-js-vanilla: "npm:^1.5.1"
|
||||
color-blend: "npm:^4.0.0"
|
||||
@@ -2675,7 +2677,7 @@ __metadata:
|
||||
eslint-import-resolver-typescript: "npm:^4.2.5"
|
||||
eslint-plugin-formatjs: "npm:^5.3.1"
|
||||
eslint-plugin-import: "npm:~2.31.0"
|
||||
eslint-plugin-jsdoc: "npm:^50.6.9"
|
||||
eslint-plugin-jsdoc: "npm:^51.0.0"
|
||||
eslint-plugin-jsx-a11y: "npm:~6.10.2"
|
||||
eslint-plugin-promise: "npm:~7.2.1"
|
||||
eslint-plugin-react: "npm:^7.37.4"
|
||||
@@ -2700,7 +2702,7 @@ __metadata:
|
||||
msw: "npm:^2.10.2"
|
||||
msw-storybook-addon: "npm:^2.0.5"
|
||||
path-complete-extname: "npm:^1.0.0"
|
||||
playwright: "npm:^1.52.0"
|
||||
playwright: "npm:^1.54.1"
|
||||
postcss-preset-env: "npm:^10.1.5"
|
||||
prettier: "npm:^3.3.3"
|
||||
prop-types: "npm:^15.8.1"
|
||||
@@ -2708,7 +2710,6 @@ __metadata:
|
||||
react: "npm:^18.2.0"
|
||||
react-dom: "npm:^18.2.0"
|
||||
react-helmet: "npm:^6.1.0"
|
||||
react-hotkeys: "npm:^1.1.4"
|
||||
react-immutable-proptypes: "npm:^2.2.0"
|
||||
react-immutable-pure-component: "npm:^2.2.2"
|
||||
react-intl: "npm:^7.1.10"
|
||||
@@ -3091,13 +3092,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@pkgr/core@npm:^0.1.0":
|
||||
version: 0.1.1
|
||||
resolution: "@pkgr/core@npm:0.1.1"
|
||||
checksum: 10c0/3f7536bc7f57320ab2cf96f8973664bef624710c403357429fbf680a5c3b4843c1dbd389bb43daa6b1f6f1f007bb082f5abcb76bb2b5dc9f421647743b71d3d8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@polka/url@npm:^1.0.0-next.24":
|
||||
version: 1.0.0-next.29
|
||||
resolution: "@polka/url@npm:1.0.0-next.29"
|
||||
@@ -3988,10 +3982,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/estree@npm:*, @types/estree@npm:1.0.7, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6":
|
||||
version: 1.0.7
|
||||
resolution: "@types/estree@npm:1.0.7"
|
||||
checksum: 10c0/be815254316882f7c40847336cd484c3bc1c3e34f710d197160d455dc9d6d050ffbf4c3bc76585dba86f737f020ab20bdb137ebe0e9116b0c86c7c0342221b8c
|
||||
"@types/estree@npm:*, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8":
|
||||
version: 1.0.8
|
||||
resolution: "@types/estree@npm:1.0.8"
|
||||
checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4002,6 +3996,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/estree@npm:1.0.7":
|
||||
version: 1.0.7
|
||||
resolution: "@types/estree@npm:1.0.7"
|
||||
checksum: 10c0/be815254316882f7c40847336cd484c3bc1c3e34f710d197160d455dc9d6d050ffbf4c3bc76585dba86f737f020ab20bdb137ebe0e9116b0c86c7c0342221b8c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/express-serve-static-core@npm:^4.17.33":
|
||||
version: 4.17.41
|
||||
resolution: "@types/express-serve-static-core@npm:4.17.41"
|
||||
@@ -4504,13 +4505,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:8.33.0, @typescript-eslint/types@npm:^8.33.0":
|
||||
"@typescript-eslint/types@npm:8.33.0":
|
||||
version: 8.33.0
|
||||
resolution: "@typescript-eslint/types@npm:8.33.0"
|
||||
checksum: 10c0/348b64eb408719d7711a433fc9716e0c2aab8b3f3676f5a1cc2e00269044132282cf655deb6d0dd9817544116909513de3b709005352d186949d1014fad1a3cb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:^8.33.0, @typescript-eslint/types@npm:^8.34.1":
|
||||
version: 8.36.0
|
||||
resolution: "@typescript-eslint/types@npm:8.36.0"
|
||||
checksum: 10c0/cacb941a0caad6ab556c416051b97ec33b364b7c8e0703e2729ae43f12daf02b42eef12011705329107752e3f1685ca82cfffe181d637f85907293cb634bee31
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/typescript-estree@npm:8.29.1":
|
||||
version: 8.29.1
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.29.1"
|
||||
@@ -4999,12 +5007,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"acorn@npm:^8.14.0, acorn@npm:^8.8.2":
|
||||
version: 8.14.1
|
||||
resolution: "acorn@npm:8.14.1"
|
||||
"acorn@npm:^8.14.0, acorn@npm:^8.15.0, acorn@npm:^8.8.2":
|
||||
version: 8.15.0
|
||||
resolution: "acorn@npm:8.15.0"
|
||||
bin:
|
||||
acorn: bin/acorn
|
||||
checksum: 10c0/dbd36c1ed1d2fa3550140000371fcf721578095b18777b85a79df231ca093b08edc6858d75d6e48c73e431c174dcf9214edbd7e6fa5911b93bd8abfa54e47123
|
||||
checksum: 10c0/dec73ff59b7d6628a01eebaece7f2bdb8bb62b9b5926dcad0f8931f2b8b79c2be21f6c68ac095592adb5adb15831a3635d9343e6a91d028bbe85d564875ec3ec
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5389,13 +5397,13 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"axios@npm:^1.4.0":
|
||||
version: 1.9.0
|
||||
resolution: "axios@npm:1.9.0"
|
||||
version: 1.10.0
|
||||
resolution: "axios@npm:1.10.0"
|
||||
dependencies:
|
||||
follow-redirects: "npm:^1.15.6"
|
||||
form-data: "npm:^4.0.0"
|
||||
proxy-from-env: "npm:^1.1.0"
|
||||
checksum: 10c0/9371a56886c2e43e4ff5647b5c2c3c046ed0a3d13482ef1d0135b994a628c41fbad459796f101c655e62f0c161d03883454474d2e435b2e021b1924d9f24994c
|
||||
checksum: 10c0/2239cb269cc789eac22f5d1aabd58e1a83f8f364c92c2caa97b6f5cbb4ab2903d2e557d9dc670b5813e9bcdebfb149e783fb8ab3e45098635cd2f559b06bd5d8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5825,9 +5833,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chromatic@npm:^12.1.0":
|
||||
version: 12.1.0
|
||||
resolution: "chromatic@npm:12.1.0"
|
||||
"chromatic@npm:^13.0.0":
|
||||
version: 13.0.0
|
||||
resolution: "chromatic@npm:13.0.0"
|
||||
peerDependencies:
|
||||
"@chromatic-com/cypress": ^0.*.* || ^1.0.0
|
||||
"@chromatic-com/playwright": ^0.*.* || ^1.0.0
|
||||
@@ -5840,7 +5848,7 @@ __metadata:
|
||||
chroma: dist/bin.js
|
||||
chromatic: dist/bin.js
|
||||
chromatic-cli: dist/bin.js
|
||||
checksum: 10c0/4acb70a4a84605f1963a823beed4f3062ec91e373104500f4295af2298b8d0b49f864d06ca81bc9389e44cae3a284332aac07c6cbfc123aa6457f3b52a4c4b78
|
||||
checksum: 10c0/30c697eb84d5b3b8cdab989df0e4fed0bf51f4bfefb616873f68fc00337978b9b38b84e52af22861769176181bd98525d467baeb22daa712a0f7a58bd61bf336
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -6790,7 +6798,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"es-module-lexer@npm:^1.5.3, es-module-lexer@npm:^1.7.0":
|
||||
"es-module-lexer@npm:^1.7.0":
|
||||
version: 1.7.0
|
||||
resolution: "es-module-lexer@npm:1.7.0"
|
||||
checksum: 10c0/4c935affcbfeba7fb4533e1da10fa8568043df1e3574b869385980de9e2d475ddc36769891936dbb07036edb3c3786a8b78ccf44964cd130dedc1f2c984b6c7b
|
||||
@@ -7050,24 +7058,23 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-plugin-jsdoc@npm:^50.6.9":
|
||||
version: 50.6.9
|
||||
resolution: "eslint-plugin-jsdoc@npm:50.6.9"
|
||||
"eslint-plugin-jsdoc@npm:^51.0.0":
|
||||
version: 51.3.4
|
||||
resolution: "eslint-plugin-jsdoc@npm:51.3.4"
|
||||
dependencies:
|
||||
"@es-joy/jsdoccomment": "npm:~0.49.0"
|
||||
"@es-joy/jsdoccomment": "npm:~0.52.0"
|
||||
are-docs-informative: "npm:^0.0.2"
|
||||
comment-parser: "npm:1.4.1"
|
||||
debug: "npm:^4.3.6"
|
||||
debug: "npm:^4.4.1"
|
||||
escape-string-regexp: "npm:^4.0.0"
|
||||
espree: "npm:^10.1.0"
|
||||
espree: "npm:^10.4.0"
|
||||
esquery: "npm:^1.6.0"
|
||||
parse-imports: "npm:^2.1.1"
|
||||
semver: "npm:^7.6.3"
|
||||
parse-imports-exports: "npm:^0.2.4"
|
||||
semver: "npm:^7.7.2"
|
||||
spdx-expression-parse: "npm:^4.0.0"
|
||||
synckit: "npm:^0.9.1"
|
||||
peerDependencies:
|
||||
eslint: ^7.0.0 || ^8.0.0 || ^9.0.0
|
||||
checksum: 10c0/cad199d262c2e889a3af4e402f6adc624e4273b3d5ca1940e7227b37d87af8090ca3444f7fff57f58dab9a827faed8722fc2f5d4daf31ec085eb00e9f5a338a7
|
||||
checksum: 10c0/59e5aa972bdd1bd4e2ca2796ed4455dff1069044abc028621e107aa4b0cbb62ce09554c8e7c2ff3a44a1cbd551e54b6970adc420ba3a89adc6236b94310a81ff
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -7173,10 +7180,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-visitor-keys@npm:^4.2.0":
|
||||
version: 4.2.0
|
||||
resolution: "eslint-visitor-keys@npm:4.2.0"
|
||||
checksum: 10c0/2ed81c663b147ca6f578312919483eb040295bbab759e5a371953456c636c5b49a559883e2677112453728d66293c0a4c90ab11cab3428cf02a0236d2e738269
|
||||
"eslint-visitor-keys@npm:^4.2.0, eslint-visitor-keys@npm:^4.2.1":
|
||||
version: 4.2.1
|
||||
resolution: "eslint-visitor-keys@npm:4.2.1"
|
||||
checksum: 10c0/fcd43999199d6740db26c58dbe0c2594623e31ca307e616ac05153c9272f12f1364f5a0b1917a8e962268fdecc6f3622c1c2908b4fcc2e047a106fe6de69dc43
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -7230,14 +7237,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"espree@npm:^10.0.1, espree@npm:^10.1.0, espree@npm:^10.3.0":
|
||||
version: 10.3.0
|
||||
resolution: "espree@npm:10.3.0"
|
||||
"espree@npm:^10.0.1, espree@npm:^10.3.0, espree@npm:^10.4.0":
|
||||
version: 10.4.0
|
||||
resolution: "espree@npm:10.4.0"
|
||||
dependencies:
|
||||
acorn: "npm:^8.14.0"
|
||||
acorn: "npm:^8.15.0"
|
||||
acorn-jsx: "npm:^5.3.2"
|
||||
eslint-visitor-keys: "npm:^4.2.0"
|
||||
checksum: 10c0/272beeaca70d0a1a047d61baff64db04664a33d7cfb5d144f84bc8a5c6194c6c8ebe9cc594093ca53add88baa23e59b01e69e8a0160ab32eac570482e165c462
|
||||
eslint-visitor-keys: "npm:^4.2.1"
|
||||
checksum: 10c0/c63fe06131c26c8157b4083313cb02a9a54720a08e21543300e55288c40e06c3fc284bdecf108d3a1372c5934a0a88644c98714f38b6ae8ed272b40d9ea08d6b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9188,27 +9195,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash.isboolean@npm:^3.0.3":
|
||||
version: 3.0.3
|
||||
resolution: "lodash.isboolean@npm:3.0.3"
|
||||
checksum: 10c0/0aac604c1ef7e72f9a6b798e5b676606042401dd58e49f051df3cc1e3adb497b3d7695635a5cbec4ae5f66456b951fdabe7d6b387055f13267cde521f10ec7f7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash.isequal@npm:^4.5.0":
|
||||
version: 4.5.0
|
||||
resolution: "lodash.isequal@npm:4.5.0"
|
||||
checksum: 10c0/dfdb2356db19631a4b445d5f37868a095e2402292d59539a987f134a8778c62a2810c2452d11ae9e6dcac71fc9de40a6fedcb20e2952a15b431ad8b29e50e28f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash.isobject@npm:^3.0.2":
|
||||
version: 3.0.2
|
||||
resolution: "lodash.isobject@npm:3.0.2"
|
||||
checksum: 10c0/da4c8480d98b16835b59380b2fbd43c54081acd9466febb788ba77c434384349e0bec162d1c4e89f613f21687b2b6d8384d8a112b80da00c78d28d9915a5cdde
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash.merge@npm:^4.6.2":
|
||||
version: 4.6.2
|
||||
resolution: "lodash.merge@npm:4.6.2"
|
||||
@@ -9619,13 +9605,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mousetrap@npm:^1.5.2":
|
||||
version: 1.6.5
|
||||
resolution: "mousetrap@npm:1.6.5"
|
||||
checksum: 10c0/5c361bdbbff3966fd58d70f39b9fe1f8e32c78f3ce65989d83af7aad32a3a95313ce835a8dd8a55cb5de9eeb7c1f0c2b9048631a3073b5606241589e8fc0ba53
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mrmime@npm:^2.0.0":
|
||||
version: 2.0.1
|
||||
resolution: "mrmime@npm:2.0.1"
|
||||
@@ -10048,13 +10027,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"parse-imports@npm:^2.1.1":
|
||||
version: 2.1.1
|
||||
resolution: "parse-imports@npm:2.1.1"
|
||||
"parse-imports-exports@npm:^0.2.4":
|
||||
version: 0.2.4
|
||||
resolution: "parse-imports-exports@npm:0.2.4"
|
||||
dependencies:
|
||||
es-module-lexer: "npm:^1.5.3"
|
||||
slashes: "npm:^3.0.12"
|
||||
checksum: 10c0/c9bb0b4e1823f84f034d2d7bd2b37415b1715a5c963fda14968c706186b48b02c10e97d04bce042b9dcd679b42f29c391ea120799ddf581c7f54786edd99e3a9
|
||||
parse-statements: "npm:1.0.11"
|
||||
checksum: 10c0/51b729037208abdf65c4a1f8e9ed06f4e7ccd907c17c668a64db54b37d95bb9e92081f8b16e4133e14102af3cb4e89870975b6ad661b4d654e9ec8f4fb5c77d6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -10070,6 +10048,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"parse-statements@npm:1.0.11":
|
||||
version: 1.0.11
|
||||
resolution: "parse-statements@npm:1.0.11"
|
||||
checksum: 10c0/48960e085019068a5f5242e875fd9d21ec87df2e291acf5ad4e4887b40eab6929a8c8d59542acb85a6497e870c5c6a24f5ab7f980ef5f907c14cc5f7984a93f3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"parse5@npm:^7.2.1":
|
||||
version: 7.2.1
|
||||
resolution: "parse5@npm:7.2.1"
|
||||
@@ -10358,27 +10343,27 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"playwright-core@npm:1.52.0":
|
||||
version: 1.52.0
|
||||
resolution: "playwright-core@npm:1.52.0"
|
||||
"playwright-core@npm:1.54.1":
|
||||
version: 1.54.1
|
||||
resolution: "playwright-core@npm:1.54.1"
|
||||
bin:
|
||||
playwright-core: cli.js
|
||||
checksum: 10c0/640945507e6ca2144e9f596b2a6ecac042c2fd3683ff99e6271e9a7b38f3602d415f282609d569456f66680aab8b3c5bb1b257d8fb63a7fc0ed648261110421f
|
||||
checksum: 10c0/b821262b024d7753b1bfa71eb2bc99f2dda12a869d175b2e1bc6ac2764bd661baf36d9d42f45caf622854ad7e4a6077b9b57014c74bb5a78fe339c9edf1c9019
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"playwright@npm:^1.52.0":
|
||||
version: 1.52.0
|
||||
resolution: "playwright@npm:1.52.0"
|
||||
"playwright@npm:^1.54.1":
|
||||
version: 1.54.1
|
||||
resolution: "playwright@npm:1.54.1"
|
||||
dependencies:
|
||||
fsevents: "npm:2.3.2"
|
||||
playwright-core: "npm:1.52.0"
|
||||
playwright-core: "npm:1.54.1"
|
||||
dependenciesMeta:
|
||||
fsevents:
|
||||
optional: true
|
||||
bin:
|
||||
playwright: cli.js
|
||||
checksum: 10c0/2c6edf1e15e59bbaf77f3fa0fe0ac975793c17cff835d9c8b8bc6395a3b6f1c01898b3058ab37891b2e4d424bcc8f1b4844fe70d943e0143d239d7451408c579
|
||||
checksum: 10c0/c5fedae31a03a1f4c4846569aef3ffb98da23000a4d255abfc8c2ede15b43cc7cd87b80f6fa078666c030373de8103787cf77ef7653ae9458aabbbd4320c2599
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -11131,22 +11116,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-hotkeys@npm:^1.1.4":
|
||||
version: 1.1.4
|
||||
resolution: "react-hotkeys@npm:1.1.4"
|
||||
dependencies:
|
||||
lodash.isboolean: "npm:^3.0.3"
|
||||
lodash.isequal: "npm:^4.5.0"
|
||||
lodash.isobject: "npm:^3.0.2"
|
||||
mousetrap: "npm:^1.5.2"
|
||||
prop-types: "npm:^15.6.0"
|
||||
peerDependencies:
|
||||
react: ">= 0.14.0"
|
||||
react-dom: ">= 0.14.0"
|
||||
checksum: 10c0/6bd566ea97e00058749d43d768ee843e5132f988571536e090b564d5dbaa71093695255514fc5b9fcf9fbd03fcb0603f6e135dcab6dcaaffe43dedbfe742a163
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-immutable-proptypes@npm:^2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "react-immutable-proptypes@npm:2.2.0"
|
||||
@@ -12092,7 +12061,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.6.3, semver@npm:^7.7.1":
|
||||
"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.7.1, semver@npm:^7.7.2":
|
||||
version: 7.7.2
|
||||
resolution: "semver@npm:7.7.2"
|
||||
bin:
|
||||
@@ -12297,13 +12266,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"slashes@npm:^3.0.12":
|
||||
version: 3.0.12
|
||||
resolution: "slashes@npm:3.0.12"
|
||||
checksum: 10c0/71ca2a1fcd1ab6814b0fdb8cf9c33a3d54321deec2aa8d173510f0086880201446021a9b9e6a18561f7c472b69a2145977c6a8fb9c53a8ff7be31778f203d175
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"slice-ansi@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "slice-ansi@npm:4.0.0"
|
||||
@@ -12991,16 +12953,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"synckit@npm:^0.9.1":
|
||||
version: 0.9.1
|
||||
resolution: "synckit@npm:0.9.1"
|
||||
dependencies:
|
||||
"@pkgr/core": "npm:^0.1.0"
|
||||
tslib: "npm:^2.6.2"
|
||||
checksum: 10c0/d8b89e1bf30ba3ffb469d8418c836ad9c0c062bf47028406b4d06548bc66af97155ea2303b96c93bf5c7c0f0d66153a6fbd6924c76521b434e6a9898982abc2e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"systemjs@npm:^6.15.1":
|
||||
version: 6.15.1
|
||||
resolution: "systemjs@npm:6.15.1"
|
||||
@@ -13341,7 +13293,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.4.0, tslib@npm:^2.6.2, tslib@npm:^2.8.0":
|
||||
"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.4.0, tslib@npm:^2.8.0":
|
||||
version: 2.8.1
|
||||
resolution: "tslib@npm:2.8.1"
|
||||
checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62
|
||||
|
||||
Reference in New Issue
Block a user