From 64aac3073340dbc92c33f5f1c6f76dcafa77a450 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 29 Jun 2020 13:56:55 +0200 Subject: Add customizable thumbnails for audio and video attachments (#14145) - Change audio files to not be stripped of metadata - Automatically extract cover art from audio if it exists - Add `thumbnail` parameter to `POST /api/v1/media`, `POST /api/v2/media` and `PUT /api/v1/media/:id` - Add `icon` to represent it in attachments in ActivityPub - Fix `preview_url` containing URL of missing missing image when there is no thumbnail instead of null - Fix duration of audio not being displayed on public pages until the file is loaded --- ...200627125810_add_thumbnail_columns_to_media_attachments.rb | 11 +++++++++++ db/schema.rb | 7 ++++++- 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20200627125810_add_thumbnail_columns_to_media_attachments.rb (limited to 'db') diff --git a/db/migrate/20200627125810_add_thumbnail_columns_to_media_attachments.rb b/db/migrate/20200627125810_add_thumbnail_columns_to_media_attachments.rb new file mode 100644 index 000000000..f9c87a53c --- /dev/null +++ b/db/migrate/20200627125810_add_thumbnail_columns_to_media_attachments.rb @@ -0,0 +1,11 @@ +class AddThumbnailColumnsToMediaAttachments < ActiveRecord::Migration[5.2] + def up + add_attachment :media_attachments, :thumbnail + add_column :media_attachments, :thumbnail_remote_url, :string + end + + def down + remove_attachment :media_attachments, :thumbnail + remove_column :media_attachments, :thumbnail_remote_url + end +end diff --git a/db/schema.rb b/db/schema.rb index d603d98d7..277eccba7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_06_20_164023) do +ActiveRecord::Schema.define(version: 2020_06_27_125810) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -489,6 +489,11 @@ ActiveRecord::Schema.define(version: 2020_06_20_164023) do t.string "blurhash" t.integer "processing" t.integer "file_storage_schema_version" + t.string "thumbnail_file_name" + t.string "thumbnail_content_type" + t.integer "thumbnail_file_size" + t.datetime "thumbnail_updated_at" + t.string "thumbnail_remote_url" t.index ["account_id"], name: "index_media_attachments_on_account_id" t.index ["scheduled_status_id"], name: "index_media_attachments_on_scheduled_status_id" t.index ["shortcode"], name: "index_media_attachments_on_shortcode", unique: true -- cgit From 65506bac3f3fe233b5b7b3241020bd74eb5c9259 Mon Sep 17 00:00:00 2001 From: ThibG Date: Tue, 30 Jun 2020 19:19:50 +0200 Subject: Add user notes on accounts (#14148) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add UserNote model * Add UI for user notes * Put comment in relationships entity * Add API to create user notes * Copy user notes to new account when receiving a Move activity * Address some of the review remarks * Replace modal by inline edition * Please CodeClimate * Button design changes * Change design again * Cancel note edition when pressing Escape * Fixes * Tweak design again * Move “Add note” item, and allow users to add notes to themselves * Rename UserNote into AccountNote, rename “comment” Relationship attribute to “note” --- .../api/v1/accounts/notes_controller.rb | 30 ++++++ app/javascript/mastodon/actions/account_notes.js | 69 ++++++++++++++ .../features/account/components/account_note.js | 103 +++++++++++++++++++++ .../mastodon/features/account/components/header.js | 11 +++ .../account/containers/account_note_container.js | 34 +++++++ .../features/account_timeline/components/header.js | 6 ++ .../containers/header_container.js | 5 + app/javascript/mastodon/reducers/account_notes.js | 44 +++++++++ app/javascript/mastodon/reducers/index.js | 2 + app/javascript/mastodon/reducers/relationships.js | 4 + app/javascript/styles/mastodon/components.scss | 62 ++++++++++++- app/models/account_note.rb | 20 ++++ app/models/concerns/account_interactions.rb | 8 ++ app/presenters/account_relationships_presenter.rb | 6 +- app/serializers/rest/relationship_serializer.rb | 6 +- app/workers/move_worker.rb | 17 ++++ config/locales/en.yml | 2 + config/routes.rb | 1 + db/migrate/20200628133322_create_account_notes.rb | 13 +++ db/schema.rb | 14 ++- spec/fabricators/account_note_fabricator.rb | 5 + spec/workers/move_worker_spec.rb | 27 ++++++ 22 files changed, 485 insertions(+), 4 deletions(-) create mode 100644 app/controllers/api/v1/accounts/notes_controller.rb create mode 100644 app/javascript/mastodon/actions/account_notes.js create mode 100644 app/javascript/mastodon/features/account/components/account_note.js create mode 100644 app/javascript/mastodon/features/account/containers/account_note_container.js create mode 100644 app/javascript/mastodon/reducers/account_notes.js create mode 100644 app/models/account_note.rb create mode 100644 db/migrate/20200628133322_create_account_notes.rb create mode 100644 spec/fabricators/account_note_fabricator.rb (limited to 'db') diff --git a/app/controllers/api/v1/accounts/notes_controller.rb b/app/controllers/api/v1/accounts/notes_controller.rb new file mode 100644 index 000000000..032e807d1 --- /dev/null +++ b/app/controllers/api/v1/accounts/notes_controller.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class Api::V1::Accounts::NotesController < Api::BaseController + include Authorization + + before_action -> { doorkeeper_authorize! :write, :'write:accounts' } + before_action :require_user! + before_action :set_account + + def create + if params[:comment].blank? + AccountNote.find_by(account: current_account, target_account: @account)&.destroy + else + @note = AccountNote.find_or_initialize_by(account: current_account, target_account: @account) + @note.comment = params[:comment] + @note.save! if @note.changed? + end + render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter + end + + private + + def set_account + @account = Account.find(params[:account_id]) + end + + def relationships_presenter + AccountRelationshipsPresenter.new([@account.id], current_user.account_id) + end +end diff --git a/app/javascript/mastodon/actions/account_notes.js b/app/javascript/mastodon/actions/account_notes.js new file mode 100644 index 000000000..059ed9e80 --- /dev/null +++ b/app/javascript/mastodon/actions/account_notes.js @@ -0,0 +1,69 @@ +import api from '../api'; + +export const ACCOUNT_NOTE_SUBMIT_REQUEST = 'ACCOUNT_NOTE_SUBMIT_REQUEST'; +export const ACCOUNT_NOTE_SUBMIT_SUCCESS = 'ACCOUNT_NOTE_SUBMIT_SUCCESS'; +export const ACCOUNT_NOTE_SUBMIT_FAIL = 'ACCOUNT_NOTE_SUBMIT_FAIL'; + +export const ACCOUNT_NOTE_INIT_EDIT = 'ACCOUNT_NOTE_INIT_EDIT'; +export const ACCOUNT_NOTE_CANCEL = 'ACCOUNT_NOTE_CANCEL'; + +export const ACCOUNT_NOTE_CHANGE_COMMENT = 'ACCOUNT_NOTE_CHANGE_COMMENT'; + +export function submitAccountNote() { + return (dispatch, getState) => { + dispatch(submitAccountNoteRequest()); + + const id = getState().getIn(['account_notes', 'edit', 'account_id']); + + api(getState).post(`/api/v1/accounts/${id}/note`, { + comment: getState().getIn(['account_notes', 'edit', 'comment']), + }).then(response => { + dispatch(submitAccountNoteSuccess(response.data)); + }).catch(error => dispatch(submitAccountNoteFail(error))); + }; +}; + +export function submitAccountNoteRequest() { + return { + type: ACCOUNT_NOTE_SUBMIT_REQUEST, + }; +}; + +export function submitAccountNoteSuccess(relationship) { + return { + type: ACCOUNT_NOTE_SUBMIT_SUCCESS, + relationship, + }; +}; + +export function submitAccountNoteFail(error) { + return { + type: ACCOUNT_NOTE_SUBMIT_FAIL, + error, + }; +}; + +export function initEditAccountNote(account) { + return (dispatch, getState) => { + const comment = getState().getIn(['relationships', account.get('id'), 'note']); + + dispatch({ + type: ACCOUNT_NOTE_INIT_EDIT, + account, + comment, + }); + }; +}; + +export function cancelAccountNote() { + return { + type: ACCOUNT_NOTE_CANCEL, + }; +}; + +export function changeAccountNoteComment(comment) { + return { + type: ACCOUNT_NOTE_CHANGE_COMMENT, + comment, + }; +}; diff --git a/app/javascript/mastodon/features/account/components/account_note.js b/app/javascript/mastodon/features/account/components/account_note.js new file mode 100644 index 000000000..832a96a6a --- /dev/null +++ b/app/javascript/mastodon/features/account/components/account_note.js @@ -0,0 +1,103 @@ +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import Icon from 'mastodon/components/icon'; +import Textarea from 'react-textarea-autosize'; + +const messages = defineMessages({ + placeholder: { id: 'account_note.placeholder', defaultMessage: 'No comment provided' }, +}); + +export default @injectIntl +class Header extends ImmutablePureComponent { + + static propTypes = { + account: ImmutablePropTypes.map.isRequired, + isEditing: PropTypes.bool, + isSubmitting: PropTypes.bool, + accountNote: PropTypes.string, + onEditAccountNote: PropTypes.func.isRequired, + onCancelAccountNote: PropTypes.func.isRequired, + onSaveAccountNote: PropTypes.func.isRequired, + onChangeAccountNote: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + handleChangeAccountNote = (e) => { + this.props.onChangeAccountNote(e.target.value); + }; + + componentWillUnmount () { + if (this.props.isEditing) { + this.props.onCancelAccountNote(); + } + } + + handleKeyDown = e => { + if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { + this.props.onSaveAccountNote(); + } else if (e.keyCode === 27) { + this.props.onCancelAccountNote(); + } + } + + render () { + const { account, accountNote, isEditing, isSubmitting, intl } = this.props; + + if (!account || (!accountNote && !isEditing)) { + return null; + } + + let action_buttons = null; + if (isEditing) { + action_buttons = ( +
+ +
+ +
+ ); + } + + let note_container = null; + if (isEditing) { + note_container = ( +