Conflicts: - `app/javascript/styles/fonts/montserrat.scss`: Code style changes upstream, path changes in glitch-soc. Applied upstream's code style changes. - `app/javascript/styles/fonts/roboto-mono.scss`: Code style changes upstream, path changes in glitch-soc. Applied upstream's code style changes. - `app/javascript/styles/fonts/roboto.scss`: Code style changes upstream, path changes in glitch-soc. Applied upstream's code style changes. - `app/models/account.rb`: Textual conflict only caused by glitch-soc using a different value for character limits in a nearby line. Applied upstream's changes. - `app/views/statuses/_simple_status.html.haml`: Attribute added to a tag modified by glitch-soc. Added upstream's attributes. - `yarn.lock`: Upstream added/updated dependencies close to glitch-soc-only ones. Updated/added upstream dependencies.main
@ -0,0 +1,71 @@
|
||||
---
|
||||
#################################
|
||||
#################################
|
||||
## Super Linter GitHub Actions ##
|
||||
#################################
|
||||
#################################
|
||||
name: Lint Code Base
|
||||
|
||||
#
|
||||
# Documentation:
|
||||
# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions
|
||||
#
|
||||
|
||||
#############################
|
||||
# Start the job on all push #
|
||||
#############################
|
||||
on:
|
||||
push:
|
||||
branches-ignore: [main]
|
||||
# Remove the line above to run when pushing to master
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
###############
|
||||
# Set the Job #
|
||||
###############
|
||||
permissions:
|
||||
checks: write
|
||||
contents: read
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# Name the Job
|
||||
name: Lint Code Base
|
||||
# Set the agent to run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
##################
|
||||
# Load all steps #
|
||||
##################
|
||||
steps:
|
||||
##########################
|
||||
# Checkout the code base #
|
||||
##########################
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Full git history is needed to get a proper list of changed files within `super-linter`
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Intall dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
################################
|
||||
# Run Linter against code base #
|
||||
################################
|
||||
- name: Lint Code Base
|
||||
uses: github/super-linter@v4
|
||||
env:
|
||||
CSS_FILE_NAME: stylelint.config.js
|
||||
DEFAULT_BRANCH: main
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
JAVASCRIPT_ES_CONFIG_FILE: .eslintrc.js
|
||||
LINTER_RULES_PATH: .
|
||||
RUBY_CONFIG_FILE: .rubocop.yml
|
||||
VALIDATE_ALL_CODEBASE: false
|
||||
VALIDATE_CSS: true
|
||||
VALIDATE_JAVASCRIPT_ES: true
|
||||
VALIDATE_RUBY: true
|
@ -1,37 +0,0 @@
|
||||
# Linter Documentation:
|
||||
# https://github.com/sasstools/sass-lint/tree/v1.13.1/docs/options
|
||||
|
||||
files:
|
||||
include: app/javascript/styles/**/*.scss
|
||||
ignore:
|
||||
- app/javascript/styles/mastodon/reset.scss
|
||||
|
||||
rules:
|
||||
# Disallows
|
||||
no-color-literals: 0
|
||||
no-css-comments: 0
|
||||
no-duplicate-properties: 0
|
||||
no-ids: 0
|
||||
no-important: 0
|
||||
no-mergeable-selectors: 0
|
||||
no-misspelled-properties: 0
|
||||
no-qualifying-elements: 0
|
||||
no-transition-all: 0
|
||||
no-vendor-prefixes: 0
|
||||
|
||||
# Nesting
|
||||
force-element-nesting: 0
|
||||
force-attribute-nesting: 0
|
||||
force-pseudo-nesting: 0
|
||||
|
||||
# Name Formats
|
||||
class-name-format: 0
|
||||
leading-zero: 0
|
||||
|
||||
# Style Guide
|
||||
attribute-quotes: 0
|
||||
hex-length: 0
|
||||
indentation: 0
|
||||
nesting-depth: 0
|
||||
property-sort-order: 0
|
||||
quotes: 0
|
@ -0,0 +1,109 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Admin::DomainBlocksController < Api::BaseController
|
||||
include Authorization
|
||||
include AccountableConcern
|
||||
|
||||
LIMIT = 100
|
||||
|
||||
before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:domain_blocks' }, only: [:index, :show]
|
||||
before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:domain_blocks' }, except: [:index, :show]
|
||||
before_action :require_staff!
|
||||
before_action :set_domain_blocks, only: :index
|
||||
before_action :set_domain_block, only: [:show, :update, :destroy]
|
||||
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
PAGINATION_PARAMS = %i(limit).freeze
|
||||
|
||||
def create
|
||||
authorize :domain_block, :create?
|
||||
|
||||
existing_domain_block = resource_params[:domain].present? ? DomainBlock.rule_for(resource_params[:domain]) : nil
|
||||
return render json: existing_domain_block, serializer: REST::Admin::ExistingDomainBlockErrorSerializer, status: 422 if existing_domain_block.present?
|
||||
|
||||
@domain_block = DomainBlock.create!(resource_params)
|
||||
DomainBlockWorker.perform_async(@domain_block.id)
|
||||
log_action :create, @domain_block
|
||||
render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
|
||||
end
|
||||
|
||||
def index
|
||||
authorize :domain_block, :index?
|
||||
render json: @domain_blocks, each_serializer: REST::Admin::DomainBlockSerializer
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @domain_block, :show?
|
||||
render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @domain_block, :update?
|
||||
|
||||
@domain_block.update(domain_block_params)
|
||||
severity_changed = @domain_block.severity_changed?
|
||||
@domain_block.save!
|
||||
DomainBlockWorker.perform_async(@domain_block.id, severity_changed)
|
||||
log_action :update, @domain_block
|
||||
render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @domain_block, :destroy?
|
||||
UnblockDomainService.new.call(@domain_block)
|
||||
log_action :destroy, @domain_block
|
||||
render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_domain_blocks
|
||||
@domain_blocks = filtered_domain_blocks.order(id: :desc).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
|
||||
end
|
||||
|
||||
def set_domain_block
|
||||
@domain_block = DomainBlock.find(params[:id])
|
||||
end
|
||||
|
||||
def filtered_domain_blocks
|
||||
# TODO: no filtering yet
|
||||
DomainBlock.all
|
||||
end
|
||||
|
||||
def domain_block_params
|
||||
params.permit(:severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_admin_domain_blocks_url(pagination_params(max_id: pagination_max_id)) if records_continue?
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v1_admin_domain_blocks_url(pagination_params(min_id: pagination_since_id)) unless @domain_blocks.empty?
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@domain_blocks.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@domain_blocks.first.id
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
@domain_blocks.size == limit_param(LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate)
|
||||
end
|
||||
end
|
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 8.8 KiB |
Before Width: | Height: | Size: 27 KiB |
@ -1,197 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactSwipeableViews from 'react-swipeable-views';
|
||||
import classNames from 'classnames';
|
||||
import { connect } from 'react-redux';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { closeOnboarding } from '../../actions/onboarding';
|
||||
import screenHello from '../../../images/screen_hello.svg';
|
||||
import screenFederation from '../../../images/screen_federation.svg';
|
||||
import screenInteractions from '../../../images/screen_interactions.svg';
|
||||
import logoTransparent from '../../../images/logo_transparent.svg';
|
||||
import { disableSwiping } from 'mastodon/initial_state';
|
||||
|
||||
const FrameWelcome = ({ domain, onNext }) => (
|
||||
<div className='introduction__frame'>
|
||||
<div className='introduction__illustration' style={{ background: `url(${logoTransparent}) no-repeat center center / auto 80%` }}>
|
||||
<img src={screenHello} alt='' />
|
||||
</div>
|
||||
|
||||
<div className='introduction__text introduction__text--centered'>
|
||||
<h3><FormattedMessage id='introduction.welcome.headline' defaultMessage='First steps' /></h3>
|
||||
<p><FormattedMessage id='introduction.welcome.text' defaultMessage="Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name." values={{ domain: <code>{domain}</code> }} /></p>
|
||||
</div>
|
||||
|
||||
<div className='introduction__action'>
|
||||
<button className='button' onClick={onNext}><FormattedMessage id='introduction.welcome.action' defaultMessage="Let's go!" /></button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
FrameWelcome.propTypes = {
|
||||
domain: PropTypes.string.isRequired,
|
||||
onNext: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const FrameFederation = ({ onNext }) => (
|
||||
<div className='introduction__frame'>
|
||||
<div className='introduction__illustration'>
|
||||
<img src={screenFederation} alt='' />
|
||||
</div>
|
||||
|
||||
<div className='introduction__text introduction__text--columnized'>
|
||||
<div>
|
||||
<h3><FormattedMessage id='introduction.federation.home.headline' defaultMessage='Home' /></h3>
|
||||
<p><FormattedMessage id='introduction.federation.home.text' defaultMessage='Posts from people you follow will appear in your home feed. You can follow anyone on any server!' /></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3><FormattedMessage id='introduction.federation.local.headline' defaultMessage='Local' /></h3>
|
||||
<p><FormattedMessage id='introduction.federation.local.text' defaultMessage='Public posts from people on the same server as you will appear in the local timeline.' /></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3><FormattedMessage id='introduction.federation.federated.headline' defaultMessage='Federated' /></h3>
|
||||
<p><FormattedMessage id='introduction.federation.federated.text' defaultMessage='Public posts from other servers of the fediverse will appear in the federated timeline.' /></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='introduction__action'>
|
||||
<button className='button' onClick={onNext}><FormattedMessage id='introduction.federation.action' defaultMessage='Next' /></button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
FrameFederation.propTypes = {
|
||||
onNext: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const FrameInteractions = ({ onNext }) => (
|
||||
<div className='introduction__frame'>
|
||||
<div className='introduction__illustration'>
|
||||
<img src={screenInteractions} alt='' />
|
||||
</div>
|
||||
|
||||
<div className='introduction__text introduction__text--columnized'>
|
||||
<div>
|
||||
<h3><FormattedMessage id='introduction.interactions.reply.headline' defaultMessage='Reply' /></h3>
|
||||
<p><FormattedMessage id='introduction.interactions.reply.text' defaultMessage="You can reply to other people's and your own toots, which will chain them together in a conversation." /></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3><FormattedMessage id='introduction.interactions.reblog.headline' defaultMessage='Boost' /></h3>
|
||||
<p><FormattedMessage id='introduction.interactions.reblog.text' defaultMessage="You can share other people's toots with your followers by boosting them." /></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3><FormattedMessage id='introduction.interactions.favourite.headline' defaultMessage='Favourite' /></h3>
|
||||
<p><FormattedMessage id='introduction.interactions.favourite.text' defaultMessage='You can save a toot for later, and let the author know that you liked it, by favouriting it.' /></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='introduction__action'>
|
||||
<button className='button' onClick={onNext}><FormattedMessage id='introduction.interactions.action' defaultMessage='Finish toot-orial!' /></button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
FrameInteractions.propTypes = {
|
||||
onNext: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default @connect(state => ({ domain: state.getIn(['meta', 'domain']) }))
|
||||
class Introduction extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
domain: PropTypes.string.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
currentIndex: 0,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
this.pages = [
|
||||
<FrameWelcome domain={this.props.domain} onNext={this.handleNext} />,
|
||||
<FrameFederation onNext={this.handleNext} />,
|
||||
<FrameInteractions onNext={this.handleFinish} />,
|
||||
];
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('keyup', this.handleKeyUp);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.addEventListener('keyup', this.handleKeyUp);
|
||||
}
|
||||
|
||||
handleDot = (e) => {
|
||||
const i = Number(e.currentTarget.getAttribute('data-index'));
|
||||
e.preventDefault();
|
||||
this.setState({ currentIndex: i });
|
||||
}
|
||||
|
||||
handlePrev = () => {
|
||||
this.setState(({ currentIndex }) => ({
|
||||
currentIndex: Math.max(0, currentIndex - 1),
|
||||
}));
|
||||
}
|
||||
|
||||
handleNext = () => {
|
||||
const { pages } = this;
|
||||
|
||||
this.setState(({ currentIndex }) => ({
|
||||
currentIndex: Math.min(currentIndex + 1, pages.length - 1),
|
||||
}));
|
||||
}
|
||||
|
||||
handleSwipe = (index) => {
|
||||
this.setState({ currentIndex: index });
|
||||
}
|
||||
|
||||
handleFinish = () => {
|
||||
this.props.dispatch(closeOnboarding());
|
||||
}
|
||||
|
||||
handleKeyUp = ({ key }) => {
|
||||
switch (key) {
|
||||
case 'ArrowLeft':
|
||||
this.handlePrev();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
this.handleNext();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { currentIndex } = this.state;
|
||||
const { pages } = this;
|
||||
|
||||
return (
|
||||
<div className='introduction'>
|
||||
<ReactSwipeableViews index={currentIndex} onChangeIndex={this.handleSwipe} disabled={disableSwiping} className='introduction__pager'>
|
||||
{pages.map((page, i) => (
|
||||
<div key={i} className={classNames('introduction__frame-wrapper', { 'active': i === currentIndex })}>{page}</div>
|
||||
))}
|
||||
</ReactSwipeableViews>
|
||||
|
||||
<div className='introduction__dots'>
|
||||
{pages.map((_, i) => (
|
||||
<div
|
||||
key={`dot-${i}`}
|
||||
role='button'
|
||||
tabIndex='0'
|
||||
data-index={i}
|
||||
onClick={this.handleDot}
|
||||
className={classNames('introduction__dot', { active: i === currentIndex })}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -1,154 +0,0 @@
|
||||
.introduction {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background: $ui-base-color;
|
||||
|
||||
@media screen and (max-width: 920px) {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
&__pager {
|
||||
background: darken($ui-base-color, 8%);
|
||||
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__pager,
|
||||
&__frame {
|
||||
border-radius: 10px;
|
||||
width: 50vw;
|
||||
min-width: 920px;
|
||||
|
||||
@media screen and (max-width: 920px) {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__frame-wrapper {
|
||||
opacity: 0;
|
||||
transition: opacity 500ms linear;
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
transition: opacity 50ms linear;
|
||||
}
|
||||
}
|
||||
|
||||
&__frame {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__illustration {
|
||||
height: 50vh;
|
||||
|
||||
@media screen and (max-width: 630px) {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
img {
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
border-top: 2px solid $ui-highlight-color;
|
||||
|
||||
&--columnized {
|
||||
display: flex;
|
||||
|
||||
& > div {
|
||||
flex: 1 1 33.33%;
|
||||
text-align: center;
|
||||
padding: 25px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 630px) {
|
||||
display: block;
|
||||
padding: 15px 0;
|
||||
padding-bottom: 20px;
|
||||
|
||||
& > div {
|
||||
padding: 10px 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 24px;
|
||||
line-height: 1.5;
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
color: $darker-text-color;
|
||||
|
||||
code {
|
||||
display: inline-block;
|
||||
background: darken($ui-base-color, 8%);
|
||||
font-size: 15px;
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
border-radius: 2px;
|
||||
padding: 1px 3px;
|
||||
}
|
||||
}
|
||||
|
||||
&--centered {
|
||||
padding: 25px;
|
||||
padding-bottom: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__dots {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 25px;
|
||||
|
||||
@media screen and (max-width: 630px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__dot {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid $ui-highlight-color;
|
||||
background: transparent;
|
||||
margin: 0 3px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: lighten($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
&.active {
|
||||
cursor: default;
|
||||
background: $ui-highlight-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__action {
|
||||
padding: 25px;
|
||||
padding-top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::Admin::DomainBlockSerializer < ActiveModel::Serializer
|
||||
attributes :id, :domain, :created_at, :severity,
|
||||
:reject_media, :reject_reports,
|
||||
:private_comment, :public_comment, :obfuscate
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
end
|
||||
end
|
@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::Admin::ExistingDomainBlockErrorSerializer < ActiveModel::Serializer
|
||||
attributes :error
|
||||
|
||||
has_one :existing_domain_block, serializer: REST::Admin::DomainBlockSerializer
|
||||
|
||||
def error
|
||||
I18n.t('admin.domain_blocks.existing_domain_block', name: existing_domain_block.domain)
|
||||
end
|
||||
|
||||
def existing_domain_block
|
||||
object
|
||||
end
|
||||
end
|