Merge remote-tracking branch 'parent/main' into upstream-20240304
|
@ -4,7 +4,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
|||
before_action -> { authorize_if_got_token! :read, :'read:statuses' }
|
||||
before_action :set_account
|
||||
|
||||
after_action :insert_pagination_headers, unless: -> { truthy_param?(:pinned) }
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
def index
|
||||
cache_if_unauthenticated!
|
||||
|
|
|
@ -127,7 +127,7 @@ module LanguagesHelper
|
|||
om: ['Oromo', 'Afaan Oromoo'].freeze,
|
||||
or: ['Oriya', 'ଓଡ଼ିଆ'].freeze,
|
||||
os: ['Ossetian', 'ирон æвзаг'].freeze,
|
||||
pa: ['Panjabi', 'ਪੰਜਾਬੀ'].freeze,
|
||||
pa: ['Punjabi', 'ਪੰਜਾਬੀ'].freeze,
|
||||
pi: ['Pāli', 'पाऴि'].freeze,
|
||||
pl: ['Polish', 'Polski'].freeze,
|
||||
ps: ['Pashto', 'پښتو'].freeze,
|
||||
|
@ -191,15 +191,18 @@ module LanguagesHelper
|
|||
chr: ['Cherokee', 'ᏣᎳᎩ ᎦᏬᏂᎯᏍᏗ'].freeze,
|
||||
ckb: ['Sorani (Kurdish)', 'سۆرانی'].freeze,
|
||||
cnr: ['Montenegrin', 'crnogorski'].freeze,
|
||||
csb: ['Kashubian', 'Kaszëbsczi'].freeze,
|
||||
jbo: ['Lojban', 'la .lojban.'].freeze,
|
||||
kab: ['Kabyle', 'Taqbaylit'].freeze,
|
||||
ldn: ['Láadan', 'Láadan'].freeze,
|
||||
lfn: ['Lingua Franca Nova', 'lingua franca nova'].freeze,
|
||||
pdc: ['Pennsylvania Dutch', 'Pennsilfaani-Deitsch'].freeze,
|
||||
sco: ['Scots', 'Scots'].freeze,
|
||||
sma: ['Southern Sami', 'Åarjelsaemien Gïele'].freeze,
|
||||
smj: ['Lule Sami', 'Julevsámegiella'].freeze,
|
||||
szl: ['Silesian', 'ślůnsko godka'].freeze,
|
||||
tok: ['Toki Pona', 'toki pona'].freeze,
|
||||
vai: ['Vai', 'ꕙꔤ'].freeze,
|
||||
xal: ['Kalmyk', 'Хальмг келн'].freeze,
|
||||
zba: ['Balaibalan', 'باليبلن'].freeze,
|
||||
zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze,
|
||||
|
|
21
app/javascript/images/mailer-new/heading/LICENSE
Normal 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.
|
1
app/javascript/images/mailer-new/heading/README.md
Normal file
|
@ -0,0 +1 @@
|
|||
Images in this folder are based on [Tabler.io icons](https://tabler.io/icons).
|
BIN
app/javascript/images/mailer-new/store-icons/btn-app-store.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
app/javascript/images/mailer-new/store-icons/btn-google-play.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
21
app/javascript/images/mailer-new/welcome-icons/LICENSE
Normal 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.
|
1
app/javascript/images/mailer-new/welcome-icons/README.md
Normal file
|
@ -0,0 +1 @@
|
|||
Images in this folder are based on [Tabler.io icons](https://tabler.io/icons).
|
Before Width: | Height: | Size: 547 B After Width: | Height: | Size: 547 B |
BIN
app/javascript/images/mailer-new/welcome-icons/apps_step-on.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 505 B After Width: | Height: | Size: 505 B |
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 688 B After Width: | Height: | Size: 688 B |
BIN
app/javascript/images/mailer-new/welcome-icons/post_step-on.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 709 B After Width: | Height: | Size: 709 B |
BIN
app/javascript/images/mailer-new/welcome-icons/share_step-on.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
app/javascript/images/mailer-new/welcome/feature_audience.png
Normal file
After Width: | Height: | Size: 94 KiB |
BIN
app/javascript/images/mailer-new/welcome/feature_control.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
app/javascript/images/mailer-new/welcome/feature_creativity.png
Normal file
After Width: | Height: | Size: 127 KiB |
BIN
app/javascript/images/mailer-new/welcome/feature_moderation.png
Normal file
After Width: | Height: | Size: 98 KiB |
After Width: | Height: | Size: 939 B |
After Width: | Height: | Size: 2.5 KiB |
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -53,7 +53,6 @@ const messages = defineMessages({
|
|||
});
|
||||
|
||||
const dateFormatOptions = {
|
||||
hour12: false,
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: '2-digit',
|
||||
|
|
|
@ -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'>
|
||||
|
|
|
@ -104,7 +104,6 @@ const dateFormatOptions = {
|
|||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
};
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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ų}}",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -668,7 +668,7 @@
|
|||
"status.mute": "靜音 @{name}",
|
||||
"status.mute_conversation": "靜音對話",
|
||||
"status.open": "展開此嘟文",
|
||||
"status.pin": "釘選到個人檔案頁面",
|
||||
"status.pin": "釘選至個人檔案頁面",
|
||||
"status.pinned": "釘選嘟文",
|
||||
"status.read_more": "閱讀更多",
|
||||
"status.reblog": "轉嘟",
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
368
app/javascript/packs/admin.tsx
Normal 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;
|
||||
});
|
|
@ -65,7 +65,6 @@ function loaded() {
|
|||
|
||||
const timeFormat = new Intl.DateTimeFormat(locale, {
|
||||
timeStyle: 'short',
|
||||
hour12: false,
|
||||
});
|
||||
|
||||
const formatMessage = ({ id, defaultMessage }, values) => {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -28,14 +28,17 @@ class Admin::Metrics::Measure::TagServersMeasure < Admin::Metrics::Measure::Base
|
|||
def sql_query_string
|
||||
<<~SQL.squish
|
||||
SELECT axis.*, (
|
||||
SELECT count(distinct accounts.domain) AS value
|
||||
FROM statuses
|
||||
INNER JOIN statuses_tags ON statuses.id = statuses_tags.status_id
|
||||
INNER JOIN accounts ON statuses.account_id = accounts.id
|
||||
WHERE statuses_tags.tag_id = :tag_id
|
||||
AND statuses.id BETWEEN :earliest_status_id AND :latest_status_id
|
||||
AND date_trunc('day', statuses.created_at)::date = axis.period
|
||||
)
|
||||
WITH tag_servers AS (
|
||||
SELECT DISTINCT accounts.domain
|
||||
FROM statuses
|
||||
INNER JOIN statuses_tags ON statuses.id = statuses_tags.status_id
|
||||
INNER JOIN accounts ON statuses.account_id = accounts.id
|
||||
WHERE statuses_tags.tag_id = :tag_id
|
||||
AND statuses.id BETWEEN :earliest_status_id AND :latest_status_id
|
||||
AND date_trunc('day', statuses.created_at)::date = axis.period
|
||||
)
|
||||
SELECT COUNT(*) FROM tag_servers
|
||||
) AS value
|
||||
FROM (
|
||||
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
|
||||
) as axis
|
||||
|
|
|
@ -135,6 +135,12 @@ class UserMailer < Devise::Mailer
|
|||
|
||||
return unless @resource.active_for_authentication?
|
||||
|
||||
@suggestions = AccountSuggestions.new(@resource.account).get(5)
|
||||
@tags = Trends.tags.query.allowed.limit(5)
|
||||
@has_account_fields = @resource.account.display_name.present? || @resource.account.note.present? || @resource.account.avatar.present?
|
||||
@has_active_relationships = @resource.account.active_relationships.exists?
|
||||
@has_statuses = @resource.account.statuses.exists?
|
||||
|
||||
I18n.with_locale(locale) do
|
||||
mail subject: default_i18n_subject
|
||||
end
|
||||
|
|
|
@ -493,7 +493,7 @@ class User < ApplicationRecord
|
|||
BootstrapTimelineWorker.perform_async(account_id)
|
||||
ActivityTracker.increment('activity:accounts:local')
|
||||
ActivityTracker.record('activity:logins', id)
|
||||
UserMailer.welcome(self).deliver_later
|
||||
UserMailer.welcome(self).deliver_later(wait: 1.hour)
|
||||
TriggerWebhookWorker.perform_async('account.approved', 'Account', account_id)
|
||||
reset_registration_limit_caches!
|
||||
end
|
||||
|
|
|
@ -41,6 +41,7 @@ class UserRole < ApplicationRecord
|
|||
}.freeze
|
||||
|
||||
EVERYONE_ROLE_ID = -99
|
||||
NOBODY_POSITION = -1
|
||||
|
||||
module Flags
|
||||
NONE = 0
|
||||
|
@ -108,7 +109,7 @@ class UserRole < ApplicationRecord
|
|||
has_many :users, inverse_of: :role, foreign_key: 'role_id', dependent: :nullify
|
||||
|
||||
def self.nobody
|
||||
@nobody ||= UserRole.new(permissions: Flags::NONE, position: -1)
|
||||
@nobody ||= UserRole.new(permissions: Flags::NONE, position: NOBODY_POSITION)
|
||||
end
|
||||
|
||||
def self.everyone
|
||||
|
@ -177,7 +178,7 @@ class UserRole < ApplicationRecord
|
|||
end
|
||||
|
||||
def set_position
|
||||
self.position = -1 if everyone?
|
||||
self.position = NOBODY_POSITION if everyone?
|
||||
end
|
||||
|
||||
def validate_own_role_edition
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
%table.email-btn-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-btn-td
|
||||
= link_to "#{text} ➜", url, class: 'email-btn-a email-btn-hover'
|
||||
- if defined?(has_arrow) && !has_arrow
|
||||
= link_to text, url, class: 'email-btn-a email-btn-hover'
|
||||
- else
|
||||
= link_to "#{text} ➜", url, class: 'email-btn-a email-btn-hover'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
%table.email-w-full.email-checklist-wrapper-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-checklist-wrapper-td
|
||||
%table.email-w-full.email-checklist-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%table.email-w-full.email-checklist-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation', class: ('email-checklist-checked' if defined?(checked) && checked) }
|
||||
%tr
|
||||
%td.email-checklist-td
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
|
@ -15,15 +15,25 @@
|
|||
- else
|
||||
= image_tag frontend_asset_url('images/mailer-new/welcome/checkbox-off.png'), alt: '', width: 20, height: 20
|
||||
%td.email-checklist-icons-step-td
|
||||
- if defined?(step_image_url)
|
||||
= image_tag step_image_url, alt: '', width: 40, height: 40
|
||||
- if defined?(key)
|
||||
= image_tag frontend_asset_url("images/mailer-new/welcome-icons/#{key}-#{checked ? 'on' : 'off'}.png"), alt: '', width: 40, height: 40
|
||||
%td.email-checklist-text-td
|
||||
.email-desktop-flex
|
||||
/[if mso]
|
||||
<table border="0" cellpadding="0" cellspacing="0" align="center" style="width:100%;" role="presentation"><tr><td vertical-align:top;">
|
||||
%div
|
||||
- if defined?(title)
|
||||
%h3= title
|
||||
- if defined?(text)
|
||||
%p= text
|
||||
/[if mso]
|
||||
</td><td style="vertical-align:top;">
|
||||
%div
|
||||
- if defined?(button_text) && defined?(button_url) && defined?(checked) && !checked
|
||||
= render 'application/mailer/button', text: button_text, url: button_url
|
||||
- if defined?(show_apps_buttons) && show_apps_buttons
|
||||
.email-welcome-apps-btns
|
||||
= link_to image_tag(frontend_asset_url('images/mailer-new/store-icons/btn-app-store.png'), alt: t('user_mailer.welcome.apps_ios_action'), width: 120, height: 40), 'https://apps.apple.com/app/mastodon-for-iphone-and-ipad/id1571998974'
|
||||
= link_to image_tag(frontend_asset_url('images/mailer-new/store-icons/btn-google-play.png'), alt: t('user_mailer.welcome.apps_android_action'), width: 120, height: 40), 'https://play.google.com/store/apps/details?id=org.joinmastodon.android'
|
||||
- elsif defined?(button_text) && defined?(button_url) && defined?(checked) && !checked
|
||||
= render 'application/mailer/button', text: button_text, url: button_url, has_arrow: false
|
||||
/[if mso]
|
||||
</td></tr></table>
|
||||
|
|
32
app/views/application/mailer/_feature.html.haml
Normal file
|
@ -0,0 +1,32 @@
|
|||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-feature-wrapper-td
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-feature-td
|
||||
.email-desktop-flex{ class: ('email-dir-rtl' if defined?(text_first_on_desktop) && !text_first_on_desktop) }
|
||||
/[if mso]
|
||||
<table border="0" cellpadding="0" cellspacing="0" align="center" style="width:100%;" role="presentation"><tr><td style="width:50%; vertical-align:top;">
|
||||
.email-desktop-column
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-column-td
|
||||
- if defined?(feature_title)
|
||||
%h2.email-h2= feature_title
|
||||
- if defined?(feature_text)
|
||||
%p.email-p= feature_text
|
||||
- if defined?(feature_btn_url)
|
||||
= link_to '', href: feature_btn_url, class: 'email-link-with-arrow' do
|
||||
#{t('user_mailer.welcome.feature_action')}
|
||||
%span= '❯'
|
||||
/[if mso]
|
||||
</td><td style="width:50%; vertical-align:top;">
|
||||
.email-desktop-column
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-column-td
|
||||
- if defined?(key)
|
||||
%p{ class: ('email-desktop-text-right' if defined?(text_first_on_desktop) && text_first_on_desktop) }
|
||||
= image_tag frontend_asset_url("images/mailer-new/welcome/#{key}.png"), alt: '', width: 240, height: 230
|
||||
/[if mso]
|
||||
</td></tr></table>
|
15
app/views/application/mailer/_follow.html.haml
Normal file
|
@ -0,0 +1,15 @@
|
|||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-mini-wrapper-td
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-mini-td
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-mini-follow-img-td
|
||||
= image_tag full_asset_url(follow.account.avatar.url), alt: '', width: 40, height: 40
|
||||
%td.email-mini-follow-text-td
|
||||
%h3= follow.account.display_name.presence || follow.account.username
|
||||
%p @#{follow.account.pretty_acct}
|
||||
%td.email-mini-follow-btn-td
|
||||
= render 'application/mailer/button', text: t('user_mailer.welcome.follow_action'), url: web_url("@#{follow.account.acct}"), has_arrow: false
|
20
app/views/application/mailer/_hashtag.html.haml
Normal file
|
@ -0,0 +1,20 @@
|
|||
- accounts = hashtag.statuses.with_public_visibility.joins(:account).merge(Account.without_suspended.without_silenced).includes(:account).limit(3).map(&:account)
|
||||
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-mini-wrapper-td
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-mini-td
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-mini-hashtag-td{ height: 40 }
|
||||
%h3 ##{hashtag.display_name}
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-mini-hashtag-img-td
|
||||
- accounts.each do |account|
|
||||
%span.email-mini-hashtag-img-span
|
||||
= image_tag full_asset_url(account.avatar.url), alt: '', width: 16, height: 16
|
||||
%td
|
||||
%p= t('user_mailer.welcome.hashtags_recent_count', people: number_with_delimiter(hashtag.history.aggregate(2.days.ago.to_date..Time.zone.today).accounts), days: 2)
|
|
@ -1,25 +1,76 @@
|
|||
- instance_presenter = InstancePresenter.new
|
||||
|
||||
= content_for :heading do
|
||||
= render 'application/mailer/heading', heading_title: t('user_mailer.welcome.title', name: @resource.account.username), heading_subtitle: t('user_mailer.welcome.explanation')
|
||||
.email-desktop-flex
|
||||
.email-header-left
|
||||
= render 'application/mailer/heading', heading_title: t('user_mailer.welcome.title', name: @resource.account.username), heading_subtitle: t('user_mailer.welcome.explanation')
|
||||
.email-header-right
|
||||
.email-header-card
|
||||
%table.email-header-card-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-header-card-banner-td{ height: 140, background: full_asset_url(instance_presenter.thumbnail&.file&.url(:'@1x') || frontend_asset_path('images/preview.png')) }
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-header-card-body-td
|
||||
%p.email-header-card-instance= @instance
|
||||
- if instance_presenter.description.present?
|
||||
%p.email-header-card-description= instance_presenter.description
|
||||
= render 'application/mailer/button', text: t('user_mailer.welcome.sign_in_action'), url: new_user_session_url, has_arrow: false
|
||||
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-body-padding-td
|
||||
%table.email-inner-card-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-inner-card-td-without-padding
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-prose.email-padding-24
|
||||
%p
|
||||
%b= t 'user_mailer.welcome.full_handle'
|
||||
= render 'application/mailer/frame', text: "#{@resource.account.username}@#{@instance}"
|
||||
%p= t 'user_mailer.welcome.full_handle_hint', instance: @instance
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-border-top.email-prose.email-padding-24
|
||||
%p= t 'user_mailer.welcome.edit_profile_step'
|
||||
= render 'application/mailer/button', text: t('user_mailer.welcome.edit_profile_action'), url: settings_profile_url
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-border-top.email-prose.email-padding-24
|
||||
%p= t 'user_mailer.welcome.edit_profile_step'
|
||||
= render 'application/mailer/button', text: t('user_mailer.welcome.final_action'), url: web_url
|
||||
%td.email-body-huge-padding-td
|
||||
%h2.email-h2= t('user_mailer.welcome.checklist_title')
|
||||
%p.email-h-sub= t('user_mailer.welcome.checklist_subtitle')
|
||||
= render 'application/mailer/checklist', key: 'edit_profile_step', title: t('user_mailer.welcome.edit_profile_title'), text: t('user_mailer.welcome.edit_profile_step'), checked: @has_account_fields, button_text: t('user_mailer.welcome.edit_profile_action'), button_url: web_url('start/profile')
|
||||
= render 'application/mailer/checklist', key: 'follow_step', title: t('user_mailer.welcome.follow_title'), text: t('user_mailer.welcome.follow_step'), checked: @has_active_relationships, button_text: t('user_mailer.welcome.follow_action'), button_url: web_url('start/follows')
|
||||
= render 'application/mailer/checklist', key: 'post_step', title: t('user_mailer.welcome.post_title'), text: t('user_mailer.welcome.post_step'), checked: @has_statuses, button_text: t('user_mailer.welcome.post_action'), button_url: web_url
|
||||
= render 'application/mailer/checklist', key: 'share_step', title: t('user_mailer.welcome.share_title'), text: t('user_mailer.welcome.share_step'), checked: false, button_text: t('user_mailer.welcome.share_action'), button_url: web_url('start/share')
|
||||
= render 'application/mailer/checklist', key: 'apps_step', title: t('user_mailer.welcome.apps_title'), text: t('user_mailer.welcome.apps_step'), checked: false, show_apps_buttons: true
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-body-columns-td
|
||||
.email-desktop-flex
|
||||
/[if mso]
|
||||
<table border="0" cellpadding="0" cellspacing="0" align="center" style="width:100%;" role="presentation"><tr><td style="width:50%; vertical-align:top;">
|
||||
.email-desktop-column
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-column-td
|
||||
%h2.email-h2= t('user_mailer.welcome.follows_title')
|
||||
%p.email-h-sub= t('user_mailer.welcome.follows_subtitle')
|
||||
= render partial: 'application/mailer/follow', collection: @suggestions
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-column-action-td
|
||||
= link_to '', href: web_url('explore/suggestions'), class: 'email-link-with-arrow' do
|
||||
= t('user_mailer.welcome.follows_view_more')
|
||||
%span= '❯'
|
||||
/[if mso]
|
||||
</td><td style="width:50%; vertical-align:top;">
|
||||
.email-desktop-column
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-column-td
|
||||
%h2.email-h2= t('user_mailer.welcome.hashtags_title')
|
||||
%p.email-h-sub= t('user_mailer.welcome.hashtags_subtitle')
|
||||
= render partial: 'application/mailer/hashtag', collection: @tags
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-column-action-td
|
||||
= link_to '', href: web_url('explore/tags'), class: 'email-link-with-arrow' do
|
||||
= t('user_mailer.welcome.hashtags_view_more')
|
||||
%span= '❯'
|
||||
/[if mso]
|
||||
</td></tr></table>
|
||||
.email-extra-wave
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-extra-td
|
||||
= render 'application/mailer/feature', key: 'feature_control', feature_title: t('user_mailer.welcome.feature_control_title'), feature_text: t('user_mailer.welcome.feature_control'), text_first_on_desktop: true
|
||||
= render 'application/mailer/feature', key: 'feature_audience', feature_title: t('user_mailer.welcome.feature_audience_title'), feature_text: t('user_mailer.welcome.feature_audience'), text_first_on_desktop: false
|
||||
= render 'application/mailer/feature', key: 'feature_moderation', feature_title: t('user_mailer.welcome.feature_moderation_title'), feature_text: t('user_mailer.welcome.feature_moderation'), text_first_on_desktop: true
|
||||
= render 'application/mailer/feature', key: 'feature_creativity', feature_title: t('user_mailer.welcome.feature_creativity_title'), feature_text: t('user_mailer.welcome.feature_creativity'), text_first_on_desktop: false
|
||||
|
|
|
@ -1,16 +1,78 @@
|
|||
<%= t 'user_mailer.welcome.title', name: @resource.account.username %> <%= t 'user_mailer.welcome.explanation' %>
|
||||
|
||||
===
|
||||
|
||||
<%= t 'user_mailer.welcome.full_handle' %> (<%= "@#{@resource.account.local_username_and_domain}" %>)
|
||||
<%= t 'user_mailer.welcome.full_handle_hint', instance: @instance %>
|
||||
|
||||
---
|
||||
|
||||
<%= t 'user_mailer.welcome.edit_profile_step' %>
|
||||
<%= t('user_mailer.welcome.sign_in_action') %>
|
||||
===
|
||||
<%= new_user_session_url %>
|
||||
|
||||
=> <%= settings_profile_url %>
|
||||
---
|
||||
|
||||
<%= t 'user_mailer.welcome.final_step' %>
|
||||
<%= t('user_mailer.welcome.checklist_title') %>
|
||||
===
|
||||
<%= t('user_mailer.welcome.checklist_subtitle') %>
|
||||
|
||||
=> <%= web_url %>
|
||||
1. <%= t('user_mailer.welcome.edit_profile_title') %>
|
||||
<%= t('user_mailer.welcome.edit_profile_step') %>
|
||||
* <%= web_url('start/profile') %>
|
||||
|
||||
2. <%= t('user_mailer.welcome.follow_title') %>
|
||||
<%= t('user_mailer.welcome.follow_step') %>
|
||||
* <%= web_url('start/follows') %>
|
||||
|
||||
3. <%= t('user_mailer.welcome.post_title') %>
|
||||
<%= t('user_mailer.welcome.post_step') %>
|
||||
* <%= web_url %>
|
||||
|
||||
4. <%= t('user_mailer.welcome.share_title') %>
|
||||
<%= t('user_mailer.welcome.share_step') %>
|
||||
* <%= web_url('start/share') %>
|
||||
|
||||
5. <%= t('user_mailer.welcome.apps_title') %>
|
||||
<%= t('user_mailer.welcome.apps_step') %>
|
||||
* iOS: https://apps.apple.com/app/mastodon-for-iphone-and-ipad/id1571998974
|
||||
* Android: https://play.google.com/store/apps/details?id=org.joinmastodon.android
|
||||
|
||||
---
|
||||
|
||||
<%= t('user_mailer.welcome.follows_title') %>
|
||||
===
|
||||
<%= t('user_mailer.welcome.follows_subtitle') %>
|
||||
|
||||
<%- @suggestions.each do |suggestion| %>
|
||||
* <%= suggestion.account.display_name.presence || suggestion.account.username %> · @<%= suggestion.account.pretty_acct %>
|
||||
<%= web_url("@#{suggestion.account.acct}") %>
|
||||
<%- end %>
|
||||
|
||||
<%= web_url('explore/suggestions') %>
|
||||
|
||||
---
|
||||
|
||||
<%= t('user_mailer.welcome.hashtags_title') %>
|
||||
===
|
||||
<%= t('user_mailer.welcome.hashtags_subtitle') %>
|
||||
|
||||
<%- @tags.each do |tag| %>
|
||||
* #<%= tag.display_name %> · <%= t('user_mailer.welcome.hashtags_recent_count', people: number_with_delimiter(tag.history.aggregate(2.days.ago.to_date..Time.zone.today).accounts), days: 2) %>
|
||||
<%= tag_url(tag) %>
|
||||
<%- end %>
|
||||
|
||||
<%= web_url('explore/tags') %>
|
||||
|
||||
---
|
||||
|
||||
<%= t('user_mailer.welcome.feature_control_title') %>
|
||||
===
|
||||
<%= word_wrap t('user_mailer.welcome.feature_control') %>
|
||||
|
||||
<%= t('user_mailer.welcome.feature_audience_title') %>
|
||||
===
|
||||
<%= word_wrap t('user_mailer.welcome.feature_audience') %>
|
||||
|
||||
<%= t('user_mailer.welcome.feature_moderation_title') %>
|
||||
===
|
||||
<%= word_wrap t('user_mailer.welcome.feature_moderation') %>
|
||||
|
||||
<%= t('user_mailer.welcome.feature_creativity_title') %>
|
||||
===
|
||||
<%= word_wrap t('user_mailer.welcome.feature_creativity') %>
|
||||
|
|