forked from mirrors/catstodon
merge with catstodon/main
parent
60d54db810
commit
df00e5e6cb
@ -1,7 +1,9 @@
|
|||||||
[production]
|
[production]
|
||||||
defaults
|
defaults
|
||||||
not IE 11
|
> 0.2%
|
||||||
|
ios >= 15.6
|
||||||
not dead
|
not dead
|
||||||
|
not OperaMini all
|
||||||
|
|
||||||
[development]
|
[development]
|
||||||
supports es6-module
|
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
|
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