Merge commit 'aea67d448b
' into kb_migration
This commit is contained in:
commit
bb2e964dca
74 changed files with 747 additions and 870 deletions
|
@ -81,6 +81,15 @@ module.exports = {
|
|||
{ property: 'substring', message: 'Use .slice instead of .substring.' },
|
||||
{ property: 'substr', message: 'Use .slice instead of .substr.' },
|
||||
],
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
selector: 'Literal[value=/•/], JSXText[value=/•/]',
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
message: "Use '·' (middle dot) instead of '•' (bullet)",
|
||||
},
|
||||
],
|
||||
'no-self-assign': 'off',
|
||||
'no-unused-expressions': 'error',
|
||||
'no-unused-vars': 'off',
|
||||
|
|
|
@ -4,6 +4,11 @@ exclude:
|
|||
- 'vendor/**/*'
|
||||
- lib/templates/haml/scaffold/_form.html.haml
|
||||
|
||||
require:
|
||||
- ./lib/linter/haml_middle_dot.rb
|
||||
|
||||
linters:
|
||||
AltText:
|
||||
enabled: true
|
||||
MiddleDot:
|
||||
enabled: true
|
||||
|
|
|
@ -61,7 +61,7 @@ docker-compose.override.yml
|
|||
/app/javascript/mastodon/features/emoji/emoji_map.json
|
||||
|
||||
# Ignore locale files
|
||||
/app/javascript/mastodon/locales
|
||||
/app/javascript/mastodon/locales/*.json
|
||||
/config/locales
|
||||
|
||||
# Ignore vendored CSS reset
|
||||
|
|
|
@ -11,6 +11,7 @@ require:
|
|||
- rubocop-rspec
|
||||
- rubocop-performance
|
||||
- rubocop-capybara
|
||||
- ./lib/linter/rubocop_middle_dot
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 3.0 # Set to minimum supported version of CI
|
||||
|
@ -213,3 +214,6 @@ Style/TrailingCommaInArrayLiteral:
|
|||
# https://docs.rubocop.org/rubocop/cops_style.html#styletrailingcommainhashliteral
|
||||
Style/TrailingCommaInHashLiteral:
|
||||
EnforcedStyleForMultiline: 'comma'
|
||||
|
||||
Style/MiddleDot:
|
||||
Enabled: true
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -5,7 +5,7 @@ ruby '>= 3.0.0'
|
|||
|
||||
gem 'pkg-config', '~> 1.5'
|
||||
|
||||
gem 'puma', '~> 6.2'
|
||||
gem 'puma', '~> 6.3'
|
||||
gem 'rails', '~> 6.1.7'
|
||||
gem 'sprockets', '~> 3.7.2'
|
||||
gem 'thor', '~> 1.2'
|
||||
|
|
|
@ -501,7 +501,7 @@ GEM
|
|||
premailer (~> 1.7, >= 1.7.9)
|
||||
private_address_check (0.5.0)
|
||||
public_suffix (5.0.1)
|
||||
puma (6.2.2)
|
||||
puma (6.3.0)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.3.0)
|
||||
activesupport (>= 3.0.0)
|
||||
|
@ -649,7 +649,7 @@ GEM
|
|||
redis (>= 4.5.0, < 5)
|
||||
sidekiq-bulk (0.2.0)
|
||||
sidekiq
|
||||
sidekiq-scheduler (5.0.2)
|
||||
sidekiq-scheduler (5.0.3)
|
||||
rufus-scheduler (~> 3.2)
|
||||
sidekiq (>= 6, < 8)
|
||||
tilt (>= 1.4.0)
|
||||
|
@ -847,7 +847,7 @@ DEPENDENCIES
|
|||
premailer-rails
|
||||
private_address_check (~> 0.5)
|
||||
public_suffix (~> 5.0)
|
||||
puma (~> 6.2)
|
||||
puma (~> 6.3)
|
||||
pundit (~> 2.3)
|
||||
rack (~> 2.2.7)
|
||||
rack-attack (~> 6.6)
|
||||
|
|
|
@ -12,6 +12,7 @@ class Settings::ImportsController < Settings::BaseController
|
|||
muting: 'muted_accounts_failures.csv',
|
||||
domain_blocking: 'blocked_domains_failures.csv',
|
||||
bookmarks: 'bookmarks_failures.csv',
|
||||
lists: 'lists_failures.csv',
|
||||
}.freeze
|
||||
|
||||
TYPE_TO_HEADERS_MAP = {
|
||||
|
@ -20,6 +21,7 @@ class Settings::ImportsController < Settings::BaseController
|
|||
muting: ['Account address', 'Hide notifications'],
|
||||
domain_blocking: false,
|
||||
bookmarks: false,
|
||||
lists: false,
|
||||
}.freeze
|
||||
|
||||
def index
|
||||
|
@ -49,6 +51,8 @@ class Settings::ImportsController < Settings::BaseController
|
|||
csv << [row.data['domain']]
|
||||
when :bookmarks
|
||||
csv << [row.data['uri']]
|
||||
when :lists
|
||||
csv << [row.data['list_name'], row.data['acct']]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@ module ReactComponentHelper
|
|||
end
|
||||
|
||||
def react_admin_component(name, props = {})
|
||||
data = { 'admin-component': name.to_s.camelcase, props: Oj.dump({ locale: I18n.locale }.merge(props)) }
|
||||
data = { 'admin-component': name.to_s.camelcase, props: Oj.dump(props) }
|
||||
div_tag_with_data(data)
|
||||
end
|
||||
|
||||
|
|
|
@ -24,8 +24,6 @@ import {
|
|||
fillListTimelineGaps,
|
||||
} from './timelines';
|
||||
|
||||
const { messages } = getLocale();
|
||||
|
||||
/**
|
||||
* @param {number} max
|
||||
* @returns {number}
|
||||
|
@ -43,8 +41,10 @@ const randomUpTo = max =>
|
|||
* @param {function(object): boolean} [options.accept]
|
||||
* @returns {function(): void}
|
||||
*/
|
||||
export const connectTimelineStream = (timelineId, channelName, params = {}, options = {}) =>
|
||||
connectStream(channelName, params, (dispatch, getState) => {
|
||||
export const connectTimelineStream = (timelineId, channelName, params = {}, options = {}) => {
|
||||
const { messages } = getLocale();
|
||||
|
||||
return connectStream(channelName, params, (dispatch, getState) => {
|
||||
const locale = getState().getIn(['meta', 'locale']);
|
||||
|
||||
// @ts-expect-error
|
||||
|
@ -125,6 +125,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
|
|||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Function} dispatch
|
||||
|
|
|
@ -144,7 +144,7 @@ class Account extends ImmutablePureComponent {
|
|||
const firstVerifiedField = account.get('fields').find(item => !!item.get('verified_at'));
|
||||
|
||||
if (firstVerifiedField) {
|
||||
verification = <>· <VerifiedBadge link={firstVerifiedField.get('value')} /></>;
|
||||
verification = <VerifiedBadge link={firstVerifiedField.get('value')} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -155,9 +155,13 @@ class Account extends ImmutablePureComponent {
|
|||
<Avatar account={account} size={size} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className='account__contents'>
|
||||
<DisplayName account={account} />
|
||||
{!minimal && <><ShortNumber value={account.get('followers_count')} isHide={account.getIn(['other_settings', 'hide_followers_count']) || false} renderer={counterRenderer('followers')} /> {verification} {muteTimeRemaining}</>}
|
||||
{!minimal && (
|
||||
<div className='account__details'>
|
||||
<ShortNumber value={account.get('followers_count')} isHide={account.getIn(['other_settings', 'hide_followers_count']) || false} renderer={counterRenderer('followers')} /> {verification} {muteTimeRemaining}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
|
|
|
@ -57,9 +57,9 @@ class Poll extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
static getDerivedStateFromProps (props, state) {
|
||||
const { poll, intl } = props;
|
||||
const { poll } = props;
|
||||
const expires_at = poll.get('expires_at');
|
||||
const expired = poll.get('expired') || expires_at !== null && (new Date(expires_at)).getTime() < intl.now();
|
||||
const expired = poll.get('expired') || expires_at !== null && (new Date(expires_at)).getTime() < Date.now();
|
||||
return (expired === state.expired) ? null : { expired };
|
||||
}
|
||||
|
||||
|
@ -76,10 +76,10 @@ class Poll extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
_setupTimer () {
|
||||
const { poll, intl } = this.props;
|
||||
const { poll } = this.props;
|
||||
clearTimeout(this._timer);
|
||||
if (!this.state.expired) {
|
||||
const delay = (new Date(poll.get('expires_at'))).getTime() - intl.now();
|
||||
const delay = (new Date(poll.get('expires_at'))).getTime() - Date.now();
|
||||
this._timer = setTimeout(() => {
|
||||
this.setState({ expired: true });
|
||||
}, delay);
|
||||
|
|
|
@ -1,24 +1,19 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
import { getLocale, onProviderError } from '../locales';
|
||||
|
||||
const { messages } = getLocale();
|
||||
import { IntlProvider } from 'mastodon/locales';
|
||||
|
||||
export default class AdminComponent extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { locale, children } = this.props;
|
||||
const { children } = this.props;
|
||||
|
||||
return (
|
||||
<IntlProvider locale={locale} messages={messages} onError={onProviderError}>
|
||||
<IntlProvider>
|
||||
{children}
|
||||
</IntlProvider>
|
||||
);
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { fetchCustomEmojis } from '../actions/custom_emojis';
|
||||
import { hydrateStore } from '../actions/store';
|
||||
import Compose from '../features/standalone/compose';
|
||||
import initialState from '../initial_state';
|
||||
import { getLocale, onProviderError } from '../locales';
|
||||
import { IntlProvider } from '../locales';
|
||||
import { store } from '../store';
|
||||
|
||||
const { messages } = getLocale();
|
||||
|
||||
if (initialState) {
|
||||
store.dispatch(hydrateStore(initialState));
|
||||
|
@ -20,17 +16,11 @@ if (initialState) {
|
|||
|
||||
store.dispatch(fetchCustomEmojis());
|
||||
|
||||
export default class TimelineContainer extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.string.isRequired,
|
||||
};
|
||||
export default class ComposeContainer extends PureComponent {
|
||||
|
||||
render () {
|
||||
const { locale } = this.props;
|
||||
|
||||
return (
|
||||
<IntlProvider locale={locale} messages={messages} onError={onProviderError}>
|
||||
<IntlProvider>
|
||||
<Provider store={store}>
|
||||
<Compose />
|
||||
</Provider>
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { BrowserRouter, Route } from 'react-router-dom';
|
||||
|
||||
|
@ -17,11 +15,9 @@ import { connectUserStream } from 'mastodon/actions/streaming';
|
|||
import ErrorBoundary from 'mastodon/components/error_boundary';
|
||||
import UI from 'mastodon/features/ui';
|
||||
import initialState, { title as siteTitle } from 'mastodon/initial_state';
|
||||
import { getLocale, onProviderError } from 'mastodon/locales';
|
||||
import { IntlProvider } from 'mastodon/locales';
|
||||
import { store } from 'mastodon/store';
|
||||
|
||||
const { messages } = getLocale();
|
||||
|
||||
const title = process.env.NODE_ENV === 'production' ? siteTitle : `${siteTitle} (Dev)`;
|
||||
|
||||
const hydrateAction = hydrateStore(initialState);
|
||||
|
@ -42,10 +38,6 @@ const createIdentityContext = state => ({
|
|||
|
||||
export default class Mastodon extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
static childContextTypes = {
|
||||
identity: PropTypes.shape({
|
||||
signedIn: PropTypes.bool.isRequired,
|
||||
|
@ -81,10 +73,8 @@ export default class Mastodon extends PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { locale } = this.props;
|
||||
|
||||
return (
|
||||
<IntlProvider locale={locale} messages={messages} onError={onProviderError}>
|
||||
<IntlProvider>
|
||||
<ReduxProvider store={store}>
|
||||
<ErrorBoundary>
|
||||
<BrowserRouter>
|
||||
|
|
|
@ -2,8 +2,6 @@ import PropTypes from 'prop-types';
|
|||
import { PureComponent } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
import { fromJS } from 'immutable';
|
||||
|
||||
import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag';
|
||||
|
@ -14,17 +12,14 @@ import Audio from 'mastodon/features/audio';
|
|||
import Card from 'mastodon/features/status/components/card';
|
||||
import MediaModal from 'mastodon/features/ui/components/media_modal';
|
||||
import Video from 'mastodon/features/video';
|
||||
import { getLocale, onProviderError } from 'mastodon/locales';
|
||||
import { IntlProvider } from 'mastodon/locales';
|
||||
import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
|
||||
|
||||
const { messages } = getLocale();
|
||||
|
||||
const MEDIA_COMPONENTS = { MediaGallery, Video, Card, Poll, Hashtag, Audio };
|
||||
|
||||
export default class MediaContainer extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.string.isRequired,
|
||||
components: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
@ -73,7 +68,7 @@ export default class MediaContainer extends PureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { locale, components } = this.props;
|
||||
const { components } = this.props;
|
||||
|
||||
let handleOpenVideo;
|
||||
|
||||
|
@ -83,7 +78,7 @@ export default class MediaContainer extends PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<IntlProvider locale={locale} messages={messages} onError={onProviderError}>
|
||||
<IntlProvider>
|
||||
<>
|
||||
{[].map.call(components, (component, i) => {
|
||||
const componentName = component.getAttribute('data-component');
|
||||
|
|
|
@ -247,7 +247,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
} else if (this.context.router) {
|
||||
reblogLink = (
|
||||
<>
|
||||
·
|
||||
{' · '}
|
||||
<Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/reblogs`} className='detailed-status__link'>
|
||||
<Icon id={reblogIcon} />
|
||||
<span className='detailed-status__reblogs'>
|
||||
|
@ -259,7 +259,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
} else {
|
||||
reblogLink = (
|
||||
<>
|
||||
·
|
||||
{' · '}
|
||||
<a href={`/interact/${status.get('id')}?type=reblog`} className='detailed-status__link' onClick={this.handleModalLink}>
|
||||
<Icon id={reblogIcon} />
|
||||
<span className='detailed-status__reblogs'>
|
||||
|
@ -313,7 +313,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
if (status.get('edited_at')) {
|
||||
edited = (
|
||||
<>
|
||||
·
|
||||
{' · '}
|
||||
<EditedTimestamp statusId={status.get('id')} timestamp={status.get('edited_at')} />
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import { setLocale } from "./locales";
|
||||
|
||||
export async function loadLocale() {
|
||||
const locale = document.querySelector('html').lang || 'en';
|
||||
|
||||
const localeData = await import(
|
||||
/* webpackMode: "lazy" */
|
||||
/* webpackChunkName: "locale/[request]" */
|
||||
/* webpackInclude: /\.json$/ */
|
||||
/* webpackPreload: true */
|
||||
`mastodon/locales/${locale}.json`);
|
||||
|
||||
setLocale({ messages: localeData });
|
||||
}
|
22
app/javascript/mastodon/locales/global_locale.ts
Normal file
22
app/javascript/mastodon/locales/global_locale.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
export interface LocaleData {
|
||||
locale: string;
|
||||
messages: Record<string, string>;
|
||||
}
|
||||
|
||||
let loadedLocale: LocaleData;
|
||||
|
||||
export function setLocale(locale: LocaleData) {
|
||||
loadedLocale = locale;
|
||||
}
|
||||
|
||||
export function getLocale() {
|
||||
if (!loadedLocale && process.env.NODE_ENV === 'development') {
|
||||
throw new Error('getLocale() called before any locale has been set');
|
||||
}
|
||||
|
||||
return loadedLocale;
|
||||
}
|
||||
|
||||
export function isLocaleLoaded() {
|
||||
return !!loadedLocale;
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
let theLocale;
|
||||
|
||||
export function setLocale(locale) {
|
||||
theLocale = locale;
|
||||
}
|
||||
|
||||
export function getLocale() {
|
||||
return theLocale;
|
||||
}
|
||||
|
||||
export function onProviderError(error) {
|
||||
// Silent the error, like upstream does
|
||||
if(process.env.NODE_ENV === 'production') return;
|
||||
|
||||
// This browser does not advertise Intl support for this locale, we only print a warning
|
||||
// As-per the spec, the browser should select the best matching locale
|
||||
if(typeof error === "object" && error.message.match("MISSING_DATA")) {
|
||||
console.warn(error.message);
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
}
|
5
app/javascript/mastodon/locales/index.ts
Normal file
5
app/javascript/mastodon/locales/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export type { LocaleData } from './global_locale';
|
||||
export { setLocale, getLocale, isLocaleLoaded } from './global_locale';
|
||||
export { loadLocale } from './load_locale';
|
||||
|
||||
export { IntlProvider } from './intl_provider';
|
56
app/javascript/mastodon/locales/intl_provider.tsx
Normal file
56
app/javascript/mastodon/locales/intl_provider.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { IntlProvider as BaseIntlProvider } from 'react-intl';
|
||||
|
||||
import { getLocale, isLocaleLoaded } from './global_locale';
|
||||
import { loadLocale } from './load_locale';
|
||||
|
||||
function onProviderError(error: unknown) {
|
||||
// Silent the error, like upstream does
|
||||
if (process.env.NODE_ENV === 'production') return;
|
||||
|
||||
// This browser does not advertise Intl support for this locale, we only print a warning
|
||||
// As-per the spec, the browser should select the best matching locale
|
||||
if (
|
||||
error &&
|
||||
typeof error === 'object' &&
|
||||
error instanceof Error &&
|
||||
error.message.match('MISSING_DATA')
|
||||
) {
|
||||
console.warn(error.message);
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
export const IntlProvider: React.FC<
|
||||
Omit<React.ComponentProps<typeof BaseIntlProvider>, 'locale' | 'messages'>
|
||||
> = ({ children, ...props }) => {
|
||||
const [localeLoaded, setLocaleLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
async function loadLocaleData() {
|
||||
if (!isLocaleLoaded()) {
|
||||
await loadLocale();
|
||||
}
|
||||
|
||||
setLocaleLoaded(true);
|
||||
}
|
||||
void loadLocaleData();
|
||||
}, []);
|
||||
|
||||
if (!localeLoaded) return null;
|
||||
|
||||
const { locale, messages } = getLocale();
|
||||
|
||||
return (
|
||||
<BaseIntlProvider
|
||||
locale={locale}
|
||||
messages={messages}
|
||||
onError={onProviderError}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</BaseIntlProvider>
|
||||
);
|
||||
};
|
29
app/javascript/mastodon/locales/load_locale.ts
Normal file
29
app/javascript/mastodon/locales/load_locale.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { Semaphore } from 'async-mutex';
|
||||
|
||||
import type { LocaleData } from './global_locale';
|
||||
import { isLocaleLoaded, setLocale } from './global_locale';
|
||||
|
||||
const localeLoadingSemaphore = new Semaphore(1);
|
||||
|
||||
export async function loadLocale() {
|
||||
const locale = document.querySelector<HTMLElement>('html')?.lang || 'en';
|
||||
|
||||
// We use a Semaphore here so only one thing can try to load the locales at
|
||||
// the same time. If one tries to do it while its in progress, it will wait
|
||||
// for the initial load to finish before it is resumed (and will see that locale
|
||||
// data is already loaded)
|
||||
await localeLoadingSemaphore.runExclusive(async () => {
|
||||
// if the locale is already set, then do nothing
|
||||
if (isLocaleLoaded()) return;
|
||||
|
||||
const localeData = (await import(
|
||||
/* webpackMode: "lazy" */
|
||||
/* webpackChunkName: "locale/[request]" */
|
||||
/* webpackInclude: /\.json$/ */
|
||||
/* webpackPreload: true */
|
||||
`mastodon/locales/${locale}.json`
|
||||
)) as LocaleData['messages'];
|
||||
|
||||
setLocale({ messages: localeData, locale });
|
||||
});
|
||||
}
|
|
@ -1,221 +0,0 @@
|
|||
# Custom Locale Data
|
||||
|
||||
This folder is used to store custom locale data. These custom locale data are
|
||||
not yet provided by [Unicode Common Locale Data Repository](http://cldr.unicode.org/development/new-cldr-developers)
|
||||
and hence not provided in [react-intl/locale-data/*](https://github.com/yahoo/react-intl).
|
||||
|
||||
The locale data should support [Locale Data APIs](https://github.com/yahoo/react-intl/wiki/API#locale-data-apis)
|
||||
of the react-intl library.
|
||||
|
||||
It is recommended to start your custom locale data from this sample English
|
||||
locale data ([*](#plural-rules)):
|
||||
|
||||
```javascript
|
||||
/*eslint eqeqeq: "off"*/
|
||||
/*eslint no-nested-ternary: "off"*/
|
||||
|
||||
export default [
|
||||
{
|
||||
locale: "en",
|
||||
pluralRuleFunction: function(e, a) {
|
||||
var n = String(e).split("."),
|
||||
l = !n[1],
|
||||
o = Number(n[0]) == e,
|
||||
t = o && n[0].slice(-1),
|
||||
r = o && n[0].slice(-2);
|
||||
return a ? 1 == t && 11 != r ? "one" : 2 == t && 12 != r ? "two" : 3 == t && 13 != r ? "few" : "other" : 1 == e && l ? "one" : "other"
|
||||
},
|
||||
fields: {
|
||||
year: {
|
||||
displayName: "year",
|
||||
relative: {
|
||||
0: "this year",
|
||||
1: "next year",
|
||||
"-1": "last year"
|
||||
},
|
||||
relativeTime: {
|
||||
future: {
|
||||
one: "in {0} year",
|
||||
other: "in {0} years"
|
||||
},
|
||||
past: {
|
||||
one: "{0} year ago",
|
||||
other: "{0} years ago"
|
||||
}
|
||||
}
|
||||
},
|
||||
month: {
|
||||
displayName: "month",
|
||||
relative: {
|
||||
0: "this month",
|
||||
1: "next month",
|
||||
"-1": "last month"
|
||||
},
|
||||
relativeTime: {
|
||||
future: {
|
||||
one: "in {0} month",
|
||||
other: "in {0} months"
|
||||
},
|
||||
past: {
|
||||
one: "{0} month ago",
|
||||
other: "{0} months ago"
|
||||
}
|
||||
}
|
||||
},
|
||||
day: {
|
||||
displayName: "day",
|
||||
relative: {
|
||||
0: "today",
|
||||
1: "tomorrow",
|
||||
"-1": "yesterday"
|
||||
},
|
||||
relativeTime: {
|
||||
future: {
|
||||
one: "in {0} day",
|
||||
other: "in {0} days"
|
||||
},
|
||||
past: {
|
||||
one: "{0} day ago",
|
||||
other: "{0} days ago"
|
||||
}
|
||||
}
|
||||
},
|
||||
hour: {
|
||||
displayName: "hour",
|
||||
relativeTime: {
|
||||
future: {
|
||||
one: "in {0} hour",
|
||||
other: "in {0} hours"
|
||||
},
|
||||
past: {
|
||||
one: "{0} hour ago",
|
||||
other: "{0} hours ago"
|
||||
}
|
||||
}
|
||||
},
|
||||
minute: {
|
||||
displayName: "minute",
|
||||
relativeTime: {
|
||||
future: {
|
||||
one: "in {0} minute",
|
||||
other: "in {0} minutes"
|
||||
},
|
||||
past: {
|
||||
one: "{0} minute ago",
|
||||
other: "{0} minutes ago"
|
||||
}
|
||||
}
|
||||
},
|
||||
second: {
|
||||
displayName: "second",
|
||||
relative: {
|
||||
0: "now"
|
||||
},
|
||||
relativeTime: {
|
||||
future: {
|
||||
one: "in {0} second",
|
||||
other: "in {0} seconds"
|
||||
},
|
||||
past: {
|
||||
one: "{0} second ago",
|
||||
other: "{0} seconds ago"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
### Plural Rules
|
||||
|
||||
The function `pluralRuleFunction()` should return the key to proper string of
|
||||
a plural form(s). The purpose of the function is to provide key of translate
|
||||
strings of correct plural form according. The different forms are described in
|
||||
[CLDR's Plural Rules][cldr-plural-rules],
|
||||
|
||||
[cldr-plural-rules]: http://cldr.unicode.org/index/cldr-spec/plural-rules
|
||||
|
||||
#### Quick Overview on CLDR Rules
|
||||
|
||||
Let's take English as an example.
|
||||
|
||||
When you describe a number, you can be either describe it as:
|
||||
* Cardinals: 1st, 2nd, 3rd ... 11th, 12th ... 21st, 22nd, 23nd ....
|
||||
* Ordinals: 1, 2, 3 ...
|
||||
|
||||
In any of these cases, the nouns will reflect the number with singular or plural
|
||||
form. For example:
|
||||
* in 0 days
|
||||
* in 1 day
|
||||
* in 2 days
|
||||
|
||||
The `pluralRuleFunction` receives 2 parameters:
|
||||
* `e`: a string representation of the number. Such as, "`1`", "`2`", "`2.1`".
|
||||
* `a`: `true` if this is "cardinal" type of description. `false` for ordinal and other case.
|
||||
|
||||
#### How you should write `pluralRuleFunction`
|
||||
|
||||
The first rule to write pluralRuleFunction is never translate the output string
|
||||
into your language. [Plural Rules][cldr-plural-rules] specified you should use
|
||||
these as the return values:
|
||||
|
||||
* "`zero`"
|
||||
* "`one`" (singular)
|
||||
* "`two`" (dual)
|
||||
* "`few`" (paucal)
|
||||
* "`many`" (also used for fractions if they have a separate class)
|
||||
* "`other`" (required—general plural form—also used if the language only has a single form)
|
||||
|
||||
Again, we'll use English as the example here.
|
||||
|
||||
Let's read the `return` statement in the pluralRuleFunction above:
|
||||
```javascript
|
||||
return a ? 1 == t && 11 != r ? "one" : 2 == t && 12 != r ? "two" : 3 == t && 13 != r ? "few" : "other" : 1 == e && l ? "one" : "other"
|
||||
```
|
||||
|
||||
This nested ternary is hard to read. It basically means:
|
||||
```javascript
|
||||
// e: the number variable to examine
|
||||
// a: "true" if cardinals
|
||||
// l: "true" if the variable e has nothin after decimal mark (e.g. "1.0" would be false)
|
||||
// o: "true" if the variable e is an integer
|
||||
// t: the "ones" of the number. e.g. "3" for number "9123"
|
||||
// r: the "ones" and "tens" of the number. e.g. "23" for number "9123"
|
||||
if (a == true) {
|
||||
if (t == 1 && r != 11) {
|
||||
return "one"; // i.e. 1st, 21st, 101st, 121st ...
|
||||
} else if (t == 2 && r != 12) {
|
||||
return "two"; // i.e. 2nd, 22nd, 102nd, 122nd ...
|
||||
} else if (t == 3 && r != 13) {
|
||||
return "few"; // i.e. 3rd, 23rd, 103rd, 123rd ...
|
||||
} else {
|
||||
return "other"; // i.e. 4th, 11th, 12th, 24th ...
|
||||
}
|
||||
} else {
|
||||
if (e == 1 && l) {
|
||||
return "one"; // i.e. 1 day
|
||||
} else {
|
||||
return "other"; // i.e. 0 days, 2 days, 3 days
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If your language, like French, do not have complicated cardinal rules, you may
|
||||
use the French's version of it:
|
||||
```javascript
|
||||
function (e, a) {
|
||||
return a ? 1 == e ? "one" : "other" : e >= 0 && e < 2 ? "one" : "other";
|
||||
}
|
||||
```
|
||||
|
||||
If your language, like Chinese, do not have any pluralization rule at all you
|
||||
may use the Chinese's version of it:
|
||||
```javascript
|
||||
function (e, a) {
|
||||
return "other";
|
||||
}
|
||||
```
|
|
@ -1,110 +0,0 @@
|
|||
/*eslint eqeqeq: "off"*/
|
||||
/*eslint no-nested-ternary: "off"*/
|
||||
/*eslint quotes: "off"*/
|
||||
|
||||
const rules = [{
|
||||
locale: "co",
|
||||
pluralRuleFunction: function (e, a) {
|
||||
return a ? 1 == e ? "one" : "other" : e >= 0 && e < 2 ? "one" : "other";
|
||||
},
|
||||
fields: {
|
||||
year: {
|
||||
displayName: "annu",
|
||||
relative: {
|
||||
0: "quist'annu",
|
||||
1: "l'annu chì vene",
|
||||
"-1": "l'annu passatu",
|
||||
},
|
||||
relativeTime: {
|
||||
future: {
|
||||
one: "in {0} annu",
|
||||
other: "in {0} anni",
|
||||
},
|
||||
past: {
|
||||
one: "{0} annu fà",
|
||||
other: "{0} anni fà",
|
||||
},
|
||||
},
|
||||
},
|
||||
month: {
|
||||
displayName: "mese",
|
||||
relative: {
|
||||
0: "Questu mese",
|
||||
1: "u mese chì vene",
|
||||
"-1": "u mese passatu",
|
||||
},
|
||||
relativeTime: {
|
||||
future: {
|
||||
one: "in {0} mese",
|
||||
other: "in {0} mesi",
|
||||
},
|
||||
past: {
|
||||
one: "{0} mese fà",
|
||||
other: "{0} mesi fà",
|
||||
},
|
||||
},
|
||||
},
|
||||
day: {
|
||||
displayName: "ghjornu",
|
||||
relative: {
|
||||
0: "oghje",
|
||||
1: "dumane",
|
||||
"-1": "eri",
|
||||
},
|
||||
relativeTime: {
|
||||
future: {
|
||||
one: "in {0} ghjornu",
|
||||
other: "in {0} ghjornu",
|
||||
},
|
||||
past: {
|
||||
one: "{0} ghjornu fà",
|
||||
other: "{0} ghjorni fà",
|
||||
},
|
||||
},
|
||||
},
|
||||
hour: {
|
||||
displayName: "ora",
|
||||
relativeTime: {
|
||||
future: {
|
||||
one: "in {0} ora",
|
||||
other: "in {0} ore",
|
||||
},
|
||||
past: {
|
||||
one: "{0} ora fà",
|
||||
other: "{0} ore fà",
|
||||
},
|
||||
},
|
||||
},
|
||||
minute: {
|
||||
displayName: "minuta",
|
||||
relativeTime: {
|
||||
future: {
|
||||
one: "in {0} minuta",
|
||||
other: "in {0} minute",
|
||||
},
|
||||
past: {
|
||||
one: "{0} minuta fà",
|
||||
other: "{0} minute fà",
|
||||
},
|
||||
},
|
||||
},
|
||||
second: {
|
||||
displayName: "siconda",
|
||||
relative: {
|
||||
0: "avà",
|
||||
},
|
||||
relativeTime: {
|
||||
future: {
|
||||
one: "in {0} siconda",
|
||||
other: "in {0} siconde",
|
||||
},
|
||||
past: {
|
||||
one: "{0} siconda fà",
|
||||
other: "{0} siconde fà",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}];
|
||||
|
||||
export default rules;
|
|
@ -1,110 +0,0 @@
|
|||
/*eslint eqeqeq: "off"*/
|
||||
/*eslint no-nested-ternary: "off"*/
|
||||
/*eslint quotes: "off"*/
|
||||
|
||||
const rules = [{
|
||||
locale: "oc",
|
||||
pluralRuleFunction: function (e, a) {
|
||||
return a ? 1 == e ? "one" : "other" : e >= 0 && e < 2 ? "one" : "other";
|
||||
},
|
||||
fields: {
|
||||
year: {
|
||||
displayName: "an",
|
||||
relative: {
|
||||
0: "ongan",
|
||||
1: "l'an que ven",
|
||||
"-1": "l'an passat",
|
||||
},
|
||||
relativeTime: {
|
||||
future: {
|
||||
one: "d’aquí {0} an",
|
||||
other: "d’aquí {0} ans",
|
||||
},
|
||||
past: {
|
||||
one: "fa {0} an",
|
||||
other: "fa {0} ans",
|
||||
},
|
||||
},
|
||||
},
|
||||
month: {
|
||||
displayName: "mes",
|
||||
relative: {
|
||||
0: "aqueste mes",
|
||||
1: "lo mes que ven",
|
||||
"-1": "lo mes passat",
|
||||
},
|
||||
relativeTime: {
|
||||
future: {
|
||||
one: "d’aquí {0} mes",
|
||||
other: "d’aquí {0} meses",
|
||||
},
|
||||
past: {
|
||||
one: "fa {0} mes",
|
||||
other: "fa {0} meses",
|
||||
},
|
||||
},
|
||||
},
|
||||
day: {
|
||||
displayName: "jorn",
|
||||
relative: {
|
||||
0: "uèi",
|
||||
1: "deman",
|
||||
"-1": "ièr",
|
||||
},
|
||||
relativeTime: {
|
||||
future: {
|
||||
one: "d’aquí {0} jorn",
|
||||
other: "d’aquí {0} jorns",
|
||||
},
|
||||
past: {
|
||||
one: "fa {0} jorn",
|
||||
other: "fa {0} jorns",
|
||||
},
|
||||
},
|
||||
},
|
||||
hour: {
|
||||
displayName: "ora",
|
||||
relativeTime: {
|
||||
future: {
|
||||
one: "d’aquí {0} ora",
|
||||
other: "d’aquí {0} oras",
|
||||
},
|
||||
past: {
|
||||
one: "fa {0} ora",
|
||||
other: "fa {0} oras",
|
||||
},
|
||||
},
|
||||
},
|
||||
minute: {
|
||||
displayName: "minuta",
|
||||
relativeTime: {
|
||||
future: {
|
||||
one: "d’aquí {0} minuta",
|
||||
other: "d’aquí {0} minutas",
|
||||
},
|
||||
past: {
|
||||
one: "fa {0} minuta",
|
||||
other: "fa {0} minutas",
|
||||
},
|
||||
},
|
||||
},
|
||||
second: {
|
||||
displayName: "segonda",
|
||||
relative: {
|
||||
0: "ara",
|
||||
},
|
||||
relativeTime: {
|
||||
future: {
|
||||
one: "d’aquí {0} segonda",
|
||||
other: "d’aquí {0} segondas",
|
||||
},
|
||||
past: {
|
||||
one: "fa {0} segonda",
|
||||
other: "fa {0} segondas",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}];
|
||||
|
||||
export default rules;
|
|
@ -1,98 +0,0 @@
|
|||
/*eslint eqeqeq: "off"*/
|
||||
/*eslint no-nested-ternary: "off"*/
|
||||
/*eslint quotes: "off"*/
|
||||
/*eslint comma-dangle: "off"*/
|
||||
|
||||
const rules = [
|
||||
{
|
||||
locale: "sa",
|
||||
fields: {
|
||||
year: {
|
||||
displayName: "year",
|
||||
relative: {
|
||||
0: "this year",
|
||||
1: "next year",
|
||||
"-1": "last year"
|
||||
},
|
||||
relativeTime: {
|
||||
future: {
|
||||
other: "+{0} y"
|
||||
},
|
||||
past: {
|
||||
other: "-{0} y"
|
||||
}
|
||||
}
|
||||
},
|
||||
month: {
|
||||
displayName: "month",
|
||||
relative: {
|
||||
0: "this month",
|
||||
1: "next month",
|
||||
"-1": "last month"
|
||||
},
|
||||
relativeTime: {
|
||||
future: {
|
||||
other: "+{0} m"
|
||||
},
|
||||
past: {
|
||||
other: "-{0} m"
|
||||
}
|
||||
}
|
||||
},
|
||||
day: {
|
||||
displayName: "day",
|
||||
relative: {
|
||||
0: "अद्य",
|
||||
1: "श्वः",
|
||||
"-1": "गतदिनम्"
|
||||
},
|
||||
relativeTime: {
|
||||
future: {
|
||||
other: "+{0} d"
|
||||
},
|
||||
past: {
|
||||
other: "-{0} d"
|
||||
}
|
||||
}
|
||||
},
|
||||
hour: {
|
||||
displayName: "hour",
|
||||
relativeTime: {
|
||||
future: {
|
||||
other: "+{0} h"
|
||||
},
|
||||
past: {
|
||||
other: "-{0} h"
|
||||
}
|
||||
}
|
||||
},
|
||||
minute: {
|
||||
displayName: "minute",
|
||||
relativeTime: {
|
||||
future: {
|
||||
other: "+{0} min"
|
||||
},
|
||||
past: {
|
||||
other: "-{0} min"
|
||||
}
|
||||
}
|
||||
},
|
||||
second: {
|
||||
displayName: "second",
|
||||
relative: {
|
||||
0: "now"
|
||||
},
|
||||
relativeTime: {
|
||||
future: {
|
||||
other: "+{0} s"
|
||||
},
|
||||
past: {
|
||||
other: "-{0} s"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export default rules;
|
|
@ -239,14 +239,14 @@ ready(() => {
|
|||
|
||||
[].forEach.call(document.querySelectorAll('[data-admin-component]'), element => {
|
||||
const componentName = element.getAttribute('data-admin-component');
|
||||
const { locale, ...componentProps } = JSON.parse(element.getAttribute('data-props'));
|
||||
const componentProps = JSON.parse(element.getAttribute('data-props'));
|
||||
|
||||
import('../mastodon/containers/admin_component').then(({ default: AdminComponent }) => {
|
||||
return import('../mastodon/components/admin/' + componentName).then(({ default: Component }) => {
|
||||
const root = createRoot(element);
|
||||
|
||||
root.render (
|
||||
<AdminComponent locale={locale}>
|
||||
<AdminComponent>
|
||||
<Component {...componentProps} />
|
||||
</AdminComponent>,
|
||||
);
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import './public-path';
|
||||
import main from "mastodon/main"
|
||||
|
||||
import { start } from '../mastodon/common';
|
||||
import { loadLocale } from '../mastodon/load_locale';
|
||||
import { loadLocale } from '../mastodon/locales';
|
||||
import { loadPolyfills } from '../mastodon/polyfills';
|
||||
|
||||
start();
|
||||
|
||||
loadPolyfills().then(loadLocale).then(async () => {
|
||||
const { default: main } = await import('mastodon/main');
|
||||
|
||||
return main();
|
||||
}).catch(e => {
|
||||
console.error(e);
|
||||
});
|
||||
loadPolyfills()
|
||||
.then(loadLocale)
|
||||
.then(main)
|
||||
.catch(e => {
|
||||
console.error(e);
|
||||
});
|
||||
|
|
|
@ -15,8 +15,7 @@ import { start } from '../mastodon/common';
|
|||
import { timeAgoString } from '../mastodon/components/relative_timestamp';
|
||||
import emojify from '../mastodon/features/emoji/emoji';
|
||||
import loadKeyboardExtensions from '../mastodon/load_keyboard_extensions';
|
||||
import { loadLocale } from '../mastodon/load_locale';
|
||||
import { getLocale } from '../mastodon/locales';
|
||||
import { loadLocale, getLocale } from '../mastodon/locales';
|
||||
import { loadPolyfills } from '../mastodon/polyfills';
|
||||
import ready from '../mastodon/ready';
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ import { createRoot } from 'react-dom/client';
|
|||
|
||||
import { start } from '../mastodon/common';
|
||||
import ComposeContainer from '../mastodon/containers/compose_container';
|
||||
import { loadLocale } from '../mastodon/load_locale';
|
||||
import { loadPolyfills } from '../mastodon/polyfills';
|
||||
import ready from '../mastodon/ready';
|
||||
|
||||
|
@ -26,6 +25,6 @@ function main() {
|
|||
ready(loaded);
|
||||
}
|
||||
|
||||
loadPolyfills().then(loadLocale).then(main).catch(error => {
|
||||
loadPolyfills().then(main).catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
|
|
|
@ -3,11 +3,8 @@
|
|||
display: block;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
|
||||
|
||||
@media screen and (max-width: $no-gap-breakpoint) {
|
||||
box-shadow: none;
|
||||
}
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
|
@ -22,7 +19,6 @@
|
|||
height: 130px;
|
||||
position: relative;
|
||||
background: darken($ui-base-color, 12%);
|
||||
border-radius: 4px 4px 0 0;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
|
@ -30,7 +26,6 @@
|
|||
height: 100%;
|
||||
margin: 0;
|
||||
object-fit: cover;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
@media screen and (width <= 600px) {
|
||||
|
@ -45,11 +40,6 @@
|
|||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
background: lighten($ui-base-color, 4%);
|
||||
border-radius: 0 0 4px 4px;
|
||||
|
||||
@media screen and (max-width: $no-gap-breakpoint) {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
flex: 0 0 auto;
|
||||
|
|
|
@ -7949,13 +7949,28 @@ noscript {
|
|||
}
|
||||
}
|
||||
|
||||
.account__contents {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.account__details {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 1em;
|
||||
}
|
||||
|
||||
.verified-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: $valid-value-color;
|
||||
gap: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
> span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
|
|
|
@ -137,6 +137,10 @@ code {
|
|||
color: $secondary-text-color;
|
||||
margin-bottom: 30px;
|
||||
|
||||
&.invited-by {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $highlight-text-color;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ class BulkImport < ApplicationRecord
|
|||
muting: 2,
|
||||
domain_blocking: 3,
|
||||
bookmarks: 4,
|
||||
lists: 5,
|
||||
}
|
||||
|
||||
enum state: {
|
||||
|
|
|
@ -18,6 +18,7 @@ class Form::Import
|
|||
muting: ['Account address', 'Hide notifications'],
|
||||
domain_blocking: ['#domain'],
|
||||
bookmarks: ['#uri'],
|
||||
lists: ['List name', 'Account address'],
|
||||
}.freeze
|
||||
|
||||
KNOWN_FIRST_HEADERS = EXPECTED_HEADERS_BY_TYPE.values.map(&:first).uniq.freeze
|
||||
|
@ -30,6 +31,7 @@ class Form::Import
|
|||
'Hide notifications' => 'hide_notifications',
|
||||
'#domain' => 'domain',
|
||||
'#uri' => 'uri',
|
||||
'List name' => 'list_name',
|
||||
}.freeze
|
||||
|
||||
class EmptyFileError < StandardError; end
|
||||
|
@ -48,6 +50,7 @@ class Form::Import
|
|||
return :muting if data.original_filename&.start_with?('mutes') || data.original_filename&.start_with?('muted_accounts')
|
||||
return :domain_blocking if data.original_filename&.start_with?('domain_blocks') || data.original_filename&.start_with?('blocked_domains')
|
||||
return :bookmarks if data.original_filename&.start_with?('bookmarks')
|
||||
return :lists if data.original_filename&.start_with?('lists')
|
||||
end
|
||||
|
||||
# Whether the uploaded CSV file seems to correspond to a different import type than the one selected
|
||||
|
@ -76,14 +79,16 @@ class Form::Import
|
|||
|
||||
private
|
||||
|
||||
def default_csv_header
|
||||
def default_csv_headers
|
||||
case type.to_sym
|
||||
when :following, :blocking, :muting
|
||||
'Account address'
|
||||
['Account address']
|
||||
when :domain_blocking
|
||||
'#domain'
|
||||
['#domain']
|
||||
when :bookmarks
|
||||
'#uri'
|
||||
['#uri']
|
||||
when :lists
|
||||
['List name', 'Account address']
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -98,7 +103,7 @@ class Form::Import
|
|||
field&.split(',')&.map(&:strip)&.presence
|
||||
when 'Account address'
|
||||
field.strip.gsub(/\A@/, '')
|
||||
when '#domain', '#uri'
|
||||
when '#domain', '#uri', 'List name'
|
||||
field.strip
|
||||
else
|
||||
field
|
||||
|
@ -109,7 +114,7 @@ class Form::Import
|
|||
@csv_data.take(1) # Ensure the headers are read
|
||||
raise EmptyFileError if @csv_data.headers == true
|
||||
|
||||
@csv_data = CSV.open(data.path, encoding: 'UTF-8', skip_blanks: true, headers: [default_csv_header], converters: csv_converter) unless KNOWN_FIRST_HEADERS.include?(@csv_data.headers&.first)
|
||||
@csv_data = CSV.open(data.path, encoding: 'UTF-8', skip_blanks: true, headers: default_csv_headers, converters: csv_converter) unless KNOWN_FIRST_HEADERS.include?(@csv_data.headers&.first)
|
||||
@csv_data
|
||||
end
|
||||
|
||||
|
@ -133,7 +138,7 @@ class Form::Import
|
|||
def validate_data
|
||||
return if data.nil?
|
||||
return errors.add(:data, I18n.t('imports.errors.too_large')) if data.size > FILE_SIZE_LIMIT
|
||||
return errors.add(:data, I18n.t('imports.errors.incompatible_type')) unless csv_data.headers.include?(default_csv_header)
|
||||
return errors.add(:data, I18n.t('imports.errors.incompatible_type')) unless default_csv_headers.all? { |header| csv_data.headers.include?(header) }
|
||||
|
||||
errors.add(:data, I18n.t('imports.errors.over_rows_processing_limit', count: ROWS_PROCESSING_LIMIT)) if csv_row_count > ROWS_PROCESSING_LIMIT
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ class BulkImportRowService
|
|||
@type = row.bulk_import.type.to_sym
|
||||
|
||||
case @type
|
||||
when :following, :blocking, :muting
|
||||
when :following, :blocking, :muting, :lists
|
||||
target_acct = @data['acct']
|
||||
target_domain = domain(target_acct)
|
||||
@target_account = stoplight_wrap_request(target_domain) { ResolveAccountService.new.call(target_acct, { check_delivery_availability: true }) }
|
||||
|
@ -33,6 +33,12 @@ class BulkImportRowService
|
|||
return false unless StatusPolicy.new(@account, @target_status).show?
|
||||
|
||||
@account.bookmarks.find_or_create_by!(status: @target_status)
|
||||
when :lists
|
||||
list = @account.owned_lists.find_or_create_by!(title: @data['list_name'])
|
||||
|
||||
FollowService.new.call(@account, @target_account) unless @account.id == @target_account.id
|
||||
|
||||
list.accounts << @target_account
|
||||
end
|
||||
|
||||
true
|
||||
|
|
|
@ -16,6 +16,8 @@ class BulkImportService < BaseService
|
|||
import_domain_blocks!
|
||||
when :bookmarks
|
||||
import_bookmarks!
|
||||
when :lists
|
||||
import_lists!
|
||||
end
|
||||
|
||||
@import.update!(state: :finished, finished_at: Time.now.utc) if @import.processed_items == @import.total_items
|
||||
|
@ -157,4 +159,24 @@ class BulkImportService < BaseService
|
|||
[row.id]
|
||||
end
|
||||
end
|
||||
|
||||
def import_lists!
|
||||
rows = @import.rows.to_a
|
||||
|
||||
if @import.overwrite?
|
||||
included_lists = rows.map { |row| row.data['list_name'] }.uniq
|
||||
|
||||
@account.owned_lists.where.not(title: included_lists).destroy_all
|
||||
|
||||
# As list membership changes do not retroactively change timeline
|
||||
# contents, simplify things by just clearing everything
|
||||
@account.owned_lists.find_each do |list|
|
||||
list.list_accounts.destroy_all
|
||||
end
|
||||
end
|
||||
|
||||
Import::RowWorker.push_bulk(rows) do |row|
|
||||
[row.id]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,6 +9,6 @@
|
|||
|
||||
- if email_domain_block.parent.present?
|
||||
= t('admin.email_domain_blocks.resolved_through_html', domain: content_tag(:samp, email_domain_block.parent.domain))
|
||||
•
|
||||
·
|
||||
|
||||
= t('admin.email_domain_blocks.attempts_over_week', count: email_domain_block.history.reduce(0) { |sum, day| sum + day.accounts })
|
||||
|
|
|
@ -29,11 +29,11 @@
|
|||
|
||||
%br/
|
||||
|
||||
= f.object.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' • ')
|
||||
= f.object.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' · ')
|
||||
- if f.object.public_comment.present?
|
||||
•
|
||||
·
|
||||
= f.object.public_comment
|
||||
- if existing_relationships
|
||||
•
|
||||
·
|
||||
= fa_icon 'warning fw'
|
||||
= t('admin.export_domain_blocks.import.existing_relationships_warning')
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
%small
|
||||
- if instance.domain_block
|
||||
= instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' • ')
|
||||
= instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' · ')
|
||||
- elsif instance.domain_allow
|
||||
= t('admin.accounts.whitelisted')
|
||||
- else
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
%td= @instance.domain_block.public_comment
|
||||
%tr
|
||||
%th= t('admin.instances.content_policies.policy')
|
||||
%td= @instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' • ')
|
||||
%td= @instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' · ')
|
||||
|
||||
= link_to t('admin.domain_blocks.edit'), edit_admin_domain_block_path(@instance.domain_block), class: 'button'
|
||||
= link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@instance.domain_block), class: 'button', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
.pending-account__header
|
||||
%samp= link_to "#{ip_block.ip}/#{ip_block.ip.prefix}", admin_accounts_path(ip: "#{ip_block.ip}/#{ip_block.ip.prefix}")
|
||||
- if ip_block.comment.present?
|
||||
•
|
||||
·
|
||||
= ip_block.comment
|
||||
%br/
|
||||
= t("simple_form.labels.ip_block.severities.#{ip_block.severity}")
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
= t('admin.roles.everyone_full_description_html')
|
||||
- else
|
||||
= link_to t('admin.roles.assigned_users', count: role.users.count), admin_accounts_path(role_ids: role.id)
|
||||
•
|
||||
·
|
||||
%abbr{ title: role.permissions_as_keys.map { |privilege| I18n.t("admin.roles.privileges.#{privilege}") }.join(', ') }= t('admin.roles.permissions_count', count: role.permissions_as_keys.size)
|
||||
%div
|
||||
= table_link_to 'pencil', t('admin.accounts.edit'), edit_admin_role_path(role) if can?(:update, role)
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
.fields-group
|
||||
= f.input :media_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }
|
||||
= f.input :content_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }
|
||||
= f.input :content_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }, hint: false, warning_hint: t('simple_form.hints.form_admin_settings.content_cache_retention_period')
|
||||
= f.input :backups_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }
|
||||
|
||||
.actions
|
||||
|
|
|
@ -10,21 +10,21 @@
|
|||
|
||||
- if preview_card.provider_name.present?
|
||||
= preview_card.provider_name
|
||||
•
|
||||
·
|
||||
|
||||
- if preview_card.language.present?
|
||||
= standard_locale_name(preview_card.language)
|
||||
•
|
||||
·
|
||||
|
||||
= t('admin.trends.links.shared_by_over_week', count: preview_card.history.reduce(0) { |sum, day| sum + day.accounts })
|
||||
|
||||
- if preview_card.trend.allowed?
|
||||
•
|
||||
·
|
||||
%abbr{ title: t('admin.trends.tags.current_score', score: preview_card.trend.score) }= t('admin.trends.tags.trending_rank', rank: preview_card.trend.rank)
|
||||
|
||||
- if preview_card.decaying?
|
||||
•
|
||||
·
|
||||
= t('admin.trends.tags.peaked_on_and_decaying', date: l(preview_card.max_score_at.to_date, format: :short))
|
||||
- elsif preview_card.requires_review?
|
||||
•
|
||||
·
|
||||
= t('admin.trends.pending_review')
|
||||
|
|
|
@ -17,17 +17,17 @@
|
|||
= t('admin.trends.statuses.shared_by', count: status.reblogs_count + status.favourites_count, friendly_count: friendly_number_to_human(status.reblogs_count + status.favourites_count))
|
||||
|
||||
- if status.account.domain.present?
|
||||
•
|
||||
·
|
||||
= status.account.domain
|
||||
- if status.language.present?
|
||||
•
|
||||
·
|
||||
= standard_locale_name(status.language)
|
||||
- if status.trendable? && !status.account.discoverable?
|
||||
•
|
||||
·
|
||||
= t('admin.trends.statuses.not_discoverable')
|
||||
- if status.trend.allowed?
|
||||
•
|
||||
·
|
||||
%abbr{ title: t('admin.trends.tags.current_score', score: status.trend.score) }= t('admin.trends.tags.trending_rank', rank: status.trend.rank)
|
||||
- elsif status.requires_review?
|
||||
•
|
||||
·
|
||||
= t('admin.trends.pending_review')
|
||||
|
|
|
@ -13,12 +13,12 @@
|
|||
= t('admin.trends.tags.used_by_over_week', count: tag.history.reduce(0) { |sum, day| sum + day.accounts })
|
||||
|
||||
- if tag.trendable? && (rank = Trends.tags.rank(tag.id))
|
||||
•
|
||||
·
|
||||
%abbr{ title: t('admin.trends.tags.current_score', score: Trends.tags.score(tag.id)) }= t('admin.trends.tags.trending_rank', rank: rank + 1)
|
||||
|
||||
- if tag.decaying?
|
||||
•
|
||||
·
|
||||
= t('admin.trends.tags.peaked_on_and_decaying', date: l(tag.max_score_at.to_date, format: :short))
|
||||
- elsif tag.requires_review?
|
||||
•
|
||||
·
|
||||
= t('admin.trends.pending_review')
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
- else
|
||||
%span.negative-hint= t('admin.webhooks.disabled')
|
||||
|
||||
•
|
||||
·
|
||||
|
||||
%abbr{ title: webhook.events.join(', ') }= t('admin.webhooks.enabled_events', count: webhook.events.size)
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<%= raw t('admin_mailer.new_trends.new_trending_links.title') %>
|
||||
|
||||
<% @links.each do |link| %>
|
||||
- <%= link.title %> • <%= link.url %>
|
||||
<%= standard_locale_name(link.language) %> • <%= raw t('admin.trends.links.usage_comparison', today: link.history.get(Time.now.utc).accounts, yesterday: link.history.get(Time.now.utc - 1.day).accounts) %> • <%= t('admin.trends.tags.current_score', score: link.trend.score.round(2)) %>
|
||||
- <%= link.title %> · <%= link.url %>
|
||||
<%= standard_locale_name(link.language) %> · <%= raw t('admin.trends.links.usage_comparison', today: link.history.get(Time.now.utc).accounts, yesterday: link.history.get(Time.now.utc - 1.day).accounts) %> · <%= t('admin.trends.tags.current_score', score: link.trend.score.round(2)) %>
|
||||
<% end %>
|
||||
|
||||
<%= raw t('application_mailer.view')%> <%= admin_trends_links_url %>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<% @statuses.each do |status| %>
|
||||
- <%= ActivityPub::TagManager.instance.url_for(status) %>
|
||||
<%= standard_locale_name(status.language) %> • <%= raw t('admin.trends.tags.current_score', score: status.trend.score.round(2)) %>
|
||||
<%= standard_locale_name(status.language) %> · <%= raw t('admin.trends.tags.current_score', score: status.trend.score.round(2)) %>
|
||||
<% end %>
|
||||
|
||||
<%= raw t('application_mailer.view')%> <%= admin_trends_statuses_url %>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<% @tags.each do |tag| %>
|
||||
- #<%= tag.display_name %>
|
||||
<%= raw t('admin.trends.tags.usage_comparison', today: tag.history.get(Time.now.utc).accounts, yesterday: tag.history.get(Time.now.utc - 1.day).accounts) %> • <%= t('admin.trends.tags.current_score', score: Trends.tags.score(tag.id).round(2)) %>
|
||||
<%= raw t('admin.trends.tags.usage_comparison', today: tag.history.get(Time.now.utc).accounts, yesterday: tag.history.get(Time.now.utc - 1.day).accounts) %> · <%= t('admin.trends.tags.current_score', score: Trends.tags.score(tag.id).round(2)) %>
|
||||
<% end %>
|
||||
|
||||
<% if @lowest_trending_tag %>
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
- account_url = local_assigns[:admin] ? admin_account_path(account.id) : ActivityPub::TagManager.instance.url_for(account)
|
||||
- compact ||= false
|
||||
|
||||
.card.h-card
|
||||
= link_to account_url, target: '_blank', rel: 'noopener noreferrer' do
|
||||
.card__img
|
||||
= image_tag account.header.url, alt: ''
|
||||
- unless compact
|
||||
.card__img
|
||||
= image_tag account.header.url, alt: ''
|
||||
.card__bar
|
||||
.avatar
|
||||
= image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo'
|
||||
|
|
|
@ -7,8 +7,14 @@
|
|||
.simple_form
|
||||
= render 'auth/shared/progress', stage: 'rules'
|
||||
|
||||
%h1.title= t('auth.rules.title')
|
||||
%p.lead= t('auth.rules.preamble', domain: site_hostname)
|
||||
- if @invite.present? && @invite.autofollow?
|
||||
%h1.title= t('auth.rules.title_invited')
|
||||
%p.lead.invited-by= t('auth.rules.invited_by', domain: site_hostname)
|
||||
= render 'application/card', account: @invite.user.account, compact: true
|
||||
%p.lead= t('auth.rules.preamble_invited', domain: site_hostname)
|
||||
- else
|
||||
%h1.title= t('auth.rules.title')
|
||||
%p.lead= t('auth.rules.preamble', domain: site_hostname)
|
||||
|
||||
%ol.rules-list
|
||||
- @rules.each do |rule|
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
- else
|
||||
= t('doorkeeper.authorized_applications.index.never_used')
|
||||
|
||||
•
|
||||
·
|
||||
|
||||
= t('doorkeeper.authorized_applications.index.authorized_at', date: l(application.created_at.to_date))
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
= simple_form_for @import, url: settings_imports_path do |f|
|
||||
.field-group
|
||||
= f.input :type, as: :grouped_select, collection: { constructive: %i(following bookmarks), destructive: %i(muting blocking domain_blocking) }, wrapper: :with_block_label, include_blank: false, label_method: ->(type) { I18n.t("imports.types.#{type}") }, group_label_method: ->(group) { I18n.t("imports.type_groups.#{group.first}") }, group_method: :last, hint: t('imports.preface')
|
||||
= f.input :type, as: :grouped_select, collection: { constructive: %i(following bookmarks lists), destructive: %i(muting blocking domain_blocking) }, wrapper: :with_block_label, include_blank: false, label_method: ->(type) { I18n.t("imports.types.#{type}") }, group_label_method: ->(group) { I18n.t("imports.type_groups.#{group.first}") }, group_method: :last, hint: t('imports.preface')
|
||||
|
||||
.fields-row
|
||||
.fields-group.fields-row__column.fields-row__column-6
|
||||
|
|
|
@ -30,9 +30,18 @@ module KmyblueComponent
|
|||
end
|
||||
end
|
||||
|
||||
module WarningHintComponent
|
||||
def warning_hint(_wrapper_options = nil)
|
||||
@warning_hint ||= begin
|
||||
options[:warning_hint].to_s.html_safe if options[:warning_hint].present?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
SimpleForm.include_component(AppendComponent)
|
||||
SimpleForm.include_component(RecommendedComponent)
|
||||
SimpleForm.include_component(KmyblueComponent)
|
||||
SimpleForm.include_component(WarningHintComponent)
|
||||
|
||||
SimpleForm.setup do |config|
|
||||
# Wrappers are used by the form builder to generate a
|
||||
|
@ -114,6 +123,7 @@ SimpleForm.setup do |config|
|
|||
b.use :html5
|
||||
b.use :label
|
||||
b.use :hint, wrap_with: { tag: :span, class: :hint }
|
||||
b.use :warning_hint, wrap_with: { tag: :span, class: [:hint, 'warning-hint'] }
|
||||
b.use :input, wrap_with: { tag: :div, class: :label_input }
|
||||
b.use :error, wrap_with: { tag: :span, class: :error }
|
||||
end
|
||||
|
|
|
@ -25,7 +25,7 @@ module Twitter::TwitterText
|
|||
\)
|
||||
/iox
|
||||
UCHARS = '\u{A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}\u{10000}-\u{1FFFD}\u{20000}-\u{2FFFD}\u{30000}-\u{3FFFD}\u{40000}-\u{4FFFD}\u{50000}-\u{5FFFD}\u{60000}-\u{6FFFD}\u{70000}-\u{7FFFD}\u{80000}-\u{8FFFD}\u{90000}-\u{9FFFD}\u{A0000}-\u{AFFFD}\u{B0000}-\u{BFFFD}\u{C0000}-\u{CFFFD}\u{D0000}-\u{DFFFD}\u{E1000}-\u{EFFFD}\u{E000}-\u{F8FF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}'
|
||||
REGEXEN[:valid_url_query_chars] = /[a-z0-9!?\*'\(\);:&=\+\$\/%#\[\]\-_\.,~|@#{UCHARS}]/iou
|
||||
REGEXEN[:valid_url_query_chars] = /[a-z0-9!?\*'\(\);:&=\+\$\/%#\[\]\-_\.,~|@\^#{UCHARS}]/iou
|
||||
REGEXEN[:valid_url_query_ending_chars] = /[a-z0-9_&=#\/\-#{UCHARS}]/iou
|
||||
REGEXEN[:valid_url_path] = /(?:
|
||||
(?:
|
||||
|
|
|
@ -1126,8 +1126,11 @@ en:
|
|||
rules:
|
||||
accept: Accept
|
||||
back: Back
|
||||
invited_by: 'You can join %{domain} thanks to the invitation you have received from:'
|
||||
preamble: These are set and enforced by the %{domain} moderators.
|
||||
preamble_invited: Before you proceed, please consider the ground rules set by the moderators of %{domain}.
|
||||
title: Some ground rules.
|
||||
title_invited: You've been invited.
|
||||
security: Security
|
||||
set_new_password: Set new password
|
||||
setup:
|
||||
|
|
|
@ -82,7 +82,7 @@ en:
|
|||
backups_retention_period: Keep generated user archives for the specified number of days.
|
||||
bootstrap_timeline_accounts: These accounts will be pinned to the top of new users' follow recommendations.
|
||||
closed_registrations_message: Displayed when sign-ups are closed
|
||||
content_cache_retention_period: Posts from other servers will be deleted after the specified number of days when set to a positive value. This may be irreversible.
|
||||
content_cache_retention_period: All posts and boosts from other servers will be deleted after the specified number of days. Some posts may not be recoverable. All related bookmarks, favourites and boosts will also be lost and impossible to undo.
|
||||
custom_css: You can apply custom styles on the web version of Mastodon.
|
||||
mascot: Overrides the illustration in the advanced web interface.
|
||||
media_cache_retention_period: Downloaded media files will be deleted after the specified number of days when set to a positive value, and re-downloaded on demand.
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
console.error("The localisation functionality has been refactored, please see the Localisation section in the development documentation (https://docs.joinmastodon.org/dev/code/#localizations)");
|
||||
|
||||
process.exit(1);
|
|
@ -13,7 +13,6 @@ const config = {
|
|||
collectCoverageFrom: [
|
||||
'app/javascript/mastodon/**/*.{js,jsx,ts,tsx}',
|
||||
'!app/javascript/mastodon/features/emoji/emoji_compressed.js',
|
||||
'!app/javascript/mastodon/locales/locale-data/*.js',
|
||||
'!app/javascript/mastodon/service_worker/entry.js',
|
||||
'!app/javascript/mastodon/test_setup.js',
|
||||
],
|
||||
|
|
26
lib/linter/haml_middle_dot.rb
Normal file
26
lib/linter/haml_middle_dot.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module HamlLint
|
||||
# Bans the usage of “•” (bullet) in HTML/HAML in favor of “·” (middle dot) in anything that will end up as a text node. (including string literals in Ruby code)
|
||||
class Linter::MiddleDot < Linter
|
||||
include LinterRegistry
|
||||
|
||||
# rubocop:disable Style/MiddleDot
|
||||
BULLET = '•'
|
||||
# rubocop:enable Style/MiddleDot
|
||||
MIDDLE_DOT = '·'
|
||||
MESSAGE = "Use '#{MIDDLE_DOT}' (middle dot) instead of '#{BULLET}' (bullet)".freeze
|
||||
|
||||
def visit_plain(node)
|
||||
return unless node.text.include?(BULLET)
|
||||
|
||||
record_lint(node, MESSAGE)
|
||||
end
|
||||
|
||||
def visit_script(node)
|
||||
return unless node.script.include?(BULLET)
|
||||
|
||||
record_lint(node, MESSAGE)
|
||||
end
|
||||
end
|
||||
end
|
31
lib/linter/rubocop_middle_dot.rb
Normal file
31
lib/linter/rubocop_middle_dot.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Style
|
||||
# Bans the usage of “•” (bullet) in HTML/HAML in favor of “·” (middle dot) in string literals
|
||||
class MiddleDot < Base
|
||||
extend AutoCorrector
|
||||
extend Util
|
||||
|
||||
# rubocop:disable Style/MiddleDot
|
||||
BULLET = '•'
|
||||
# rubocop:enable Style/MiddleDot
|
||||
MIDDLE_DOT = '·'
|
||||
MESSAGE = "Use '#{MIDDLE_DOT}' (middle dot) instead of '#{BULLET}' (bullet)".freeze
|
||||
|
||||
def on_str(node)
|
||||
# Constants like __FILE__ are handled as strings,
|
||||
# but don't respond to begin.
|
||||
return unless node.loc.respond_to?(:begin) && node.loc.begin
|
||||
|
||||
return unless node.value.include?(BULLET)
|
||||
|
||||
add_offense(node, message: MESSAGE) do |corrector|
|
||||
corrector.replace(node, node.source.gsub(BULLET, MIDDLE_DOT))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,16 +4,39 @@ require_relative '../../../config/boot'
|
|||
require_relative '../../../config/environment'
|
||||
|
||||
require 'thor'
|
||||
require_relative 'helper'
|
||||
require_relative 'progress_helper'
|
||||
|
||||
module Mastodon
|
||||
module CLI
|
||||
class Base < Thor
|
||||
include CLI::Helper
|
||||
include ProgressHelper
|
||||
|
||||
def self.exit_on_failure?
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def pastel
|
||||
@pastel ||= Pastel.new
|
||||
end
|
||||
|
||||
def dry_run?
|
||||
options[:dry_run]
|
||||
end
|
||||
|
||||
def dry_run_mode_suffix
|
||||
dry_run? ? ' (DRY RUN)' : ''
|
||||
end
|
||||
|
||||
def reset_connection_pools!
|
||||
ActiveRecord::Base.establish_connection(
|
||||
ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).first.configuration_hash
|
||||
.dup
|
||||
.tap { |config| config['pool'] = options[:concurrency] + 1 }
|
||||
)
|
||||
RedisConfiguration.establish_pool(options[:concurrency])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,23 +9,19 @@ HttpLog.configuration.logger = dev_null
|
|||
Paperclip.options[:log] = false
|
||||
Chewy.logger = dev_null
|
||||
|
||||
module Mastodon::CLI
|
||||
module Helper
|
||||
def dry_run?
|
||||
options[:dry_run]
|
||||
end
|
||||
require 'ruby-progressbar/outputs/null'
|
||||
|
||||
def dry_run_mode_suffix
|
||||
dry_run? ? ' (DRY RUN)' : ''
|
||||
end
|
||||
module Mastodon::CLI
|
||||
module ProgressHelper
|
||||
PROGRESS_FORMAT = '%c/%u |%b%i| %e'
|
||||
|
||||
def create_progress_bar(total = nil)
|
||||
ProgressBar.create(total: total, format: '%c/%u |%b%i| %e')
|
||||
end
|
||||
|
||||
def reset_connection_pools!
|
||||
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[Rails.env].dup.tap { |config| config['pool'] = options[:concurrency] + 1 })
|
||||
RedisConfiguration.establish_pool(options[:concurrency])
|
||||
ProgressBar.create(
|
||||
{
|
||||
total: total,
|
||||
format: PROGRESS_FORMAT,
|
||||
}.merge(progress_output_options)
|
||||
)
|
||||
end
|
||||
|
||||
def parallelize_with_progress(scope)
|
||||
|
@ -82,8 +78,10 @@ module Mastodon::CLI
|
|||
[total.value, aggregate.value]
|
||||
end
|
||||
|
||||
def pastel
|
||||
@pastel ||= Pastel.new
|
||||
private
|
||||
|
||||
def progress_output_options
|
||||
Rails.env.test? ? { output: ProgressBar::Outputs::Null } : {}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -29,15 +29,7 @@ module Mastodon::CLI
|
|||
database will be imported into the indices, unless overridden with --no-import.
|
||||
LONG_DESC
|
||||
def deploy
|
||||
if options[:concurrency] < 1
|
||||
say('Cannot run with this concurrency setting, must be at least 1', :red)
|
||||
exit(1)
|
||||
end
|
||||
|
||||
if options[:batch_size] < 1
|
||||
say('Cannot run with this batch_size setting, must be at least 1', :red)
|
||||
exit(1)
|
||||
end
|
||||
verify_deploy_options!
|
||||
|
||||
indices = if options[:only]
|
||||
options[:only].map { |str| "#{str.camelize}Index".constantize }
|
||||
|
@ -98,5 +90,26 @@ module Mastodon::CLI
|
|||
|
||||
say("Indexed #{added} records, de-indexed #{removed}", :green, true)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def verify_deploy_options!
|
||||
verify_deploy_concurrency!
|
||||
verify_deploy_batch_size!
|
||||
end
|
||||
|
||||
def verify_deploy_concurrency!
|
||||
return unless options[:concurrency] < 1
|
||||
|
||||
say('Cannot run with this concurrency setting, must be at least 1', :red)
|
||||
exit(1)
|
||||
end
|
||||
|
||||
def verify_deploy_batch_size!
|
||||
return unless options[:batch_size] < 1
|
||||
|
||||
say('Cannot run with this batch_size setting, must be at least 1', :red)
|
||||
exit(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
14
package.json
14
package.json
|
@ -21,7 +21,6 @@
|
|||
"lint:sass": "stylelint \"**/*.{css,scss}\" && prettier --check \"**/*.{css,scss}\"",
|
||||
"lint:yml": "prettier --check \"**/*.{yaml,yml}\"",
|
||||
"lint": "yarn lint:js && yarn lint:json && yarn lint:sass && yarn lint:yml",
|
||||
"manage:translations": "node ./config/webpack/translationRunner.js",
|
||||
"postversion": "git push --tags",
|
||||
"prepare": "husky install",
|
||||
"start": "node ./streaming/index.js",
|
||||
|
@ -49,6 +48,7 @@
|
|||
"@reduxjs/toolkit": "^1.9.5",
|
||||
"abortcontroller-polyfill": "^1.7.5",
|
||||
"arrow-key-navigation": "^1.2.0",
|
||||
"async-mutex": "^0.4.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"axios": "^1.4.0",
|
||||
"babel-loader": "^8.3.0",
|
||||
|
@ -138,12 +138,12 @@
|
|||
"webpack-cli": "^3.3.12",
|
||||
"webpack-merge": "^5.9.0",
|
||||
"wicg-inert": "^3.1.2",
|
||||
"workbox-expiration": "^6.6.0",
|
||||
"workbox-precaching": "^6.6.0",
|
||||
"workbox-routing": "^6.6.0",
|
||||
"workbox-strategies": "^6.6.0",
|
||||
"workbox-webpack-plugin": "^6.6.0",
|
||||
"workbox-window": "^6.6.0",
|
||||
"workbox-expiration": "^7.0.0",
|
||||
"workbox-precaching": "^7.0.0",
|
||||
"workbox-routing": "^7.0.0",
|
||||
"workbox-strategies": "^7.0.0",
|
||||
"workbox-webpack-plugin": "^7.0.0",
|
||||
"workbox-window": "^7.0.0",
|
||||
"ws": "^8.12.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
3
spec/fixtures/files/lists.csv
vendored
Normal file
3
spec/fixtures/files/lists.csv
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
Mastodon project,gargron@example.com
|
||||
Mastodon project,mastodon@example.com
|
||||
test,foo@example.com
|
|
|
@ -33,7 +33,7 @@ describe ReactComponentHelper do
|
|||
|
||||
it 'returns a tag with data attributes' do
|
||||
expect(parsed_html.div['data-admin-component']).to eq('Name')
|
||||
expect(parsed_html.div['data-props']).to eq('{"locale":"en","one":"two"}')
|
||||
expect(parsed_html.div['data-props']).to eq('{"one":"two"}')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -924,4 +924,78 @@ describe Mastodon::CLI::Accounts do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#rotate' do
|
||||
context 'when neither username nor --all option are given' do
|
||||
it 'exits with an error message' do
|
||||
expect { cli.rotate }.to output(
|
||||
a_string_including('No account(s) given')
|
||||
).to_stdout
|
||||
.and raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a username is given' do
|
||||
let(:account) { Fabricate(:account) }
|
||||
|
||||
it 'correctly rotates keys for the specified account' do
|
||||
old_private_key = account.private_key
|
||||
old_public_key = account.public_key
|
||||
|
||||
cli.rotate(account.username)
|
||||
account.reload
|
||||
|
||||
expect(account.private_key).to_not eq(old_private_key)
|
||||
expect(account.public_key).to_not eq(old_public_key)
|
||||
end
|
||||
|
||||
it 'broadcasts the new keys for the specified account' do
|
||||
allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_in)
|
||||
|
||||
cli.rotate(account.username)
|
||||
|
||||
expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_in).with(anything, account.id, anything).once
|
||||
end
|
||||
|
||||
context 'when the given username is not found' do
|
||||
it 'exits with an error message when the specified username is not found' do
|
||||
expect { cli.rotate('non_existent_username') }.to output(
|
||||
a_string_including('No such account')
|
||||
).to_stdout
|
||||
.and raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when --all option is provided' do
|
||||
let(:accounts) { Fabricate.times(3, :account) }
|
||||
let(:options) { { all: true } }
|
||||
|
||||
before do
|
||||
allow(Account).to receive(:local).and_return(Account.where(id: accounts.map(&:id)))
|
||||
cli.options = { all: true }
|
||||
end
|
||||
|
||||
it 'correctly rotates keys for all local accounts' do
|
||||
old_private_keys = accounts.map(&:private_key)
|
||||
old_public_keys = accounts.map(&:public_key)
|
||||
|
||||
cli.rotate
|
||||
accounts.each(&:reload)
|
||||
|
||||
expect(accounts.map(&:private_key)).to_not eq(old_private_keys)
|
||||
expect(accounts.map(&:public_key)).to_not eq(old_public_keys)
|
||||
end
|
||||
|
||||
it 'broadcasts the new keys for each account' do
|
||||
allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_in)
|
||||
|
||||
cli.rotate
|
||||
|
||||
accounts.each do |account|
|
||||
expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_in).with(anything, account.id, anything).once
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -86,6 +86,7 @@ RSpec.describe Form::Import do
|
|||
it_behaves_like 'too many CSV rows', 'muting', 'imports.txt', 1
|
||||
it_behaves_like 'too many CSV rows', 'domain_blocking', 'domain_blocks.csv', 2
|
||||
it_behaves_like 'too many CSV rows', 'bookmarks', 'bookmark-imports.txt', 3
|
||||
it_behaves_like 'too many CSV rows', 'lists', 'lists.csv', 2
|
||||
|
||||
# Importing list of addresses with no headers into various types
|
||||
it_behaves_like 'valid import', 'following', 'imports.txt'
|
||||
|
@ -98,6 +99,9 @@ RSpec.describe Form::Import do
|
|||
# Importing bookmarks list with no headers into expected type
|
||||
it_behaves_like 'valid import', 'bookmarks', 'bookmark-imports.txt'
|
||||
|
||||
# Importing lists with no headers into expected type
|
||||
it_behaves_like 'valid import', 'lists', 'lists.csv'
|
||||
|
||||
# Importing followed accounts with headers into various compatible types
|
||||
it_behaves_like 'valid import', 'following', 'following_accounts.csv'
|
||||
it_behaves_like 'valid import', 'blocking', 'following_accounts.csv'
|
||||
|
@ -273,6 +277,12 @@ RSpec.describe Form::Import do
|
|||
{ 'acct' => 'user@test.com', 'hide_notifications' => false },
|
||||
]
|
||||
|
||||
it_behaves_like 'on successful import', 'lists', 'merge', 'lists.csv', [
|
||||
{ 'acct' => 'gargron@example.com', 'list_name' => 'Mastodon project' },
|
||||
{ 'acct' => 'mastodon@example.com', 'list_name' => 'Mastodon project' },
|
||||
{ 'acct' => 'foo@example.com', 'list_name' => 'test' },
|
||||
]
|
||||
|
||||
# Based on the bug report 20571 where UTF-8 encoded domains were rejecting import of their users
|
||||
#
|
||||
# https://github.com/mastodon/mastodon/issues/20571
|
||||
|
|
|
@ -91,5 +91,77 @@ RSpec.describe BulkImportRowService do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when importing a list row' do
|
||||
let(:import_type) { 'lists' }
|
||||
let(:target_account) { Fabricate(:account) }
|
||||
let(:data) do
|
||||
{ 'acct' => target_account.acct, 'list_name' => 'my list' }
|
||||
end
|
||||
|
||||
shared_examples 'common behavior' do
|
||||
context 'when the target account is already followed' do
|
||||
before do
|
||||
account.follow!(target_account)
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
expect(subject.call(import_row)).to be true
|
||||
end
|
||||
|
||||
it 'adds the target account to the list' do
|
||||
expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user already requested to follow the target account' do
|
||||
before do
|
||||
account.request_follow!(target_account)
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
expect(subject.call(import_row)).to be true
|
||||
end
|
||||
|
||||
it 'adds the target account to the list' do
|
||||
expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the target account is neither followed nor requested' do
|
||||
it 'returns true' do
|
||||
expect(subject.call(import_row)).to be true
|
||||
end
|
||||
|
||||
it 'adds the target account to the list' do
|
||||
expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the target account is the user themself' do
|
||||
let(:target_account) { account }
|
||||
|
||||
it 'returns true' do
|
||||
expect(subject.call(import_row)).to be true
|
||||
end
|
||||
|
||||
it 'adds the target account to the list' do
|
||||
expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the list does not exist yet' do
|
||||
include_examples 'common behavior'
|
||||
end
|
||||
|
||||
context 'when the list exists' do
|
||||
before do
|
||||
Fabricate(:list, account: account, title: 'my list')
|
||||
end
|
||||
|
||||
include_examples 'common behavior'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,6 +12,7 @@ RSpec.describe FetchLinkCardService, type: :service do
|
|||
stub_request(:get, 'http://example.com/koi8-r').to_return(request_fixture('koi8-r.txt'))
|
||||
stub_request(:get, 'http://example.com/日本語').to_return(request_fixture('sjis.txt'))
|
||||
stub_request(:get, 'https://github.com/qbi/WannaCry').to_return(status: 404)
|
||||
stub_request(:get, 'http://example.com/test?data=file.gpx%5E1').to_return(status: 200)
|
||||
stub_request(:get, 'http://example.com/test-').to_return(request_fixture('idn.txt'))
|
||||
stub_request(:get, 'http://example.com/windows-1251').to_return(request_fixture('windows-1251.txt'))
|
||||
|
||||
|
@ -87,6 +88,15 @@ RSpec.describe FetchLinkCardService, type: :service do
|
|||
expect(a_request(:get, 'http://example.com/sjis')).to_not have_been_made
|
||||
end
|
||||
end
|
||||
|
||||
context do
|
||||
let(:status) { Fabricate(:status, text: 'test http://example.com/test?data=file.gpx^1') }
|
||||
|
||||
it 'does fetch URLs with a caret in search params' do
|
||||
expect(a_request(:get, 'http://example.com/test?data=file.gpx')).to_not have_been_made
|
||||
expect(a_request(:get, 'http://example.com/test?data=file.gpx%5E1')).to have_been_made.once
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a remote status' do
|
||||
|
|
274
yarn.lock
274
yarn.lock
|
@ -1743,6 +1743,15 @@
|
|||
"@jridgewell/set-array" "^1.0.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
|
||||
"@jridgewell/gen-mapping@^0.3.0":
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098"
|
||||
integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==
|
||||
dependencies:
|
||||
"@jridgewell/set-array" "^1.0.1"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
"@jridgewell/trace-mapping" "^0.3.9"
|
||||
|
||||
"@jridgewell/gen-mapping@^0.3.2":
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"
|
||||
|
@ -1767,6 +1776,14 @@
|
|||
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
|
||||
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
|
||||
|
||||
"@jridgewell/source-map@^0.3.2":
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.3.tgz#8108265659d4c33e72ffe14e33d6cc5eb59f2fda"
|
||||
integrity sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==
|
||||
dependencies:
|
||||
"@jridgewell/gen-mapping" "^0.3.0"
|
||||
"@jridgewell/trace-mapping" "^0.3.9"
|
||||
|
||||
"@jridgewell/sourcemap-codec@1.4.14":
|
||||
version "1.4.14"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
|
||||
|
@ -3017,9 +3034,9 @@ ansi-regex@^2.0.0:
|
|||
integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
|
||||
|
||||
ansi-regex@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
|
||||
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed"
|
||||
integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==
|
||||
|
||||
ansi-regex@^5.0.0, ansi-regex@^5.0.1:
|
||||
version "5.0.1"
|
||||
|
@ -3266,6 +3283,13 @@ async-limiter@~1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
|
||||
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
|
||||
|
||||
async-mutex@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.4.0.tgz#ae8048cd4d04ace94347507504b3cf15e631c25f"
|
||||
integrity sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
async@^2.6.2:
|
||||
version "2.6.4"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
|
||||
|
@ -7862,9 +7886,9 @@ loader-utils@^1.2.3, loader-utils@^1.4.0:
|
|||
json5 "^1.0.1"
|
||||
|
||||
loader-utils@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
|
||||
integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
|
||||
integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
|
||||
dependencies:
|
||||
big.js "^5.2.2"
|
||||
emojis-list "^3.0.0"
|
||||
|
@ -10819,7 +10843,7 @@ source-map@^0.7.3:
|
|||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656"
|
||||
integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==
|
||||
|
||||
source-map@^0.8.0-beta.0, source-map@~0.8.0-beta.0:
|
||||
source-map@^0.8.0-beta.0:
|
||||
version "0.8.0-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11"
|
||||
integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==
|
||||
|
@ -10900,9 +10924,9 @@ sprintf-js@~1.0.2:
|
|||
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
|
||||
|
||||
ssri@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.0.tgz#79ca74e21f8ceaeddfcb4b90143c458b8d988808"
|
||||
integrity sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==
|
||||
version "8.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af"
|
||||
integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==
|
||||
dependencies:
|
||||
minipass "^3.1.1"
|
||||
|
||||
|
@ -11396,13 +11420,13 @@ terser-webpack-plugin@^1.4.3, terser-webpack-plugin@^4.2.3:
|
|||
webpack-sources "^1.4.3"
|
||||
|
||||
terser@^5.0.0, terser@^5.3.4:
|
||||
version "5.13.1"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.13.1.tgz#66332cdc5a01b04a224c9fad449fc1a18eaa1799"
|
||||
integrity sha512-hn4WKOfwnwbYfe48NgrQjqNOH9jzLqRcIfbYytOXCOv46LBfWr9bDS17MQqOi+BWGD0sJK3Sj5NC/gJjiojaoA==
|
||||
version "5.17.6"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.6.tgz#d810e75e1bb3350c799cd90ebefe19c9412c12de"
|
||||
integrity sha512-V8QHcs8YuyLkLHsJO5ucyff1ykrLVsR4dNnS//L5Y3NiSXpbK1J+WMVUs67eI0KTxs9JtHhgEQpXQVHlHI92DQ==
|
||||
dependencies:
|
||||
"@jridgewell/source-map" "^0.3.2"
|
||||
acorn "^8.5.0"
|
||||
commander "^2.20.0"
|
||||
source-map "~0.8.0-beta.0"
|
||||
source-map-support "~0.5.20"
|
||||
|
||||
tesseract.js-core@^2.2.0:
|
||||
|
@ -12314,25 +12338,25 @@ word-wrap@^1.2.3, word-wrap@~1.2.3:
|
|||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
||||
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
|
||||
|
||||
workbox-background-sync@6.6.1:
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.6.1.tgz#08d603a33717ce663e718c30cc336f74909aff2f"
|
||||
integrity sha512-trJd3ovpWCvzu4sW0E8rV3FUyIcC0W8G+AZ+VcqzzA890AsWZlUGOTSxIMmIHVusUw/FDq1HFWfy/kC/WTRqSg==
|
||||
workbox-background-sync@7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-7.0.0.tgz#2b84b96ca35fec976e3bd2794b70e4acec46b3a5"
|
||||
integrity sha512-S+m1+84gjdueM+jIKZ+I0Lx0BDHkk5Nu6a3kTVxP4fdj3gKouRNmhO8H290ybnJTOPfBDtTMXSQA/QLTvr7PeA==
|
||||
dependencies:
|
||||
idb "^7.0.1"
|
||||
workbox-core "6.6.1"
|
||||
workbox-core "7.0.0"
|
||||
|
||||
workbox-broadcast-update@6.6.1:
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-6.6.1.tgz#0fad9454cf8e4ace0c293e5617c64c75d8a8c61e"
|
||||
integrity sha512-fBhffRdaANdeQ1V8s692R9l/gzvjjRtydBOvR6WCSB0BNE2BacA29Z4r9/RHd9KaXCPl6JTdI9q0bR25YKP8TQ==
|
||||
workbox-broadcast-update@7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-7.0.0.tgz#7f611ca1a94ba8ac0aa40fa171c9713e0f937d22"
|
||||
integrity sha512-oUuh4jzZrLySOo0tC0WoKiSg90bVAcnE98uW7F8GFiSOXnhogfNDGZelPJa+6KpGBO5+Qelv04Hqx2UD+BJqNQ==
|
||||
dependencies:
|
||||
workbox-core "6.6.1"
|
||||
workbox-core "7.0.0"
|
||||
|
||||
workbox-build@6.6.1:
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-6.6.1.tgz#6010e9ce550910156761448f2dbea8cfcf759cb0"
|
||||
integrity sha512-INPgDx6aRycAugUixbKgiEQBWD0MPZqU5r0jyr24CehvNuLPSXp/wGOpdRJmts656lNiXwqV7dC2nzyrzWEDnw==
|
||||
workbox-build@7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-7.0.0.tgz#02ab5ef2991b3369b8b9395703f08912212769b4"
|
||||
integrity sha512-CttE7WCYW9sZC+nUYhQg3WzzGPr4IHmrPnjKiu3AMXsiNQKx+l4hHl63WTrnicLmKEKHScWDH8xsGBdrYgtBzg==
|
||||
dependencies:
|
||||
"@apideck/better-ajv-errors" "^0.3.1"
|
||||
"@babel/core" "^7.11.1"
|
||||
|
@ -12356,132 +12380,132 @@ workbox-build@6.6.1:
|
|||
strip-comments "^2.0.1"
|
||||
tempy "^0.6.0"
|
||||
upath "^1.2.0"
|
||||
workbox-background-sync "6.6.1"
|
||||
workbox-broadcast-update "6.6.1"
|
||||
workbox-cacheable-response "6.6.1"
|
||||
workbox-core "6.6.1"
|
||||
workbox-expiration "6.6.1"
|
||||
workbox-google-analytics "6.6.1"
|
||||
workbox-navigation-preload "6.6.1"
|
||||
workbox-precaching "6.6.1"
|
||||
workbox-range-requests "6.6.1"
|
||||
workbox-recipes "6.6.1"
|
||||
workbox-routing "6.6.1"
|
||||
workbox-strategies "6.6.1"
|
||||
workbox-streams "6.6.1"
|
||||
workbox-sw "6.6.1"
|
||||
workbox-window "6.6.1"
|
||||
workbox-background-sync "7.0.0"
|
||||
workbox-broadcast-update "7.0.0"
|
||||
workbox-cacheable-response "7.0.0"
|
||||
workbox-core "7.0.0"
|
||||
workbox-expiration "7.0.0"
|
||||
workbox-google-analytics "7.0.0"
|
||||
workbox-navigation-preload "7.0.0"
|
||||
workbox-precaching "7.0.0"
|
||||
workbox-range-requests "7.0.0"
|
||||
workbox-recipes "7.0.0"
|
||||
workbox-routing "7.0.0"
|
||||
workbox-strategies "7.0.0"
|
||||
workbox-streams "7.0.0"
|
||||
workbox-sw "7.0.0"
|
||||
workbox-window "7.0.0"
|
||||
|
||||
workbox-cacheable-response@6.6.1:
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-6.6.1.tgz#284c2b86be3f4fd191970ace8c8e99797bcf58e9"
|
||||
integrity sha512-85LY4veT2CnTCDxaVG7ft3NKaFbH6i4urZXgLiU4AiwvKqS2ChL6/eILiGRYXfZ6gAwDnh5RkuDbr/GMS4KSag==
|
||||
workbox-cacheable-response@7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-7.0.0.tgz#ee27c036728189eed69d25a135013053277482d2"
|
||||
integrity sha512-0lrtyGHn/LH8kKAJVOQfSu3/80WDc9Ma8ng0p2i/5HuUndGttH+mGMSvOskjOdFImLs2XZIimErp7tSOPmu/6g==
|
||||
dependencies:
|
||||
workbox-core "6.6.1"
|
||||
workbox-core "7.0.0"
|
||||
|
||||
workbox-core@6.6.1:
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-6.6.1.tgz#7184776d4134c5ed2f086878c882728fc9084265"
|
||||
integrity sha512-ZrGBXjjaJLqzVothoE12qTbVnOAjFrHDXpZe7coCb6q65qI/59rDLwuFMO4PcZ7jcbxY+0+NhUVztzR/CbjEFw==
|
||||
workbox-core@7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-7.0.0.tgz#dec114ec923cc2adc967dd9be1b8a0bed50a3545"
|
||||
integrity sha512-81JkAAZtfVP8darBpfRTovHg8DGAVrKFgHpOArZbdFd78VqHr5Iw65f2guwjE2NlCFbPFDoez3D3/6ZvhI/rwQ==
|
||||
|
||||
workbox-expiration@6.6.1, workbox-expiration@^6.6.0:
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-6.6.1.tgz#a841fa36676104426dbfb9da1ef6a630b4f93739"
|
||||
integrity sha512-qFiNeeINndiOxaCrd2DeL1Xh1RFug3JonzjxUHc5WkvkD2u5abY3gZL1xSUNt3vZKsFFGGORItSjVTVnWAZO4A==
|
||||
workbox-expiration@7.0.0, workbox-expiration@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-7.0.0.tgz#3d90bcf2a7577241de950f89784f6546b66c2baa"
|
||||
integrity sha512-MLK+fogW+pC3IWU9SFE+FRStvDVutwJMR5if1g7oBJx3qwmO69BNoJQVaMXq41R0gg3MzxVfwOGKx3i9P6sOLQ==
|
||||
dependencies:
|
||||
idb "^7.0.1"
|
||||
workbox-core "6.6.1"
|
||||
workbox-core "7.0.0"
|
||||
|
||||
workbox-google-analytics@6.6.1:
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-6.6.1.tgz#a07a6655ab33d89d1b0b0a935ffa5dea88618c5d"
|
||||
integrity sha512-1TjSvbFSLmkpqLcBsF7FuGqqeDsf+uAXO/pjiINQKg3b1GN0nBngnxLcXDYo1n/XxK4N7RaRrpRlkwjY/3ocuA==
|
||||
workbox-google-analytics@7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-7.0.0.tgz#603b2c4244af1e85de0fb26287d4e17d3293452a"
|
||||
integrity sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==
|
||||
dependencies:
|
||||
workbox-background-sync "6.6.1"
|
||||
workbox-core "6.6.1"
|
||||
workbox-routing "6.6.1"
|
||||
workbox-strategies "6.6.1"
|
||||
workbox-background-sync "7.0.0"
|
||||
workbox-core "7.0.0"
|
||||
workbox-routing "7.0.0"
|
||||
workbox-strategies "7.0.0"
|
||||
|
||||
workbox-navigation-preload@6.6.1:
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-6.6.1.tgz#61a34fe125558dd88cf09237f11bd966504ea059"
|
||||
integrity sha512-DQCZowCecO+wRoIxJI2V6bXWK6/53ff+hEXLGlQL4Rp9ZaPDLrgV/32nxwWIP7QpWDkVEtllTAK5h6cnhxNxDA==
|
||||
workbox-navigation-preload@7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-7.0.0.tgz#4913878dbbd97057181d57baa18d2bbdde085c6c"
|
||||
integrity sha512-juWCSrxo/fiMz3RsvDspeSLGmbgC0U9tKqcUPZBCf35s64wlaLXyn2KdHHXVQrb2cqF7I0Hc9siQalainmnXJA==
|
||||
dependencies:
|
||||
workbox-core "6.6.1"
|
||||
workbox-core "7.0.0"
|
||||
|
||||
workbox-precaching@6.6.1, workbox-precaching@^6.6.0:
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-6.6.1.tgz#dedeeba10a2d163d990bf99f1c2066ac0d1a19e2"
|
||||
integrity sha512-K4znSJ7IKxCnCYEdhNkMr7X1kNh8cz+mFgx9v5jFdz1MfI84pq8C2zG+oAoeE5kFrUf7YkT5x4uLWBNg0DVZ5A==
|
||||
workbox-precaching@7.0.0, workbox-precaching@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-7.0.0.tgz#3979ba8033aadf3144b70e9fe631d870d5fbaa03"
|
||||
integrity sha512-EC0vol623LJqTJo1mkhD9DZmMP604vHqni3EohhQVwhJlTgyKyOkMrZNy5/QHfOby+39xqC01gv4LjOm4HSfnA==
|
||||
dependencies:
|
||||
workbox-core "6.6.1"
|
||||
workbox-routing "6.6.1"
|
||||
workbox-strategies "6.6.1"
|
||||
workbox-core "7.0.0"
|
||||
workbox-routing "7.0.0"
|
||||
workbox-strategies "7.0.0"
|
||||
|
||||
workbox-range-requests@6.6.1:
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-6.6.1.tgz#ddaf7e73af11d362fbb2f136a9063a4c7f507a39"
|
||||
integrity sha512-4BDzk28govqzg2ZpX0IFkthdRmCKgAKreontYRC5YsAPB2jDtPNxqx3WtTXgHw1NZalXpcH/E4LqUa9+2xbv1g==
|
||||
workbox-range-requests@7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-7.0.0.tgz#97511901e043df27c1aa422adcc999a7751f52ed"
|
||||
integrity sha512-SxAzoVl9j/zRU9OT5+IQs7pbJBOUOlriB8Gn9YMvi38BNZRbM+RvkujHMo8FOe9IWrqqwYgDFBfv6sk76I1yaQ==
|
||||
dependencies:
|
||||
workbox-core "6.6.1"
|
||||
workbox-core "7.0.0"
|
||||
|
||||
workbox-recipes@6.6.1:
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-6.6.1.tgz#ea70d2b2b0b0bce8de0a9d94f274d4a688e69fae"
|
||||
integrity sha512-/oy8vCSzromXokDA+X+VgpeZJvtuf8SkQ8KL0xmRivMgJZrjwM3c2tpKTJn6PZA6TsbxGs3Sc7KwMoZVamcV2g==
|
||||
workbox-recipes@7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-7.0.0.tgz#1a6a01c8c2dfe5a41eef0fed3fe517e8a45c6514"
|
||||
integrity sha512-DntcK9wuG3rYQOONWC0PejxYYIDHyWWZB/ueTbOUDQgefaeIj1kJ7pdP3LZV2lfrj8XXXBWt+JDRSw1lLLOnww==
|
||||
dependencies:
|
||||
workbox-cacheable-response "6.6.1"
|
||||
workbox-core "6.6.1"
|
||||
workbox-expiration "6.6.1"
|
||||
workbox-precaching "6.6.1"
|
||||
workbox-routing "6.6.1"
|
||||
workbox-strategies "6.6.1"
|
||||
workbox-cacheable-response "7.0.0"
|
||||
workbox-core "7.0.0"
|
||||
workbox-expiration "7.0.0"
|
||||
workbox-precaching "7.0.0"
|
||||
workbox-routing "7.0.0"
|
||||
workbox-strategies "7.0.0"
|
||||
|
||||
workbox-routing@6.6.1, workbox-routing@^6.6.0:
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-6.6.1.tgz#cba9a1c7e0d1ea11e24b6f8c518840efdc94f581"
|
||||
integrity sha512-j4ohlQvfpVdoR8vDYxTY9rA9VvxTHogkIDwGdJ+rb2VRZQ5vt1CWwUUZBeD/WGFAni12jD1HlMXvJ8JS7aBWTg==
|
||||
workbox-routing@7.0.0, workbox-routing@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-7.0.0.tgz#6668438a06554f60645aedc77244a4fe3a91e302"
|
||||
integrity sha512-8YxLr3xvqidnbVeGyRGkaV4YdlKkn5qZ1LfEePW3dq+ydE73hUUJJuLmGEykW3fMX8x8mNdL0XrWgotcuZjIvA==
|
||||
dependencies:
|
||||
workbox-core "6.6.1"
|
||||
workbox-core "7.0.0"
|
||||
|
||||
workbox-strategies@6.6.1, workbox-strategies@^6.6.0:
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-6.6.1.tgz#38d0f0fbdddba97bd92e0c6418d0b1a2ccd5b8bf"
|
||||
integrity sha512-WQLXkRnsk4L81fVPkkgon1rZNxnpdO5LsO+ws7tYBC6QQQFJVI6v98klrJEjFtZwzw/mB/HT5yVp7CcX0O+mrw==
|
||||
workbox-strategies@7.0.0, workbox-strategies@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-7.0.0.tgz#dcba32b3f3074476019049cc490fe1a60ea73382"
|
||||
integrity sha512-dg3qJU7tR/Gcd/XXOOo7x9QoCI9nk74JopaJaYAQ+ugLi57gPsXycVdBnYbayVj34m6Y8ppPwIuecrzkpBVwbA==
|
||||
dependencies:
|
||||
workbox-core "6.6.1"
|
||||
workbox-core "7.0.0"
|
||||
|
||||
workbox-streams@6.6.1:
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-6.6.1.tgz#b2f7ba7b315c27a6e3a96a476593f99c5d227d26"
|
||||
integrity sha512-maKG65FUq9e4BLotSKWSTzeF0sgctQdYyTMq529piEN24Dlu9b6WhrAfRpHdCncRS89Zi2QVpW5V33NX8PgH3Q==
|
||||
workbox-streams@7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-7.0.0.tgz#36722aecd04785f88b6f709e541c094fc658c0f9"
|
||||
integrity sha512-moVsh+5to//l6IERWceYKGiftc+prNnqOp2sgALJJFbnNVpTXzKISlTIsrWY+ogMqt+x1oMazIdHj25kBSq/HQ==
|
||||
dependencies:
|
||||
workbox-core "6.6.1"
|
||||
workbox-routing "6.6.1"
|
||||
workbox-core "7.0.0"
|
||||
workbox-routing "7.0.0"
|
||||
|
||||
workbox-sw@6.6.1:
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-6.6.1.tgz#d4c4ca3125088e8b9fd7a748ed537fa0247bd72c"
|
||||
integrity sha512-R7whwjvU2abHH/lR6kQTTXLHDFU2izht9kJOvBRYK65FbwutT4VvnUAJIgHvfWZ/fokrOPhfoWYoPCMpSgUKHQ==
|
||||
workbox-sw@7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-7.0.0.tgz#7350126411e3de1409f7ec243df8d06bb5b08b86"
|
||||
integrity sha512-SWfEouQfjRiZ7GNABzHUKUyj8pCoe+RwjfOIajcx6J5mtgKkN+t8UToHnpaJL5UVVOf5YhJh+OHhbVNIHe+LVA==
|
||||
|
||||
workbox-webpack-plugin@^6.6.0:
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-6.6.1.tgz#4f81cc1ad4e5d2cd7477a86ba83c84ee2d187531"
|
||||
integrity sha512-zpZ+ExFj9NmiI66cFEApyjk7hGsfJ1YMOaLXGXBoZf0v7Iu6hL0ZBe+83mnDq3YYWAfA3fnyFejritjOHkFcrA==
|
||||
workbox-webpack-plugin@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-7.0.0.tgz#6c61661a2cacde1239192a5877a041a2943d1a55"
|
||||
integrity sha512-R1ZzCHPfzeJjLK2/TpKUhxSQ3fFDCxlWxgRhhSjMQLz3G2MlBnyw/XeYb34e7SGgSv0qG22zEhMIzjMNqNeKbw==
|
||||
dependencies:
|
||||
fast-json-stable-stringify "^2.1.0"
|
||||
pretty-bytes "^5.4.1"
|
||||
upath "^1.2.0"
|
||||
webpack-sources "^1.4.3"
|
||||
workbox-build "6.6.1"
|
||||
workbox-build "7.0.0"
|
||||
|
||||
workbox-window@6.6.1, workbox-window@^6.6.0:
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-6.6.1.tgz#f22a394cbac36240d0dadcbdebc35f711bb7b89e"
|
||||
integrity sha512-wil4nwOY58nTdCvif/KEZjQ2NP8uk3gGeRNy2jPBbzypU4BT4D9L8xiwbmDBpZlSgJd2xsT9FvSNU0gsxV51JQ==
|
||||
workbox-window@7.0.0, workbox-window@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-7.0.0.tgz#a683ab33c896e4f16786794eac7978fc98a25d08"
|
||||
integrity sha512-j7P/bsAWE/a7sxqTzXo3P2ALb1reTfZdvVp6OJ/uLr/C2kZAMvjeWGm8V4htQhor7DOvYg0sSbFN2+flT5U0qA==
|
||||
dependencies:
|
||||
"@types/trusted-types" "^2.0.2"
|
||||
workbox-core "6.6.1"
|
||||
workbox-core "7.0.0"
|
||||
|
||||
wrap-ansi@^5.1.0:
|
||||
version "5.1.0"
|
||||
|
@ -12532,9 +12556,9 @@ write-file-atomic@^5.0.1:
|
|||
signal-exit "^4.0.1"
|
||||
|
||||
ws@^6.2.1:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"
|
||||
integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==
|
||||
version "6.2.2"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e"
|
||||
integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==
|
||||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue