forked from mirrors/catstodon
merge with catstodon/main
parent
60d54db810
commit
df00e5e6cb
@ -1,7 +1,9 @@
|
||||
[production]
|
||||
defaults
|
||||
not IE 11
|
||||
> 0.2%
|
||||
ios >= 15.6
|
||||
not dead
|
||||
not OperaMini all
|
||||
|
||||
[development]
|
||||
supports es6-module
|
||||
|
@ -0,0 +1,18 @@
|
||||
name: Check formatting
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Javascript environment
|
||||
uses: ./.github/actions/setup-javascript
|
||||
|
||||
- name: Check formatting with Prettier
|
||||
run: yarn format:check
|
@ -1,38 +0,0 @@
|
||||
name: JSON Linting
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'dependabot/**'
|
||||
- 'renovate/**'
|
||||
paths:
|
||||
- 'package.json'
|
||||
- 'yarn.lock'
|
||||
- '.nvmrc'
|
||||
- '.prettier*'
|
||||
- '**/*.json'
|
||||
- '.github/workflows/lint-json.yml'
|
||||
- '!app/javascript/mastodon/locales/*.json'
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- 'package.json'
|
||||
- 'yarn.lock'
|
||||
- '.nvmrc'
|
||||
- '.prettier*'
|
||||
- '**/*.json'
|
||||
- '.github/workflows/lint-json.yml'
|
||||
- '!app/javascript/mastodon/locales/*.json'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Javascript environment
|
||||
uses: ./.github/actions/setup-javascript
|
||||
|
||||
- name: Prettier
|
||||
run: yarn lint:json
|
@ -1,38 +0,0 @@
|
||||
name: Markdown Linting
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'dependabot/**'
|
||||
- 'renovate/**'
|
||||
paths:
|
||||
- '.github/workflows/lint-md.yml'
|
||||
- '.nvmrc'
|
||||
- '.prettier*'
|
||||
- '**/*.md'
|
||||
- '!AUTHORS.md'
|
||||
- 'package.json'
|
||||
- 'yarn.lock'
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/lint-md.yml'
|
||||
- '.nvmrc'
|
||||
- '.prettier*'
|
||||
- '**/*.md'
|
||||
- '!AUTHORS.md'
|
||||
- 'package.json'
|
||||
- 'yarn.lock'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Javascript environment
|
||||
uses: ./.github/actions/setup-javascript
|
||||
|
||||
- name: Prettier
|
||||
run: yarn lint:md
|
@ -1,40 +0,0 @@
|
||||
name: YML Linting
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'dependabot/**'
|
||||
- 'renovate/**'
|
||||
paths:
|
||||
- 'package.json'
|
||||
- 'yarn.lock'
|
||||
- '.nvmrc'
|
||||
- '.prettier*'
|
||||
- '**/*.yaml'
|
||||
- '**/*.yml'
|
||||
- '.github/workflows/lint-yml.yml'
|
||||
- '!config/locales/*.yml'
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- 'package.json'
|
||||
- 'yarn.lock'
|
||||
- '.nvmrc'
|
||||
- '.prettier*'
|
||||
- '**/*.yaml'
|
||||
- '**/*.yml'
|
||||
- '.github/workflows/lint-yml.yml'
|
||||
- '!config/locales/*.yml'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Javascript environment
|
||||
uses: ./.github/actions/setup-javascript
|
||||
|
||||
- name: Prettier
|
||||
run: yarn lint:yml
|
@ -1,13 +0,0 @@
|
||||
# This configuration was generated by
|
||||
# `haml-lint --auto-gen-config`
|
||||
# on 2024-01-09 11:30:07 -0500 using Haml-Lint version 0.53.0.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the lints are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of Haml-Lint, may require this file to be generated again.
|
||||
|
||||
linters:
|
||||
# Offense count: 1
|
||||
LineLength:
|
||||
exclude:
|
||||
- 'app/views/admin/roles/_form.html.haml'
|
@ -1,4 +1 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn lint-staged
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Notifications::PoliciesController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:notifications' }, only: :show
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:notifications' }, only: :update
|
||||
|
||||
before_action :require_user!
|
||||
before_action :set_policy
|
||||
|
||||
def show
|
||||
render json: @policy, serializer: REST::NotificationPolicySerializer
|
||||
end
|
||||
|
||||
def update
|
||||
@policy.update!(resource_params)
|
||||
render json: @policy, serializer: REST::NotificationPolicySerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_policy
|
||||
@policy = NotificationPolicy.find_or_initialize_by(account: current_account)
|
||||
|
||||
with_read_replica do
|
||||
@policy.summarize!
|
||||
end
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.permit(
|
||||
:filter_not_following,
|
||||
:filter_not_followers,
|
||||
:filter_new_accounts,
|
||||
:filter_private_mentions
|
||||
)
|
||||
end
|
||||
end
|
@ -0,0 +1,75 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Notifications::RequestsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:notifications' }, only: :index
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:notifications' }, except: :index
|
||||
|
||||
before_action :require_user!
|
||||
before_action :set_request, except: :index
|
||||
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
def index
|
||||
with_read_replica do
|
||||
@requests = load_requests
|
||||
@relationships = relationships
|
||||
end
|
||||
|
||||
render json: @requests, each_serializer: REST::NotificationRequestSerializer, relationships: @relationships
|
||||
end
|
||||
|
||||
def show
|
||||
render json: @request, serializer: REST::NotificationRequestSerializer
|
||||
end
|
||||
|
||||
def accept
|
||||
AcceptNotificationRequestService.new.call(@request)
|
||||
render_empty
|
||||
end
|
||||
|
||||
def dismiss
|
||||
@request.update!(dismissed: true)
|
||||
render_empty
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_requests
|
||||
requests = NotificationRequest.where(account: current_account).where(dismissed: truthy_param?(:dismissed) || false).includes(:last_status, from_account: [:account_stat, :user]).to_a_paginated_by_id(
|
||||
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||
params_slice(:max_id, :since_id, :min_id)
|
||||
)
|
||||
|
||||
NotificationRequest.preload_cache_collection(requests) do |statuses|
|
||||
cache_collection(statuses, Status)
|
||||
end
|
||||
end
|
||||
|
||||
def relationships
|
||||
StatusRelationshipsPresenter.new(@requests.map(&:last_status), current_user&.account_id)
|
||||
end
|
||||
|
||||
def set_request
|
||||
@request = NotificationRequest.where(account: current_account).find(params[:id])
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_notifications_requests_url pagination_params(max_id: pagination_max_id) unless @requests.empty?
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v1_notifications_requests_url pagination_params(min_id: pagination_since_id) unless @requests.empty?
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@requests.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@requests.first.id
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:dismissed).permit(:dismissed).merge(core_params)
|
||||
end
|
||||
end
|
@ -0,0 +1,52 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api::ErrorHandling
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
|
||||
render json: { error: e.to_s }, status: 422
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotUnique do
|
||||
render json: { error: 'Duplicate record' }, status: 422
|
||||
end
|
||||
|
||||
rescue_from Date::Error do
|
||||
render json: { error: 'Invalid date supplied' }, status: 422
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do
|
||||
render json: { error: 'Record not found' }, status: 404
|
||||
end
|
||||
|
||||
rescue_from HTTP::Error, Mastodon::UnexpectedResponseError do
|
||||
render json: { error: 'Remote data could not be fetched' }, status: 503
|
||||
end
|
||||
|
||||
rescue_from OpenSSL::SSL::SSLError do
|
||||
render json: { error: 'Remote SSL certificate could not be verified' }, status: 503
|
||||
end
|
||||
|
||||
rescue_from Mastodon::NotPermittedError do
|
||||
render json: { error: 'This action is not allowed' }, status: 403
|
||||
end
|
||||
|
||||
rescue_from Seahorse::Client::NetworkingError do |e|
|
||||
Rails.logger.warn "Storage server error: #{e}"
|
||||
render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503
|
||||
end
|
||||
|
||||
rescue_from Mastodon::RaceConditionError, Stoplight::Error::RedLight do
|
||||
render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503
|
||||
end
|
||||
|
||||
rescue_from Mastodon::RateLimitExceededError do
|
||||
render json: { error: I18n.t('errors.429') }, status: 429
|
||||
end
|
||||
|
||||
rescue_from ActionController::ParameterMissing, Mastodon::InvalidParameterError do |e|
|
||||
render json: { error: e.to_s }, status: 400
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,61 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SeveredRelationshipsController < ApplicationController
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :set_body_classes
|
||||
before_action :set_cache_headers
|
||||
|
||||
before_action :set_event, only: [:following, :followers]
|
||||
|
||||
def index
|
||||
@events = AccountRelationshipSeveranceEvent.where(account: current_account)
|
||||
end
|
||||
|
||||
def following
|
||||
respond_to do |format|
|
||||
format.csv { send_data following_data, filename: "following-#{@event.target_name}-#{@event.created_at.to_date.iso8601}.csv" }
|
||||
end
|
||||
end
|
||||
|
||||
def followers
|
||||
respond_to do |format|
|
||||
format.csv { send_data followers_data, filename: "followers-#{@event.target_name}-#{@event.created_at.to_date.iso8601}.csv" }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_event
|
||||
@event = AccountRelationshipSeveranceEvent.find(params[:id])
|
||||
end
|
||||
|
||||
def following_data
|
||||
CSV.generate(headers: ['Account address', 'Show boosts', 'Notify on new posts', 'Languages'], write_headers: true) do |csv|
|
||||
@event.severed_relationships.active.about_local_account(current_account).includes(:remote_account).reorder(id: :desc).each do |follow|
|
||||
csv << [acct(follow.target_account), follow.show_reblogs, follow.notify, follow.languages&.join(', ')]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def followers_data
|
||||
CSV.generate(headers: ['Account address'], write_headers: true) do |csv|
|
||||
@event.severed_relationships.passive.about_local_account(current_account).includes(:remote_account).reorder(id: :desc).each do |follow|
|
||||
csv << [acct(follow.account)]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def acct(account)
|
||||
account.local? ? account.local_username_and_domain : account.acct
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'admin'
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.cache_control.replace(private: true, no_store: true)
|
||||
end
|
||||
end
|
@ -1,228 +0,0 @@
|
||||
// This file will be loaded on admin pages, regardless of theme.
|
||||
|
||||
import 'packs/public-path';
|
||||
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');
|
||||
|
||||
[].forEach.call(document.querySelectorAll(batchCheckboxClassName), (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) {
|
||||
checkAllElement.checked = [].every.call(document.querySelectorAll(batchCheckboxClassName), (content) => content.checked);
|
||||
checkAllElement.indeterminate = !checkAllElement.checked && [].some.call(document.querySelectorAll(batchCheckboxClassName), (content) => content.checked);
|
||||
|
||||
if (selectAllMatchingElement) {
|
||||
if (checkAllElement.checked) {
|
||||
showSelectAll();
|
||||
} else {
|
||||
hideSelectAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Rails.delegate(document, '.media-spoiler-show-button', 'click', () => {
|
||||
[].forEach.call(document.querySelectorAll('button.media-spoiler'), (element) => {
|
||||
element.click();
|
||||
});
|
||||
});
|
||||
|
||||
Rails.delegate(document, '.media-spoiler-hide-button', 'click', () => {
|
||||
[].forEach.call(document.querySelectorAll('.spoiler-button.spoiler-button--visible button'), (element) => {
|
||||
element.click();
|
||||
});
|
||||
});
|
||||
|
||||
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 rejectReportsDiv = document.querySelector('.input.with_label.domain_block_reject_reports');
|
||||
|
||||
if (rejectMediaDiv) {
|
||||
rejectMediaDiv.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';
|
||||
|
||||
[].forEach.call(document.querySelectorAll('#form_admin_settings_require_invite_text'), (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) {
|
||||
checkAllElement.checked = [].every.call(document.querySelectorAll(batchCheckboxClassName), (content) => content.checked);
|
||||
checkAllElement.indeterminate = !checkAllElement.checked && [].some.call(document.querySelectorAll(batchCheckboxClassName), (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;
|
||||
}
|
||||
});
|
||||
|
||||
[].forEach.call(document.querySelectorAll('input[type="datetime-local"]'), element => {
|
||||
if (element.value) {
|
||||
element.value = convertUTCDateTimeToLocal(element.value);
|
||||
}
|
||||
if (element.placeholder) {
|
||||
element.placeholder = convertUTCDateTimeToLocal(element.placeholder);
|
||||
}
|
||||
});
|
||||
|
||||
Rails.delegate(document, 'form', 'submit', ({ target }) => {
|
||||
[].forEach.call(target.querySelectorAll('input[type="datetime-local"]'), 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);
|
||||
}
|
||||
});
|
@ -0,0 +1,340 @@
|
||||
// This file will be loaded on admin pages, regardless of theme.
|
||||
|
||||
import 'packs/public-path';
|
||||
|
||||
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);
|
||||
},
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
}).catch((reason) => {
|
||||
throw reason;
|
||||
});
|
@ -1,25 +0,0 @@
|
||||
// This file will be loaded on embed pages, regardless of theme.
|
||||
|
||||
import 'packs/public-path';
|
||||
|
||||
window.addEventListener('message', e => {
|
||||
const data = e.data || {};
|
||||
|
||||
if (!window.parent || data.type !== 'setHeight') {
|
||||
return;
|
||||
}
|
||||
|
||||
function setEmbedHeight () {
|
||||
window.parent.postMessage({
|
||||
type: 'setHeight',
|
||||
id: data.id,
|
||||
height: document.getElementsByTagName('html')[0].scrollHeight,
|
||||
}, '*');
|
||||
}
|
||||
|
||||
if (['interactive', 'complete'].includes(document.readyState)) {
|
||||
setEmbedHeight();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', setEmbedHeight);
|
||||
}
|
||||
});
|
@ -0,0 +1,41 @@
|
||||
// This file will be loaded on embed pages, regardless of theme.
|
||||
|
||||
import 'packs/public-path';
|
||||
import ready from '../mastodon/ready';
|
||||
|
||||
interface SetHeightMessage {
|
||||
type: 'setHeight';
|
||||
id: string;
|
||||
height: number;
|
||||
}
|
||||
|
||||
function isSetHeightMessage(data: unknown): data is SetHeightMessage {
|
||||
if (
|
||||
data &&
|
||||
typeof data === 'object' &&
|
||||
'type' in data &&
|
||||
data.type === 'setHeight'
|
||||
)
|
||||
return true;
|
||||
else return false;
|
||||
}
|
||||
|
||||
window.addEventListener('message', (e) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- typings are not correct, it can be null in very rare cases
|
||||
if (!e.data || !isSetHeightMessage(e.data) || !window.parent) return;
|
||||
|
||||
const data = e.data;
|
||||
|
||||
ready(() => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: 'setHeight',
|
||||
id: data.id,
|
||||
height: document.getElementsByTagName('html')[0].scrollHeight,
|
||||
},
|
||||
'*',
|
||||
);
|
||||
}).catch((e) => {
|
||||
console.error('Error in setHeightMessage postMessage', e);
|
||||
});
|
||||
});
|
@ -1,44 +0,0 @@
|
||||
// This file will be loaded on settings pages, regardless of theme.
|
||||
|
||||
import 'packs/public-path';
|
||||
import Rails from '@rails/ujs';
|
||||
|
||||
Rails.delegate(document, '#edit_profile input[type=file]', 'change', ({ target }) => {
|
||||
const avatar = document.getElementById(target.id + '-preview');
|
||||
const [file] = target.files || [];
|
||||
const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc;
|
||||
|
||||
avatar.src = url;
|
||||
});
|
||||
|
||||
Rails.delegate(document, '.input-copy input', 'click', ({ target }) => {
|
||||
target.focus();
|
||||
target.select();
|
||||
target.setSelectionRange(0, target.value.length);
|
||||
});
|
||||
|
||||
Rails.delegate(document, '.input-copy button', 'click', ({ target }) => {
|
||||
const input = target.parentNode.querySelector('.input-copy__wrapper input');
|
||||
|
||||
const oldReadOnly = input.readonly;
|
||||
|
||||
input.readonly = false;
|
||||
input.focus();
|
||||
input.select();
|
||||
input.setSelectionRange(0, input.value.length);
|
||||
|
||||
try {
|
||||
if (document.execCommand('copy')) {
|
||||
input.blur();
|
||||
target.parentNode.classList.add('copied');
|
||||
|
||||
setTimeout(() => {
|
||||
target.parentNode.classList.remove('copied');
|
||||
}, 700);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
input.readonly = oldReadOnly;
|
||||
});
|
@ -0,0 +1,70 @@
|
||||
// This file will be loaded on settings pages, regardless of theme.
|
||||
|
||||
import 'packs/public-path';
|
||||
import Rails from '@rails/ujs';
|
||||
|
||||
Rails.delegate(
|
||||
document,
|
||||
'#edit_profile input[type=file]',
|
||||
'change',
|
||||
({ target }) => {
|
||||
if (!(target instanceof HTMLInputElement)) return;
|
||||
|
||||
const avatar = document.querySelector<HTMLImageElement>(
|
||||
`img#${target.id}-preview`,
|
||||
);
|
||||
|
||||
if (!avatar) return;
|
||||
|
||||
let file: File | undefined;
|
||||
if (target.files) file = target.files[0];
|
||||
|
||||
const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc;
|
||||
|
||||
if (url) avatar.src = url;
|
||||
},
|
||||
);
|
||||
|
||||
Rails.delegate(document, '.input-copy input', 'click', ({ target }) => {
|
||||
if (!(target instanceof HTMLInputElement)) return;
|
||||
|
||||
target.focus();
|
||||
target.select();
|
||||
target.setSelectionRange(0, target.value.length);
|
||||
});
|
||||
|
||||
Rails.delegate(document, '.input-copy button', 'click', ({ target }) => {
|
||||
if (!(target instanceof HTMLButtonElement)) return;
|
||||
|
||||
const input = target.parentNode?.querySelector<HTMLInputElement>(
|
||||
'.input-copy__wrapper input',
|
||||
);
|
||||
|
||||
if (!input) return;
|
||||
|
||||
const oldReadOnly = input.readOnly;
|
||||
|
||||
input.readOnly = false;
|
||||
input.focus();
|
||||
input.select();
|
||||
input.setSelectionRange(0, input.value.length);
|
||||
|
||||
try {
|
||||
if (document.execCommand('copy')) {
|
||||
input.blur();
|
||||
|
||||
const parent = target.parentElement;
|
||||
|
||||
if (!parent) return;
|
||||
parent.classList.add('copied');
|
||||
|
||||
setTimeout(() => {
|
||||
parent.classList.remove('copied');
|
||||
}, 700);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
input.readOnly = oldReadOnly;
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue