diff --git a/.gitignore b/.gitignore index a4057eb6e..8cdcc8d27 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,11 @@ ubuntu-xenial-16.04-cloudimg-console.log # Ignore Docker option files docker-compose.override.yml + + +/public/packs +/public/packs-test +/node_modules +/yarn-error.log +yarn-debug.log* +.yarn-integrity diff --git a/.nope_browserlistrc b/.nope_browserlistrc new file mode 100644 index 000000000..e94f8140c --- /dev/null +++ b/.nope_browserlistrc @@ -0,0 +1 @@ +defaults diff --git a/.ruby-version b/.ruby-version index 57cf282eb..2714f5313 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.6.5 +2.6.4 diff --git a/app/assets/javascripts/user_groups.js b/app/assets/javascripts/user_groups.js new file mode 100644 index 000000000..dee720fac --- /dev/null +++ b/app/assets/javascripts/user_groups.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/stylesheets/scaffold.css b/app/assets/stylesheets/scaffold.css new file mode 100644 index 000000000..cd4f3de38 --- /dev/null +++ b/app/assets/stylesheets/scaffold.css @@ -0,0 +1,80 @@ +body { + background-color: #fff; + color: #333; + margin: 33px; +} + +body, p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; +} + +a { + color: #000; +} + +a:visited { + color: #666; +} + +a:hover { + color: #fff; + background-color: #000; +} + +th { + padding-bottom: 5px; +} + +td { + padding: 0 5px 7px; +} + +div.field, +div.actions { + margin-bottom: 10px; +} + +#notice { + color: green; +} + +.field_with_errors { + padding: 2px; + background-color: red; + display: table; +} + +#error_explanation { + width: 450px; + border: 2px solid red; + padding: 7px 7px 0; + margin-bottom: 20px; + background-color: #f0f0f0; +} + +#error_explanation h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px -7px 0; + background-color: #c00; + color: #fff; +} + +#error_explanation ul li { + font-size: 12px; + list-style: square; +} + +label { + display: block; +} diff --git a/app/assets/stylesheets/user_groups.css b/app/assets/stylesheets/user_groups.css new file mode 100644 index 000000000..afad32db0 --- /dev/null +++ b/app/assets/stylesheets/user_groups.css @@ -0,0 +1,4 @@ +/* + Place all the styles related to the matching controller here. + They will automatically be included in application.css. +*/ diff --git a/app/controllers/user_groups_controller.rb b/app/controllers/user_groups_controller.rb new file mode 100644 index 000000000..d4ec0a2b4 --- /dev/null +++ b/app/controllers/user_groups_controller.rb @@ -0,0 +1,58 @@ +class UserGroupsController < ApplicationController + before_action :set_user_group, only: [:show, :edit, :update, :destroy] + + # GET /user_groups + def index + @user_groups = UserGroup.all + end + + # GET /user_groups/1 + def show + end + + # GET /user_groups/new + def new + @user_group = UserGroup.new + end + + # GET /user_groups/1/edit + def edit + end + + # POST /user_groups + def create + @user_group = UserGroup.new(user_group_params) + + if @user_group.save + redirect_to @user_group, notice: 'User group was successfully created.' + else + render :new + end + end + + # PATCH/PUT /user_groups/1 + def update + if @user_group.update(user_group_params) + redirect_to @user_group, notice: 'User group was successfully updated.' + else + render :edit + end + end + + # DELETE /user_groups/1 + def destroy + @user_group.destroy + redirect_to user_groups_url, notice: 'User group was successfully destroyed.' + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_user_group + @user_group = UserGroup.find(params[:id]) + end + + # Only allow a trusted parameter "white list" through. + def user_group_params + params.require(:user_group).permit(:name, :createAt, :visibility, :members) + end +end diff --git a/app/helpers/user_groups_helper.rb b/app/helpers/user_groups_helper.rb new file mode 100644 index 000000000..83cd8f3cd --- /dev/null +++ b/app/helpers/user_groups_helper.rb @@ -0,0 +1,2 @@ +module UserGroupsHelper +end diff --git a/app/javascript/mastodon/actions/conversations.js b/app/javascript/mastodon/actions/conversations.js index 4ef654b1f..2027fcb68 100644 --- a/app/javascript/mastodon/actions/conversations.js +++ b/app/javascript/mastodon/actions/conversations.js @@ -1,23 +1,19 @@ import api, { getLinks } from '../api'; -import { - importFetchedAccounts, - importFetchedStatuses, - importFetchedStatus, -} from './importer'; +import { importFetchedAccounts, importFetchedStatus, importFetchedStatuses } from './importer'; -export const CONVERSATIONS_MOUNT = 'CONVERSATIONS_MOUNT'; +export const CONVERSATIONS_MOUNT = 'CONVERSATIONS_MOUNT'; export const CONVERSATIONS_UNMOUNT = 'CONVERSATIONS_UNMOUNT'; export const CONVERSATIONS_FETCH_REQUEST = 'CONVERSATIONS_FETCH_REQUEST'; export const CONVERSATIONS_FETCH_SUCCESS = 'CONVERSATIONS_FETCH_SUCCESS'; -export const CONVERSATIONS_FETCH_FAIL = 'CONVERSATIONS_FETCH_FAIL'; -export const CONVERSATIONS_UPDATE = 'CONVERSATIONS_UPDATE'; +export const CONVERSATIONS_FETCH_FAIL = 'CONVERSATIONS_FETCH_FAIL'; +export const CONVERSATIONS_UPDATE = 'CONVERSATIONS_UPDATE'; export const CONVERSATIONS_READ = 'CONVERSATIONS_READ'; export const CONVERSATIONS_DELETE_REQUEST = 'CONVERSATIONS_DELETE_REQUEST'; export const CONVERSATIONS_DELETE_SUCCESS = 'CONVERSATIONS_DELETE_SUCCESS'; -export const CONVERSATIONS_DELETE_FAIL = 'CONVERSATIONS_DELETE_FAIL'; +export const CONVERSATIONS_DELETE_FAIL = 'CONVERSATIONS_DELETE_FAIL'; export const mountConversations = () => ({ type: CONVERSATIONS_MOUNT, @@ -30,7 +26,7 @@ export const unmountConversations = () => ({ export const markConversationRead = conversationId => (dispatch, getState) => { dispatch({ type: CONVERSATIONS_READ, - id: conversationId, + id : conversationId, }); api(getState).post(`/api/v1/conversations/${conversationId}/read`); diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js index 06a19afc3..b2f0bf07d 100644 --- a/app/javascript/mastodon/actions/statuses.js +++ b/app/javascript/mastodon/actions/statuses.js @@ -3,31 +3,31 @@ import openDB from '../storage/db'; import { evictStatus } from '../storage/modifier'; import { deleteFromTimelines } from './timelines'; -import { importFetchedStatus, importFetchedStatuses, importAccount, importStatus } from './importer'; +import { importAccount, importFetchedStatus, importFetchedStatuses, importStatus } from './importer'; import { ensureComposeIsVisible } from './compose'; export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST'; export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS'; -export const STATUS_FETCH_FAIL = 'STATUS_FETCH_FAIL'; +export const STATUS_FETCH_FAIL = 'STATUS_FETCH_FAIL'; export const STATUS_DELETE_REQUEST = 'STATUS_DELETE_REQUEST'; export const STATUS_DELETE_SUCCESS = 'STATUS_DELETE_SUCCESS'; -export const STATUS_DELETE_FAIL = 'STATUS_DELETE_FAIL'; +export const STATUS_DELETE_FAIL = 'STATUS_DELETE_FAIL'; export const CONTEXT_FETCH_REQUEST = 'CONTEXT_FETCH_REQUEST'; export const CONTEXT_FETCH_SUCCESS = 'CONTEXT_FETCH_SUCCESS'; -export const CONTEXT_FETCH_FAIL = 'CONTEXT_FETCH_FAIL'; +export const CONTEXT_FETCH_FAIL = 'CONTEXT_FETCH_FAIL'; export const STATUS_MUTE_REQUEST = 'STATUS_MUTE_REQUEST'; export const STATUS_MUTE_SUCCESS = 'STATUS_MUTE_SUCCESS'; -export const STATUS_MUTE_FAIL = 'STATUS_MUTE_FAIL'; +export const STATUS_MUTE_FAIL = 'STATUS_MUTE_FAIL'; export const STATUS_UNMUTE_REQUEST = 'STATUS_UNMUTE_REQUEST'; export const STATUS_UNMUTE_SUCCESS = 'STATUS_UNMUTE_SUCCESS'; -export const STATUS_UNMUTE_FAIL = 'STATUS_UNMUTE_FAIL'; +export const STATUS_UNMUTE_FAIL = 'STATUS_UNMUTE_FAIL'; export const STATUS_REVEAL = 'STATUS_REVEAL'; -export const STATUS_HIDE = 'STATUS_HIDE'; +export const STATUS_HIDE = 'STATUS_HIDE'; export const REDRAFT = 'REDRAFT'; @@ -37,7 +37,7 @@ export function fetchStatusRequest(id, skipLoading) { id, skipLoading, }; -}; +} function getFromDB(dispatch, getState, accountIndex, index, id) { return new Promise((resolve, reject) => { @@ -113,24 +113,24 @@ export function fetchStatus(id) { dispatch(fetchStatusFail(id, error, skipLoading)); }); }; -}; +} export function fetchStatusSuccess(skipLoading) { return { type: STATUS_FETCH_SUCCESS, skipLoading, }; -}; +} export function fetchStatusFail(id, error, skipLoading) { return { - type: STATUS_FETCH_FAIL, + type : STATUS_FETCH_FAIL, id, error, skipLoading, skipAlert: true, }; -}; +} export function redraft(status, raw_text) { return { @@ -138,7 +138,7 @@ export function redraft(status, raw_text) { status, raw_text, }; -}; +} export function deleteStatus(id, routerHistory, withRedraft = false) { return (dispatch, getState) => { @@ -163,29 +163,29 @@ export function deleteStatus(id, routerHistory, withRedraft = false) { dispatch(deleteStatusFail(id, error)); }); }; -}; +} export function deleteStatusRequest(id) { return { type: STATUS_DELETE_REQUEST, - id: id, + id : id, }; -}; +} export function deleteStatusSuccess(id) { return { type: STATUS_DELETE_SUCCESS, - id: id, + id : id, }; -}; +} export function deleteStatusFail(id, error) { return { - type: STATUS_DELETE_FAIL, - id: id, + type : STATUS_DELETE_FAIL, + id : id, error: error, }; -}; +} export function fetchContext(id) { return (dispatch, getState) => { @@ -203,33 +203,33 @@ export function fetchContext(id) { dispatch(fetchContextFail(id, error)); }); }; -}; +} export function fetchContextRequest(id) { return { type: CONTEXT_FETCH_REQUEST, id, }; -}; +} export function fetchContextSuccess(id, ancestors, descendants) { return { - type: CONTEXT_FETCH_SUCCESS, + type : CONTEXT_FETCH_SUCCESS, id, ancestors, descendants, statuses: ancestors.concat(descendants), }; -}; +} export function fetchContextFail(id, error) { return { - type: CONTEXT_FETCH_FAIL, + type : CONTEXT_FETCH_FAIL, id, error, skipAlert: true, }; -}; +} export function muteStatus(id) { return (dispatch, getState) => { @@ -241,21 +241,21 @@ export function muteStatus(id) { dispatch(muteStatusFail(id, error)); }); }; -}; +} export function muteStatusRequest(id) { return { type: STATUS_MUTE_REQUEST, id, }; -}; +} export function muteStatusSuccess(id) { return { type: STATUS_MUTE_SUCCESS, id, }; -}; +} export function muteStatusFail(id, error) { return { @@ -263,7 +263,7 @@ export function muteStatusFail(id, error) { id, error, }; -}; +} export function unmuteStatus(id) { return (dispatch, getState) => { @@ -275,21 +275,21 @@ export function unmuteStatus(id) { dispatch(unmuteStatusFail(id, error)); }); }; -}; +} export function unmuteStatusRequest(id) { return { type: STATUS_UNMUTE_REQUEST, id, }; -}; +} export function unmuteStatusSuccess(id) { return { type: STATUS_UNMUTE_SUCCESS, id, }; -}; +} export function unmuteStatusFail(id, error) { return { @@ -297,7 +297,7 @@ export function unmuteStatusFail(id, error) { id, error, }; -}; +} export function hideStatus(ids) { if (!Array.isArray(ids)) { @@ -308,7 +308,7 @@ export function hideStatus(ids) { type: STATUS_HIDE, ids, }; -}; +} export function revealStatus(ids) { if (!Array.isArray(ids)) { @@ -319,4 +319,4 @@ export function revealStatus(ids) { type: STATUS_REVEAL, ids, }; -}; +} diff --git a/app/javascript/mastodon/components/autosuggest_textarea.js b/app/javascript/mastodon/components/autosuggest_textarea.js index ac2a6366a..3d954e8a8 100644 --- a/app/javascript/mastodon/components/autosuggest_textarea.js +++ b/app/javascript/mastodon/components/autosuggest_textarea.js @@ -12,7 +12,7 @@ import classNames from 'classnames'; const textAtCursorMatchesToken = (str, caretPosition) => { let word; - let left = str.slice(0, caretPosition).search(/\S+$/); + let left = str.slice(0, caretPosition).search(/\S+$/); let right = str.slice(caretPosition).search(/\s/); if (right < 0) { @@ -37,34 +37,37 @@ const textAtCursorMatchesToken = (str, caretPosition) => { export default class AutosuggestTextarea extends ImmutablePureComponent { static propTypes = { - value: PropTypes.string, - suggestions: ImmutablePropTypes.list, - disabled: PropTypes.bool, - placeholder: PropTypes.string, - onSuggestionSelected: PropTypes.func.isRequired, + value : PropTypes.string, + suggestions : ImmutablePropTypes.list, + disabled : PropTypes.bool, + placeholder : PropTypes.string, + onSuggestionSelected : PropTypes.func.isRequired, onSuggestionsClearRequested: PropTypes.func.isRequired, onSuggestionsFetchRequested: PropTypes.func.isRequired, - onChange: PropTypes.func.isRequired, - onKeyUp: PropTypes.func, - onKeyDown: PropTypes.func, - onPaste: PropTypes.func.isRequired, - autoFocus: PropTypes.bool, + onChange : PropTypes.func.isRequired, + onKeyUp : PropTypes.func, + onKeyDown : PropTypes.func, + onPaste : PropTypes.func.isRequired, + autoFocus : PropTypes.bool, + directMessage : PropTypes.bool, + directMessageRecipient : PropTypes.string, }; static defaultProps = { - autoFocus: true, + autoFocus : true, + directMessage: false, }; state = { - suggestionsHidden: true, - focused: false, + suggestionsHidden : true, + focused : false, selectedSuggestion: 0, - lastToken: null, - tokenStart: 0, + lastToken : null, + tokenStart : 0, }; onChange = (e) => { - const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart); + const [tokenStart, token] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart); if (token !== null && this.state.lastToken !== token) { this.setState({ lastToken: token, selectedSuggestion: 0, tokenStart }); @@ -75,7 +78,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { } this.props.onChange(e); - } + }; onKeyDown = (e) => { const { suggestions, disabled } = this.props; @@ -92,7 +95,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { return; } - switch(e.key) { + switch (e.key) { case 'Escape': if (suggestions.size === 0 || suggestionsHidden) { document.querySelector('.ui').parentElement.focus(); @@ -124,7 +127,6 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { e.stopPropagation(); this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion)); } - break; } @@ -133,27 +135,27 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { } this.props.onKeyDown(e); - } + }; onBlur = () => { this.setState({ suggestionsHidden: true, focused: false }); - } + }; onFocus = (e) => { this.setState({ focused: true }); if (this.props.onFocus) { this.props.onFocus(e); } - } + }; onSuggestionClick = (e) => { const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute('data-index')); e.preventDefault(); this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion); this.textarea.focus(); - } + }; - componentWillReceiveProps (nextProps) { + componentWillReceiveProps(nextProps) { if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) { this.setState({ suggestionsHidden: false }); } @@ -161,14 +163,14 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { setTextarea = (c) => { this.textarea = c; - } + }; onPaste = (e) => { if (e.clipboardData && e.clipboardData.files.length === 1) { this.props.onPaste(e.clipboardData.files); e.preventDefault(); } - } + }; renderSuggestion = (suggestion, i) => { const { selectedSuggestion } = this.state; @@ -176,23 +178,30 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { if (suggestion.type === 'emoji') { inner = ; - key = suggestion.id; + key = suggestion.id; } else if (suggestion.type === 'hashtag') { inner = ; - key = suggestion.name; + key = suggestion.name; } else if (suggestion.type === 'account') { inner = ; - key = suggestion.id; + key = suggestion.id; } return ( -
+
{inner} -
+
); - } + }; - render () { + render() { const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, children } = this.props; const { suggestionsHidden } = this.state; const style = { direction: 'ltr' }; @@ -202,10 +211,13 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { } return [ -
+
-