Merge remote-tracking branch 'parent/main' into upstream-20240304

This commit is contained in:
KMY 2024-03-04 10:37:41 +09:00
commit 42b5727723
147 changed files with 1529 additions and 958 deletions

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-2024 Paweł Kuna
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1 @@
Images in this folder are based on [Tabler.io icons](https://tabler.io/icons).

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-2024 Paweł Kuna
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1 @@
Images in this folder are based on [Tabler.io icons](https://tabler.io/icons).

View file

Before

Width:  |  Height:  |  Size: 547 B

After

Width:  |  Height:  |  Size: 547 B

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 505 B

After

Width:  |  Height:  |  Size: 505 B

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

Before

Width:  |  Height:  |  Size: 688 B

After

Width:  |  Height:  |  Size: 688 B

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

Before

Width:  |  Height:  |  Size: 709 B

After

Width:  |  Height:  |  Size: 709 B

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 939 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -67,7 +67,7 @@ class EditedTimestamp extends PureComponent {
return (
<DropdownMenu statusId={statusId} renderItem={this.renderItem} scrollable renderHeader={this.renderHeader} onItemClick={this.handleItemClick}>
<button className='dropdown-menu__text-button'>
<FormattedMessage id='status.edited' defaultMessage='Edited {date}' values={{ date: intl.formatDate(timestamp, { hour12: false, month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) }} /> <Icon id='caret-down' icon={ArrowDropDownIcon} />
<FormattedMessage id='status.edited' defaultMessage='Edited {date}' values={{ date: intl.formatDate(timestamp, { month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) }} /> <Icon id='caret-down' icon={ArrowDropDownIcon} />
</button>
</DropdownMenu>
);

View file

@ -53,7 +53,6 @@ const messages = defineMessages({
});
const dateFormatOptions = {
hour12: false,
year: 'numeric',
month: 'short',
day: '2-digit',

View file

@ -632,7 +632,7 @@ class Status extends ImmutablePureComponent {
{withExpiration}
{withLimited}
<span className='status__visibility-icon'><VisibilityIcon visibility={visibilityName} /></span>
<RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get('edited_at'), { hour12: false, year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
<RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get('edited_at'), { year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
</a>
<a onClick={this.handleAccountClick} href={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>

View file

@ -104,7 +104,6 @@ const dateFormatOptions = {
month: 'short',
day: 'numeric',
year: 'numeric',
hour12: false,
hour: '2-digit',
minute: '2-digit',
};

View file

@ -343,7 +343,7 @@ class Announcement extends ImmutablePureComponent {
<div className='announcements__item'>
<strong className='announcements__item__range'>
<FormattedMessage id='announcement.announcement' defaultMessage='Announcement' />
{hasTimeRange && <span> · <FormattedDate value={startsAt} hour12={false} year={(skipYear || startsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month='short' day='2-digit' hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /> - <FormattedDate value={endsAt} hour12={false} year={(skipYear || endsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month={skipEndDate ? undefined : 'short'} day={skipEndDate ? undefined : '2-digit'} hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /></span>}
{hasTimeRange && <span> · <FormattedDate value={startsAt} year={(skipYear || startsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month='short' day='2-digit' hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /> - <FormattedDate value={endsAt} year={(skipYear || endsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month={skipEndDate ? undefined : 'short'} day={skipEndDate ? undefined : '2-digit'} hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /></span>}
</strong>
<Content announcement={announcement} />

View file

@ -92,6 +92,10 @@ export default class Card extends PureComponent {
this.setState({ embedded: true });
};
handleExternalLinkClick = (e) => {
e.stopPropagation();
};
setRef = c => {
this.node = c;
};
@ -201,7 +205,7 @@ export default class Card extends PureComponent {
<div className='status-card__actions' onClick={this.handleEmbedClick} role='none'>
<div>
<button type='button' onClick={this.handleEmbedClick}><Icon id='play' icon={PlayArrowIcon} /></button>
<a href={card.get('url')} target='_blank' rel='noopener noreferrer'><Icon id='external-link' icon={OpenInNewIcon} /></a>
<a href={card.get('url')} onClick={this.handleExternalLinkClick} target='_blank' rel='noopener noreferrer'><Icon id='external-link' icon={OpenInNewIcon} /></a>
</div>
</div>
) : spoilerButton}

View file

@ -374,8 +374,8 @@ class DetailedStatus extends ImmutablePureComponent {
<div className='detailed-status__meta'>
<a className='detailed-status__datetime' href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} target='_blank' rel='noopener noreferrer'>
<FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
</a>{edited}{visibilityLink}{searchabilityLink}{applicationLink}{reblogLink} · {favouriteLink} · {emojiReactionsLink} - {statusReferencesLink}
<FormattedDate value={new Date(status.get('created_at'))} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
</a>{edited}{visibilityLink}{searchabilityLink}{applicationLink}{reblogLink} · {favouriteLink} · {emojiReactionsLink} · {statusReferencesLink}
</div>
</div>
</div>

View file

@ -1,6 +1,6 @@
{
"about.blocks": "Prižiūrimi serveriai",
"about.contact": "Kontaktuoti:",
"about.contact": "Kontaktai:",
"about.disclaimer": "Mastodon nemokama atvirojo kodo programa ir Mastodon gGmbH prekės ženklas.",
"about.domain_blocks.no_reason_available": "Priežastis nepateikta",
"about.domain_blocks.preamble": "Mastodon paprastai leidžia peržiūrėti turinį ir bendrauti su naudotojais iš bet kurio kito fediverse esančio serverio. Šios yra išimtys, kurios buvo padarytos šiame konkrečiame serveryje.",
@ -424,7 +424,7 @@
"notifications.column_settings.mention": "Paminėjimai:",
"notifications.column_settings.poll": "Balsavimo rezultatai:",
"notifications.column_settings.push": "\"Push\" pranešimai",
"notifications.column_settings.reblog": "\"Boost\" kiekis:",
"notifications.column_settings.reblog": "Pakėlimai:",
"notifications.column_settings.show": "Rodyti stulpelyje",
"notifications.column_settings.sound": "Paleisti garsą",
"notifications.column_settings.status": "New toots:",
@ -636,6 +636,7 @@
"status.translate": "Versti",
"status.translated_from_with": "Išversta iš {lang} naudojant {provider}",
"status.uncached_media_warning": "Peržiūra nepasiekiama",
"subscribed_languages.lead": "Po pakeitimo tavo pagrindinėje ir sąrašo laiko juostose bus rodomi tik įrašai pasirinktomis kalbomis. Jei nori gauti įrašus visomis kalbomis, pasirink nė vieno.",
"tabs_bar.home": "Pradžia",
"tabs_bar.notifications": "Pranešimai",
"time_remaining.days": "Liko {number, plural, one {# diena} few {# dienos} many {# dieno} other {# dienų}}",

View file

@ -529,8 +529,8 @@
"poll_button.add_poll": "Peiling toevoegen",
"poll_button.remove_poll": "Peiling verwijderen",
"privacy.change": "Zichtbaarheid van bericht aanpassen",
"privacy.direct.long": "Iedereen die in het bericht wordt vermeld",
"privacy.direct.short": "Bepaalde mensen",
"privacy.direct.long": "Alleen voor mensen die specifiek in het bericht worden vermeld",
"privacy.direct.short": "Specifieke mensen",
"privacy.private.long": "Alleen jouw volgers",
"privacy.private.short": "Volgers",
"privacy.public.long": "Iedereen op Mastodon en daarbuiten",

View file

@ -668,7 +668,7 @@
"status.mute": "靜音 @{name}",
"status.mute_conversation": "靜音對話",
"status.open": "展開此嘟文",
"status.pin": "釘選個人檔案頁面",
"status.pin": "釘選個人檔案頁面",
"status.pinned": "釘選嘟文",
"status.read_more": "閱讀更多",
"status.reblog": "轉嘟",

View file

@ -1,254 +0,0 @@
import './public-path';
import React from 'react';
import { createRoot } from 'react-dom/client';
import Rails from '@rails/ujs';
import ready from '../mastodon/ready';
const setAnnouncementEndsAttributes = (target) => {
const valid = target?.value && target?.validity?.valid;
const element = document.querySelector('input[type="datetime-local"]#announcement_ends_at');
if (valid) {
element.classList.remove('optional');
element.required = true;
element.min = target.value;
} else {
element.classList.add('optional');
element.removeAttribute('required');
element.removeAttribute('min');
}
};
Rails.delegate(document, 'input[type="datetime-local"]#announcement_starts_at', 'change', ({ target }) => {
setAnnouncementEndsAttributes(target);
});
const batchCheckboxClassName = '.batch-checkbox input[type="checkbox"]';
const showSelectAll = () => {
const selectAllMatchingElement = document.querySelector('.batch-table__select-all');
selectAllMatchingElement.classList.add('active');
};
const hideSelectAll = () => {
const selectAllMatchingElement = document.querySelector('.batch-table__select-all');
const hiddenField = document.querySelector('#select_all_matching');
const selectedMsg = document.querySelector('.batch-table__select-all .selected');
const notSelectedMsg = document.querySelector('.batch-table__select-all .not-selected');
selectAllMatchingElement.classList.remove('active');
selectedMsg.classList.remove('active');
notSelectedMsg.classList.add('active');
hiddenField.value = '0';
};
Rails.delegate(document, '#batch_checkbox_all', 'change', ({ target }) => {
const selectAllMatchingElement = document.querySelector('.batch-table__select-all');
document.querySelectorAll(batchCheckboxClassName).forEach((content) => {
content.checked = target.checked;
});
if (selectAllMatchingElement) {
if (target.checked) {
showSelectAll();
} else {
hideSelectAll();
}
}
});
Rails.delegate(document, '.batch-table__select-all button', 'click', () => {
const hiddenField = document.querySelector('#select_all_matching');
const active = hiddenField.value === '1';
const selectedMsg = document.querySelector('.batch-table__select-all .selected');
const notSelectedMsg = document.querySelector('.batch-table__select-all .not-selected');
if (active) {
hiddenField.value = '0';
selectedMsg.classList.remove('active');
notSelectedMsg.classList.add('active');
} else {
hiddenField.value = '1';
notSelectedMsg.classList.remove('active');
selectedMsg.classList.add('active');
}
});
Rails.delegate(document, batchCheckboxClassName, 'change', () => {
const checkAllElement = document.querySelector('#batch_checkbox_all');
const selectAllMatchingElement = document.querySelector('.batch-table__select-all');
if (checkAllElement) {
const allCheckboxes = Array.from(
document.querySelectorAll(batchCheckboxClassName)
);
checkAllElement.checked = allCheckboxes.every((content) => content.checked);
checkAllElement.indeterminate = !checkAllElement.checked && allCheckboxes.some((content) => content.checked);
if (selectAllMatchingElement) {
if (checkAllElement.checked) {
showSelectAll();
} else {
hideSelectAll();
}
}
}
});
Rails.delegate(document, '.filter-subset--with-select select', 'change', ({ target }) => {
target.form.submit();
});
const onDomainBlockSeverityChange = (target) => {
const rejectMediaDiv = document.querySelector('.input.with_label.domain_block_reject_media');
const rejectFavouriteDiv= document.querySelector('.input.with_label.domain_block_reject_favourite');
const rejectReplyDiv = document.querySelector('.input.with_label.domain_block_reject_reply');
const rejectReportsDiv = document.querySelector('.input.with_label.domain_block_reject_reports');
if (rejectMediaDiv) {
rejectMediaDiv.style.display = (target.value === 'suspend') ? 'none' : 'block';
}
if (rejectFavouriteDiv) {
rejectFavouriteDiv.style.display = (target.value === 'suspend') ? 'none' : 'block';
}
if (rejectReplyDiv) {
rejectReplyDiv.style.display = (target.value === 'suspend') ? 'none' : 'block';
}
if (rejectReportsDiv) {
rejectReportsDiv.style.display = (target.value === 'suspend') ? 'none' : 'block';
}
};
Rails.delegate(document, '#domain_block_severity', 'change', ({ target }) => onDomainBlockSeverityChange(target));
const onEnableBootstrapTimelineAccountsChange = (target) => {
const bootstrapTimelineAccountsField = document.querySelector('#form_admin_settings_bootstrap_timeline_accounts');
if (bootstrapTimelineAccountsField) {
bootstrapTimelineAccountsField.disabled = !target.checked;
if (target.checked) {
bootstrapTimelineAccountsField.parentElement.classList.remove('disabled');
bootstrapTimelineAccountsField.parentElement.parentElement.classList.remove('disabled');
} else {
bootstrapTimelineAccountsField.parentElement.classList.add('disabled');
bootstrapTimelineAccountsField.parentElement.parentElement.classList.add('disabled');
}
}
};
Rails.delegate(document, '#form_admin_settings_enable_bootstrap_timeline_accounts', 'change', ({ target }) => onEnableBootstrapTimelineAccountsChange(target));
const onChangeRegistrationMode = (target) => {
const enabled = target.value === 'approved';
document.querySelectorAll('.form_admin_settings_registrations_mode .warning-hint').forEach((warning_hint) => {
warning_hint.style.display = target.value === 'open' ? 'inline' : 'none';
});
document.querySelectorAll('#form_admin_settings_require_invite_text').forEach((input) => {
input.disabled = !enabled;
if (enabled) {
let element = input;
do {
element.classList.remove('disabled');
element = element.parentElement;
} while (element && !element.classList.contains('fields-group'));
} else {
let element = input;
do {
element.classList.add('disabled');
element = element.parentElement;
} while (element && !element.classList.contains('fields-group'));
}
});
};
const convertUTCDateTimeToLocal = (value) => {
const date = new Date(value + 'Z');
const twoChars = (x) => (x.toString().padStart(2, '0'));
return `${date.getFullYear()}-${twoChars(date.getMonth()+1)}-${twoChars(date.getDate())}T${twoChars(date.getHours())}:${twoChars(date.getMinutes())}`;
};
const convertLocalDatetimeToUTC = (value) => {
const re = /^([0-9]{4,})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2})/;
const match = re.exec(value);
const date = new Date(match[1], match[2] - 1, match[3], match[4], match[5]);
const fullISO8601 = date.toISOString();
return fullISO8601.slice(0, fullISO8601.indexOf('T') + 6);
};
Rails.delegate(document, '#form_admin_settings_registrations_mode', 'change', ({ target }) => onChangeRegistrationMode(target));
ready(() => {
const domainBlockSeverityInput = document.getElementById('domain_block_severity');
if (domainBlockSeverityInput) onDomainBlockSeverityChange(domainBlockSeverityInput);
const enableBootstrapTimelineAccounts = document.getElementById('form_admin_settings_enable_bootstrap_timeline_accounts');
if (enableBootstrapTimelineAccounts) onEnableBootstrapTimelineAccountsChange(enableBootstrapTimelineAccounts);
const registrationMode = document.getElementById('form_admin_settings_registrations_mode');
if (registrationMode) onChangeRegistrationMode(registrationMode);
const checkAllElement = document.querySelector('#batch_checkbox_all');
if (checkAllElement) {
const allCheckboxes = Array.from(document.querySelectorAll(batchCheckboxClassName));
checkAllElement.checked = allCheckboxes.every( (content) => content.checked);
checkAllElement.indeterminate = !checkAllElement.checked && allCheckboxes.some((content) => content.checked);
}
document.querySelector('a#add-instance-button')?.addEventListener('click', (e) => {
const domain = document.querySelector('input[type="text"]#by_domain')?.value;
if (domain) {
const url = new URL(event.target.href);
url.searchParams.set('_domain', domain);
e.target.href = url;
}
});
document.querySelectorAll('input[type="datetime-local"]').forEach(element => {
if (element.value) {
element.value = convertUTCDateTimeToLocal(element.value);
}
if (element.placeholder) {
element.placeholder = convertUTCDateTimeToLocal(element.placeholder);
}
});
Rails.delegate(document, 'form', 'submit', ({ target }) => {
target.querySelectorAll('input[type="datetime-local"]').forEach(element => {
if (element.value && element.validity.valid) {
element.value = convertLocalDatetimeToUTC(element.value);
}
});
});
const announcementStartsAt = document.querySelector('input[type="datetime-local"]#announcement_starts_at');
if (announcementStartsAt) {
setAnnouncementEndsAttributes(announcementStartsAt);
}
document.querySelectorAll('[data-admin-component]').forEach(element => {
const componentName = element.getAttribute('data-admin-component');
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>
<Component {...componentProps} />
</AdminComponent>,
);
});
}).catch(error => {
console.error(error);
});
});
});

View file

@ -0,0 +1,368 @@
import './public-path';
import { createRoot } from 'react-dom/client';
import Rails from '@rails/ujs';
import ready from '../mastodon/ready';
const setAnnouncementEndsAttributes = (target: HTMLInputElement) => {
const valid = target.value && target.validity.valid;
const element = document.querySelector<HTMLInputElement>(
'input[type="datetime-local"]#announcement_ends_at',
);
if (!element) return;
if (valid) {
element.classList.remove('optional');
element.required = true;
element.min = target.value;
} else {
element.classList.add('optional');
element.removeAttribute('required');
element.removeAttribute('min');
}
};
Rails.delegate(
document,
'input[type="datetime-local"]#announcement_starts_at',
'change',
({ target }) => {
if (target instanceof HTMLInputElement)
setAnnouncementEndsAttributes(target);
},
);
const batchCheckboxClassName = '.batch-checkbox input[type="checkbox"]';
const showSelectAll = () => {
const selectAllMatchingElement = document.querySelector(
'.batch-table__select-all',
);
selectAllMatchingElement?.classList.add('active');
};
const hideSelectAll = () => {
const selectAllMatchingElement = document.querySelector(
'.batch-table__select-all',
);
const hiddenField = document.querySelector<HTMLInputElement>(
'input#select_all_matching',
);
const selectedMsg = document.querySelector(
'.batch-table__select-all .selected',
);
const notSelectedMsg = document.querySelector(
'.batch-table__select-all .not-selected',
);
selectAllMatchingElement?.classList.remove('active');
selectedMsg?.classList.remove('active');
notSelectedMsg?.classList.add('active');
if (hiddenField) hiddenField.value = '0';
};
Rails.delegate(document, '#batch_checkbox_all', 'change', ({ target }) => {
if (!(target instanceof HTMLInputElement)) return;
const selectAllMatchingElement = document.querySelector(
'.batch-table__select-all',
);
document
.querySelectorAll<HTMLInputElement>(batchCheckboxClassName)
.forEach((content) => {
content.checked = target.checked;
});
if (selectAllMatchingElement) {
if (target.checked) {
showSelectAll();
} else {
hideSelectAll();
}
}
});
Rails.delegate(document, '.batch-table__select-all button', 'click', () => {
const hiddenField = document.querySelector<HTMLInputElement>(
'#select_all_matching',
);
if (!hiddenField) return;
const active = hiddenField.value === '1';
const selectedMsg = document.querySelector(
'.batch-table__select-all .selected',
);
const notSelectedMsg = document.querySelector(
'.batch-table__select-all .not-selected',
);
if (!selectedMsg || !notSelectedMsg) return;
if (active) {
hiddenField.value = '0';
selectedMsg.classList.remove('active');
notSelectedMsg.classList.add('active');
} else {
hiddenField.value = '1';
notSelectedMsg.classList.remove('active');
selectedMsg.classList.add('active');
}
});
Rails.delegate(document, batchCheckboxClassName, 'change', () => {
const checkAllElement = document.querySelector<HTMLInputElement>(
'input#batch_checkbox_all',
);
const selectAllMatchingElement = document.querySelector(
'.batch-table__select-all',
);
if (checkAllElement) {
const allCheckboxes = Array.from(
document.querySelectorAll<HTMLInputElement>(batchCheckboxClassName),
);
checkAllElement.checked = allCheckboxes.every((content) => content.checked);
checkAllElement.indeterminate =
!checkAllElement.checked &&
allCheckboxes.some((content) => content.checked);
if (selectAllMatchingElement) {
if (checkAllElement.checked) {
showSelectAll();
} else {
hideSelectAll();
}
}
}
});
Rails.delegate(
document,
'.filter-subset--with-select select',
'change',
({ target }) => {
if (target instanceof HTMLSelectElement) target.form?.submit();
},
);
const onDomainBlockSeverityChange = (target: HTMLSelectElement) => {
const rejectMediaDiv = document.querySelector(
'.input.with_label.domain_block_reject_media',
);
const rejectReportsDiv = document.querySelector(
'.input.with_label.domain_block_reject_reports',
);
if (rejectMediaDiv && rejectMediaDiv instanceof HTMLElement) {
rejectMediaDiv.style.display =
target.value === 'suspend' ? 'none' : 'block';
}
if (rejectReportsDiv && rejectReportsDiv instanceof HTMLElement) {
rejectReportsDiv.style.display =
target.value === 'suspend' ? 'none' : 'block';
}
};
Rails.delegate(document, '#domain_block_severity', 'change', ({ target }) => {
if (target instanceof HTMLSelectElement) onDomainBlockSeverityChange(target);
});
const onEnableBootstrapTimelineAccountsChange = (target: HTMLInputElement) => {
const bootstrapTimelineAccountsField =
document.querySelector<HTMLInputElement>(
'#form_admin_settings_bootstrap_timeline_accounts',
);
if (bootstrapTimelineAccountsField) {
bootstrapTimelineAccountsField.disabled = !target.checked;
if (target.checked) {
bootstrapTimelineAccountsField.parentElement?.classList.remove(
'disabled',
);
bootstrapTimelineAccountsField.parentElement?.parentElement?.classList.remove(
'disabled',
);
} else {
bootstrapTimelineAccountsField.parentElement?.classList.add('disabled');
bootstrapTimelineAccountsField.parentElement?.parentElement?.classList.add(
'disabled',
);
}
}
};
Rails.delegate(
document,
'#form_admin_settings_enable_bootstrap_timeline_accounts',
'change',
({ target }) => {
if (target instanceof HTMLInputElement)
onEnableBootstrapTimelineAccountsChange(target);
},
);
const onChangeRegistrationMode = (target: HTMLSelectElement) => {
const enabled = target.value === 'approved';
document
.querySelectorAll<HTMLElement>(
'.form_admin_settings_registrations_mode .warning-hint',
)
.forEach((warning_hint) => {
warning_hint.style.display = target.value === 'open' ? 'inline' : 'none';
});
document
.querySelectorAll<HTMLInputElement>(
'input#form_admin_settings_require_invite_text',
)
.forEach((input) => {
input.disabled = !enabled;
if (enabled) {
let element: HTMLElement | null = input;
do {
element.classList.remove('disabled');
element = element.parentElement;
} while (element && !element.classList.contains('fields-group'));
} else {
let element: HTMLElement | null = input;
do {
element.classList.add('disabled');
element = element.parentElement;
} while (element && !element.classList.contains('fields-group'));
}
});
};
const convertUTCDateTimeToLocal = (value: string) => {
const date = new Date(value + 'Z');
const twoChars = (x: number) => x.toString().padStart(2, '0');
return `${date.getFullYear()}-${twoChars(date.getMonth() + 1)}-${twoChars(date.getDate())}T${twoChars(date.getHours())}:${twoChars(date.getMinutes())}`;
};
function convertLocalDatetimeToUTC(value: string) {
const date = new Date(value);
const fullISO8601 = date.toISOString();
return fullISO8601.slice(0, fullISO8601.indexOf('T') + 6);
}
Rails.delegate(
document,
'#form_admin_settings_registrations_mode',
'change',
({ target }) => {
if (target instanceof HTMLSelectElement) onChangeRegistrationMode(target);
},
);
async function mountReactComponent(element: Element) {
const componentName = element.getAttribute('data-admin-component');
const stringProps = element.getAttribute('data-props');
if (!stringProps) return;
const componentProps = JSON.parse(stringProps) as object;
const { default: AdminComponent } = await import(
'@/mastodon/containers/admin_component'
);
const { default: Component } = (await import(
`@/mastodon/components/admin/${componentName}`
)) as { default: React.ComponentType };
const root = createRoot(element);
root.render(
<AdminComponent>
<Component {...componentProps} />
</AdminComponent>,
);
}
ready(() => {
const domainBlockSeveritySelect = document.querySelector<HTMLSelectElement>(
'select#domain_block_severity',
);
if (domainBlockSeveritySelect)
onDomainBlockSeverityChange(domainBlockSeveritySelect);
const enableBootstrapTimelineAccounts =
document.querySelector<HTMLInputElement>(
'input#form_admin_settings_enable_bootstrap_timeline_accounts',
);
if (enableBootstrapTimelineAccounts)
onEnableBootstrapTimelineAccountsChange(enableBootstrapTimelineAccounts);
const registrationMode = document.querySelector<HTMLSelectElement>(
'select#form_admin_settings_registrations_mode',
);
if (registrationMode) onChangeRegistrationMode(registrationMode);
const checkAllElement = document.querySelector<HTMLInputElement>(
'input#batch_checkbox_all',
);
if (checkAllElement) {
const allCheckboxes = Array.from(
document.querySelectorAll<HTMLInputElement>(batchCheckboxClassName),
);
checkAllElement.checked = allCheckboxes.every((content) => content.checked);
checkAllElement.indeterminate =
!checkAllElement.checked &&
allCheckboxes.some((content) => content.checked);
}
document
.querySelector('a#add-instance-button')
?.addEventListener('click', (e) => {
const domain = document.querySelector<HTMLInputElement>(
'input[type="text"]#by_domain',
)?.value;
if (domain && e.target instanceof HTMLAnchorElement) {
const url = new URL(e.target.href);
url.searchParams.set('_domain', domain);
e.target.href = url.toString();
}
});
document
.querySelectorAll<HTMLInputElement>('input[type="datetime-local"]')
.forEach((element) => {
if (element.value) {
element.value = convertUTCDateTimeToLocal(element.value);
}
if (element.placeholder) {
element.placeholder = convertUTCDateTimeToLocal(element.placeholder);
}
});
Rails.delegate(document, 'form', 'submit', ({ target }) => {
if (target instanceof HTMLFormElement)
target
.querySelectorAll<HTMLInputElement>('input[type="datetime-local"]')
.forEach((element) => {
if (element.value && element.validity.valid) {
element.value = convertLocalDatetimeToUTC(element.value);
}
});
});
const announcementStartsAt = document.querySelector<HTMLInputElement>(
'input[type="datetime-local"]#announcement_starts_at',
);
if (announcementStartsAt) {
setAnnouncementEndsAttributes(announcementStartsAt);
}
document.querySelectorAll('[data-admin-component]').forEach((element) => {
void mountReactComponent(element);
});
}).catch((reason) => {
throw reason;
});

View file

@ -65,7 +65,6 @@ function loaded() {
const timeFormat = new Intl.DateTimeFormat(locale, {
timeStyle: 'short',
hour12: false,
});
const formatMessage = ({ id, defaultMessage }, values) => {

View file

@ -192,6 +192,18 @@ table + p {
}
}
.email-dir-rtl {
direction: rtl;
[dir='rtl'] & {
direction: ltr;
}
}
.email-dir-ltr {
direction: ltr;
}
.email-padding-24 {
padding: 24px;
}
@ -216,6 +228,30 @@ table + p {
border-bottom: 1px solid #dfdee3;
}
.email-desktop-flex {
font-size: 0;
max-width: 740px;
margin-left: auto;
margin-right: auto;
&.email-dir-rtl > .email-desktop-column {
direction: ltr;
[dir='rtl'] & {
direction: rtl;
}
}
}
.email-desktop-column {
display: inline-block;
width: 100%;
max-width: none;
text-align: start;
vertical-align: top;
font-size: 16px;
}
// Header
.email-header-td {
padding: 16px 32px;
@ -312,6 +348,66 @@ table + p {
}
}
.email-header-card-table {
width: 100%;
border-collapse: separate;
overflow: hidden;
border-radius: 12px;
background-color: #fff;
border: 2px solid #fff;
box-shadow: 0 4px 16px 0 rgba(23, 6, 59, 8%);
}
.email-header-card {
position: relative;
max-height: 100px;
}
.email-header-card-banner-td {
border-radius: 12px 12px 0 0;
height: 80px;
background-color: #f3f2f5 !important;
background-position: center !important;
background-size: cover !important;
}
.email-header-card-body-td {
padding: 12px;
.email-btn-table {
width: 100%;
max-width: 212px;
}
}
.email-header-card-instance {
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
color: #17063b;
font-size: 14px;
line-height: 20px;
font-weight: 600;
&:only-of-type {
margin-bottom: 12px;
}
}
.email-header-card-description {
margin-bottom: 12px;
color: #746a89;
font-size: 12px;
line-height: 16px;
max-height: 32px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
// To make the design work with images off
// we create an empty div that overlaps with
// the rest of the content with a dark background.
@ -336,6 +432,16 @@ table + p {
mso-padding-alt: 32px;
}
.email-body-columns-td {
border-top: 1px solid #dfdee3;
padding: 32px 24px 8px;
}
.email-body-huge-padding-td {
padding: 110px 32px 32px;
mso-padding-alt: 32px;
}
.email-body-padding-td {
& > p {
font-size: 14px;
@ -353,6 +459,30 @@ table + p {
}
}
// Texts
.email-h2 {
margin-bottom: 4px;
color: #17063b;
font-size: 18px;
font-weight: 600;
line-height: 28px;
}
.email-h-sub {
margin-bottom: 16px;
color: #746a89;
font-size: 14px;
line-height: 16px;
}
.email-p {
margin-bottom: 16px;
color: #746a89;
font-size: 14px;
font-weight: 400;
line-height: 20px;
}
// Footer
.email-footer-td {
padding: 28px 32px 32px;
@ -539,8 +669,13 @@ table + p {
background-color: #fff;
}
.email-checklist-checked {
border-color: #c4e6d7;
background-color: #eaf6f1;
}
.email-checklist-td {
padding: 16px;
padding: 16px 16px 6px;
}
.email-checklist-icons-td {
@ -576,10 +711,15 @@ table + p {
font-size: 14px;
font-weight: 600;
line-height: 16.8px;
.email-checklist-checked & {
color: #746a89;
text-decoration: line-through;
}
}
p {
margin: 0 0 2px;
margin: 0 0 12px;
color: #746a89;
font-size: 14px;
line-height: 16.8px;
@ -597,6 +737,194 @@ table + p {
padding-left: 10px;
padding-right: 10px;
}
div + div {
margin-inline-start: auto;
margin-bottom: 12px;
}
}
// Welcome email
.email-welcome-apps-btns {
font-size: 12px;
line-height: 44px;
}
.email-column-td {
padding: 0 8px;
vertical-align: top;
}
.email-link-with-arrow {
color: #6364ff;
font-size: 14px;
font-weight: 600;
line-height: 16.8px;
&:hover {
color: #563acc !important;
}
span {
font-size: 12px;
font-weight: 400;
}
}
.email-column-action-td {
padding: 24px 0;
color: #6364ff;
font-size: 14px;
font-weight: 600;
line-height: 16.8px;
text-align: center;
}
// Follow and hashtags
.email-mini-wrapper-td {
padding: 4px 0;
table {
table-layout: fixed;
}
}
.email-mini-td {
border-radius: 12px;
border: 1px solid #e8e6eb;
background-color: #fff;
padding: 15px 16px;
}
.email-mini-follow-img-td {
width: 40px;
vertical-align: top;
img {
border-radius: 8px;
}
}
.email-mini-follow-text-td {
padding-left: 8px;
padding-right: 16px;
vertical-align: top;
h3 {
color: #17063b;
font-size: 14px;
font-weight: 600;
line-height: 20px;
}
p {
color: #746a89;
font-size: 12px;
font-weight: 400;
line-height: 16px;
}
}
.email-mini-follow-btn-td {
width: 68px;
vertical-align: top;
.email-btn-table {
width: 100%;
}
.email-btn-td {
mso-padding-alt: 10px;
}
.email-btn-a {
padding-left: 10px;
padding-right: 10px;
}
}
.email-mini-hashtag-td {
height: 40px;
td {
vertical-align: middle;
}
h3 {
color: #17063b;
font-size: 14px;
font-weight: 600;
line-height: 20px;
}
p {
color: #746a89;
font-size: 12px;
font-weight: 400;
line-height: 16px;
word-break: break-all;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.email-mini-hashtag-img-td {
width: 40px;
height: 20px;
white-space: nowrap;
text-indent: -2px;
font-size: 0;
& + td {
padding-left: 8px;
}
}
.email-mini-hashtag-img-span {
display: inline-block;
max-width: 12px;
font-size: 12px;
img {
width: 16px;
height: 16px;
border-radius: 50%;
max-width: none;
border: 2px solid #fff;
vertical-align: middle;
}
}
// Extra content on light purple background
.email-extra-wave {
height: 42px;
background-image: url('../images/mailer-new/welcome/purple-extra-soft-wave.png');
background-position: bottom center;
background-repeat: no-repeat;
}
.email-extra-td {
padding: 32px 32px 24px;
background-color: #f0f0ff;
background-image: url('../images/mailer-new/welcome/purple-extra-soft-spacer.png'); // Using an image to maintain the color even in forced dark modes
.email-column-td {
padding-top: 8px;
padding-bottom: 8px;
}
}
// Feature card
.email-feature-wrapper-td {
padding: 8px 0;
}
.email-feature-td {
padding: 24px;
background-color: #fff;
border: 1px solid #e8e6eb;
border-radius: 12px;
}
// Responsive
@ -617,4 +945,21 @@ table + p {
.email-desktop-flex {
display: flex;
}
.email-header-left {
padding-right: 32px;
}
.email-header-right {
width: 240px;
margin-inline-start: auto;
}
.email-desktop-column {
max-width: 346px !important;
}
.email-desktop-text-right {
text-align: right;
}
}

View file

@ -5485,10 +5485,6 @@ a.status-card {
pointer-events: auto;
opacity: 1;
}
@media screen and (min-width: $no-gap-breakpoint) {
inset-inline-start: 16px - 2px;
}
}
.icon-search {
@ -8697,7 +8693,7 @@ noscript {
.search__input {
border: 1px solid lighten($ui-base-color, 8%);
padding: 10px;
padding-inline-end: 28px;
padding-inline-end: 30px;
}
.search__popout {