From 5092d17f2936146fa26e5d8a9b9e391f77010f28 Mon Sep 17 00:00:00 2001 From: "Acid Chicken (硫酸鶏)" Date: Sat, 2 Feb 2019 20:25:04 +0900 Subject: Add WebP support (#9879) * Add WebP support * Remove the changes to the tooltip refs: https://github.com/tootsuite/mastodon/pull/9879#pullrequestreview-199312528 --- app/javascript/mastodon/utils/resize_image.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/utils/resize_image.js b/app/javascript/mastodon/utils/resize_image.js index d1608094f..bbdbc865e 100644 --- a/app/javascript/mastodon/utils/resize_image.js +++ b/app/javascript/mastodon/utils/resize_image.js @@ -31,7 +31,7 @@ const loadImage = inputFile => new Promise((resolve, reject) => { }); const getOrientation = (img, type = 'image/png') => new Promise(resolve => { - if (type !== 'image/jpeg') { + if (!['image/jpeg', 'image/webp'].includes(type)) { resolve(1); return; } -- cgit From 750c67660de753065ec160b4e389ba0dda2f81cc Mon Sep 17 00:00:00 2001 From: tmm576 Date: Sat, 2 Feb 2019 14:22:05 -0500 Subject: Allow multiple files upload through web UI, including drag & drop (#9856) * Allow drag and drop uploads of multiple files to compose * Calculate aggregate upload progress for single action * Allow multiple uploads to compose through traditional input, consolidate update file limit logic, provide file limit feedback --- app/javascript/mastodon/actions/alerts.js | 4 +-- app/javascript/mastodon/actions/compose.js | 38 ++++++++++++++++------ .../features/compose/components/upload_button.js | 2 +- app/javascript/mastodon/features/ui/index.js | 2 +- .../mastodon/locales/defaultMessages.json | 11 ++++++- app/javascript/mastodon/locales/en.json | 1 + 6 files changed, 43 insertions(+), 15 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/actions/alerts.js b/app/javascript/mastodon/actions/alerts.js index 3f5d7ef46..50cd48a9e 100644 --- a/app/javascript/mastodon/actions/alerts.js +++ b/app/javascript/mastodon/actions/alerts.js @@ -22,7 +22,7 @@ export function clearAlert() { }; }; -export function showAlert(title, message) { +export function showAlert(title = messages.unexpectedTitle, message = messages.unexpectedMessage) { return { type: ALERT_SHOW, title, @@ -44,6 +44,6 @@ export function showAlertForError(error) { return showAlert(title, message); } else { console.error(error); - return showAlert(messages.unexpectedTitle, messages.unexpectedMessage); + return showAlert(); } } diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index a4352faab..0be2a5cd4 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -8,6 +8,8 @@ import resizeImage from '../utils/resize_image'; import { importFetchedAccounts } from './importer'; import { updateTimeline } from './timelines'; import { showAlertForError } from './alerts'; +import { showAlert } from './alerts'; +import { defineMessages } from 'react-intl'; let cancelFetchComposeSuggestionsAccounts; @@ -49,6 +51,10 @@ export const COMPOSE_UPLOAD_CHANGE_REQUEST = 'COMPOSE_UPLOAD_UPDATE_REQUEST' export const COMPOSE_UPLOAD_CHANGE_SUCCESS = 'COMPOSE_UPLOAD_UPDATE_SUCCESS'; export const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL'; +const messages = defineMessages({ + uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' }, +}); + export function changeCompose(text) { return { type: COMPOSE_CHANGE, @@ -184,20 +190,32 @@ export function submitComposeFail(error) { export function uploadCompose(files) { return function (dispatch, getState) { - if (getState().getIn(['compose', 'media_attachments']).size > 3) { + const uploadLimit = 4; + const media = getState().getIn(['compose', 'media_attachments']); + const total = Array.from(files).reduce((a, v) => a + v.size, 0); + const progress = new Array(files.length).fill(0); + + if (files.length + media.size > uploadLimit) { + dispatch(showAlert(undefined, messages.uploadErrorLimit)); return; } - dispatch(uploadComposeRequest()); - resizeImage(files[0]).then(file => { - const data = new FormData(); - data.append('file', file); - - return api(getState).post('/api/v1/media', data, { - onUploadProgress: ({ loaded, total }) => dispatch(uploadComposeProgress(loaded, total)), - }).then(({ data }) => dispatch(uploadComposeSuccess(data))); - }).catch(error => dispatch(uploadComposeFail(error))); + for (const [i, f] of Array.from(files).entries()) { + if (media.size + i > 3) break; + + resizeImage(f).then(file => { + const data = new FormData(); + data.append('file', file); + + return api(getState).post('/api/v1/media', data, { + onUploadProgress: function({ loaded }){ + progress[i] = loaded; + dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total)); + }, + }).then(({ data }) => dispatch(uploadComposeSuccess(data))); + }).catch(error => dispatch(uploadComposeFail(error))); + }; }; }; diff --git a/app/javascript/mastodon/features/compose/components/upload_button.js b/app/javascript/mastodon/features/compose/components/upload_button.js index b6fe770ea..db55ad70b 100644 --- a/app/javascript/mastodon/features/compose/components/upload_button.js +++ b/app/javascript/mastodon/features/compose/components/upload_button.js @@ -63,7 +63,7 @@ class UploadButton extends ImmutablePureComponent { key={resetFileKey} ref={this.setRef} type='file' - multiple={false} + multiple accept={acceptContentTypes.toArray().join(',')} onChange={this.handleChange} disabled={disabled} diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index f01c2bf24..93e45678f 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -263,7 +263,7 @@ class UI extends React.PureComponent { this.setState({ draggingOver: false }); this.dragTargets = []; - if (e.dataTransfer && e.dataTransfer.files.length === 1) { + if (e.dataTransfer && e.dataTransfer.files.length >= 1) { this.props.dispatch(uploadCompose(e.dataTransfer.files)); } } diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 6ac8160ba..89d4e6bba 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -12,6 +12,15 @@ ], "path": "app/javascript/mastodon/actions/alerts.json" }, + { + "descriptors": [ + { + "defaultMessage": "File upload limit exceeded.", + "id": "upload_error.limit" + } + ], + "path": "app/javascript/mastodon/actions/compose.json" + }, { "descriptors": [ { @@ -2285,4 +2294,4 @@ ], "path": "app/javascript/mastodon/features/video/index.json" } -] \ No newline at end of file +] diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 1dbf28022..e840b17d8 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -342,6 +342,7 @@ "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Drag & drop to upload", "upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)", + "upload_error.limit": "File upload limit exceeded.", "upload_form.description": "Describe for the visually impaired", "upload_form.focus": "Change preview", "upload_form.undo": "Delete", -- cgit From 364f2ff9aa2b4bf601d68a12bce758aeb5530467 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 4 Feb 2019 04:25:59 +0100 Subject: Add featured hashtags to profiles (#9755) * Add hashtag filter to profiles GET /@:username/tagged/:hashtag GET /api/v1/accounts/:id/statuses?tagged=:hashtag * Display featured hashtags on public profile * Use separate model for featured tags * Update featured hashtag counters on-write * Limit featured tags to 10 --- app/controllers/accounts_controller.rb | 14 +++++- .../api/v1/accounts/statuses_controller.rb | 5 +++ .../settings/featured_tags_controller.rb | 51 ++++++++++++++++++++++ app/controllers/settings/profiles_controller.rb | 2 +- app/controllers/settings/sessions_controller.rb | 1 + app/javascript/styles/mastodon/accounts.scss | 4 ++ app/javascript/styles/mastodon/admin.scss | 7 ++- app/javascript/styles/mastodon/widgets.scss | 7 ++- app/models/concerns/account_associations.rb | 1 + app/models/featured_tag.rb | 46 +++++++++++++++++++ app/models/tag.rb | 2 + app/services/process_hashtags_service.rb | 12 ++++- app/services/remove_status_service.rb | 4 ++ app/views/accounts/show.html.haml | 13 ++++++ app/views/settings/featured_tags/index.html.haml | 27 ++++++++++++ config/locales/en.yml | 5 +++ config/locales/simple_form.en.yml | 4 ++ config/navigation.rb | 1 + config/routes.rb | 2 + ...171005102658_create_account_moderation_notes.rb | 1 + db/migrate/20190203180359_create_featured_tags.rb | 12 +++++ db/schema.rb | 15 ++++++- spec/fabricators/featured_tag_fabricator.rb | 6 +++ spec/models/featured_tag_spec.rb | 4 ++ 24 files changed, 238 insertions(+), 8 deletions(-) create mode 100644 app/controllers/settings/featured_tags_controller.rb create mode 100644 app/models/featured_tag.rb create mode 100644 app/views/settings/featured_tags/index.html.haml create mode 100644 db/migrate/20190203180359_create_featured_tags.rb create mode 100644 spec/fabricators/featured_tag_fabricator.rb create mode 100644 spec/models/featured_tag_spec.rb (limited to 'app/javascript') diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index f788a9078..6e3a23073 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -57,6 +57,7 @@ class AccountsController < ApplicationController def filtered_statuses default_statuses.tap do |statuses| + statuses.merge!(hashtag_scope) if tag_requested? statuses.merge!(only_media_scope) if media_requested? statuses.merge!(no_replies_scope) unless replies_requested? end @@ -78,12 +79,15 @@ class AccountsController < ApplicationController Status.without_replies end + def hashtag_scope + Status.tagged_with(Tag.find_by(name: params[:tag].downcase)&.id) + end + def set_account @account = Account.find_local!(params[:username]) end def older_url - ::Rails.logger.info("older: max_id #{@statuses.last.id}, url #{pagination_url(max_id: @statuses.last.id)}") pagination_url(max_id: @statuses.last.id) end @@ -92,7 +96,9 @@ class AccountsController < ApplicationController end def pagination_url(max_id: nil, min_id: nil) - if media_requested? + if tag_requested? + short_account_tag_url(@account, params[:tag], max_id: max_id, min_id: min_id) + elsif media_requested? short_account_media_url(@account, max_id: max_id, min_id: min_id) elsif replies_requested? short_account_with_replies_url(@account, max_id: max_id, min_id: min_id) @@ -109,6 +115,10 @@ class AccountsController < ApplicationController request.path.ends_with?('/with_replies') end + def tag_requested? + request.path.ends_with?("/tagged/#{params[:tag]}") + end + def filtered_status_page(params) if params[:min_id].present? filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb index 6c2a5c141..6fdc827cb 100644 --- a/app/controllers/api/v1/accounts/statuses_controller.rb +++ b/app/controllers/api/v1/accounts/statuses_controller.rb @@ -33,6 +33,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController statuses.merge!(only_media_scope) if truthy_param?(:only_media) statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies) statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs) + statuses.merge!(hashtag_scope) if params[:tagged].present? statuses end @@ -67,6 +68,10 @@ class Api::V1::Accounts::StatusesController < Api::BaseController Status.without_reblogs end + def hashtag_scope + Status.tagged_with(Tag.find_by(name: params[:tagged])&.id) + end + def pagination_params(core_params) params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params) end diff --git a/app/controllers/settings/featured_tags_controller.rb b/app/controllers/settings/featured_tags_controller.rb new file mode 100644 index 000000000..19815e416 --- /dev/null +++ b/app/controllers/settings/featured_tags_controller.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +class Settings::FeaturedTagsController < Settings::BaseController + layout 'admin' + + before_action :authenticate_user! + before_action :set_featured_tags, only: :index + before_action :set_featured_tag, except: [:index, :create] + before_action :set_most_used_tags, only: :index + + def index + @featured_tag = FeaturedTag.new + end + + def create + @featured_tag = current_account.featured_tags.new(featured_tag_params) + @featured_tag.reset_data + + if @featured_tag.save + redirect_to settings_featured_tags_path + else + set_featured_tags + set_most_used_tags + + render :index + end + end + + def destroy + @featured_tag.destroy! + redirect_to settings_featured_tags_path + end + + private + + def set_featured_tag + @featured_tag = current_account.featured_tags.find(params[:id]) + end + + def set_featured_tags + @featured_tags = current_account.featured_tags.reject(&:new_record?) + end + + def set_most_used_tags + @most_used_tags = Tag.most_used(current_account).where.not(id: @featured_tags.map(&:id)).limit(10) + end + + def featured_tag_params + params.require(:featured_tag).permit(:name) + end +end diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb index db9081fdf..8b640cdca 100644 --- a/app/controllers/settings/profiles_controller.rb +++ b/app/controllers/settings/profiles_controller.rb @@ -32,6 +32,6 @@ class Settings::ProfilesController < Settings::BaseController end def set_account - @account = current_user.account + @account = current_account end end diff --git a/app/controllers/settings/sessions_controller.rb b/app/controllers/settings/sessions_controller.rb index 11b150ffd..84ebb21f2 100644 --- a/app/controllers/settings/sessions_controller.rb +++ b/app/controllers/settings/sessions_controller.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class Settings::SessionsController < Settings::BaseController + before_action :authenticate_user! before_action :set_session, only: :destroy def destroy diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss index 63a5c61b8..f4f458cf4 100644 --- a/app/javascript/styles/mastodon/accounts.scss +++ b/app/javascript/styles/mastodon/accounts.scss @@ -288,3 +288,7 @@ border-bottom: 0; } } + +.directory__tag .trends__item__current { + width: auto; +} diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 177f8145f..6d785707c 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -153,10 +153,15 @@ $content-width: 840px; font-weight: 500; } - .directory__tag a { + .directory__tag > a, + .directory__tag > div { box-shadow: none; } + .directory__tag .table-action-link .fa { + color: inherit; + } + .directory__tag h4 { font-size: 18px; font-weight: 700; diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss index c97337e4e..1eaf30c5b 100644 --- a/app/javascript/styles/mastodon/widgets.scss +++ b/app/javascript/styles/mastodon/widgets.scss @@ -269,7 +269,8 @@ box-sizing: border-box; margin-bottom: 10px; - a { + & > a, + & > div { display: flex; align-items: center; justify-content: space-between; @@ -279,7 +280,9 @@ text-decoration: none; color: inherit; box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + } + & > a { &:hover, &:active, &:focus { @@ -287,7 +290,7 @@ } } - &.active a { + &.active > a { background: $ui-highlight-color; cursor: default; } diff --git a/app/models/concerns/account_associations.rb b/app/models/concerns/account_associations.rb index 7dafeee34..397ec4a22 100644 --- a/app/models/concerns/account_associations.rb +++ b/app/models/concerns/account_associations.rb @@ -55,5 +55,6 @@ module AccountAssociations # Hashtags has_and_belongs_to_many :tags + has_many :featured_tags, -> { includes(:tag) }, dependent: :destroy, inverse_of: :account end end diff --git a/app/models/featured_tag.rb b/app/models/featured_tag.rb new file mode 100644 index 000000000..b5a10ad2d --- /dev/null +++ b/app/models/featured_tag.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true +# == Schema Information +# +# Table name: featured_tags +# +# id :bigint(8) not null, primary key +# account_id :bigint(8) +# tag_id :bigint(8) +# statuses_count :bigint(8) default(0), not null +# last_status_at :datetime +# created_at :datetime not null +# updated_at :datetime not null +# + +class FeaturedTag < ApplicationRecord + belongs_to :account, inverse_of: :featured_tags, required: true + belongs_to :tag, inverse_of: :featured_tags, required: true + + delegate :name, to: :tag, allow_nil: true + + validates :name, presence: true + validate :validate_featured_tags_limit, on: :create + + def name=(str) + self.tag = Tag.find_or_initialize_by(name: str.delete('#').mb_chars.downcase.to_s) + end + + def increment(timestamp) + update(statuses_count: statuses_count + 1, last_status_at: timestamp) + end + + def decrement(deleted_status_id) + update(statuses_count: [0, statuses_count - 1].max, last_status_at: account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).where.not(id: deleted_status_id).select(:created_at).first&.created_at) + end + + def reset_data + self.statuses_count = account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).count + self.last_status_at = account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).select(:created_at).first&.created_at + end + + private + + def validate_featured_tags_limit + errors.add(:base, I18n.t('featured_tags.errors.limit')) if account.featured_tags.count >= 10 + end +end diff --git a/app/models/tag.rb b/app/models/tag.rb index 99830ae92..4373e967b 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -14,6 +14,7 @@ class Tag < ApplicationRecord has_and_belongs_to_many :accounts has_and_belongs_to_many :sample_accounts, -> { searchable.discoverable.popular.limit(3) }, class_name: 'Account' + has_many :featured_tags, dependent: :destroy, inverse_of: :tag has_one :account_tag_stat, dependent: :destroy HASHTAG_NAME_RE = '[[:word:]_]*[[:alpha:]_·][[:word:]_]*' @@ -23,6 +24,7 @@ class Tag < ApplicationRecord scope :discoverable, -> { joins(:account_tag_stat).where(AccountTagStat.arel_table[:accounts_count].gt(0)).where(account_tag_stats: { hidden: false }).order(Arel.sql('account_tag_stats.accounts_count desc')) } scope :hidden, -> { where(account_tag_stats: { hidden: true }) } + scope :most_used, ->(account) { joins(:statuses).where(statuses: { account: account }).group(:id).order(Arel.sql('count(*) desc')) } delegate :accounts_count, :accounts_count=, diff --git a/app/services/process_hashtags_service.rb b/app/services/process_hashtags_service.rb index cf7471c98..d5ec076a8 100644 --- a/app/services/process_hashtags_service.rb +++ b/app/services/process_hashtags_service.rb @@ -2,12 +2,22 @@ class ProcessHashtagsService < BaseService def call(status, tags = []) - tags = Extractor.extract_hashtags(status.text) if status.local? + tags = Extractor.extract_hashtags(status.text) if status.local? + records = [] tags.map { |str| str.mb_chars.downcase }.uniq(&:to_s).each do |name| tag = Tag.where(name: name).first_or_create(name: name) + status.tags << tag + records << tag + TrendingTags.record_use!(tag, status.account, status.created_at) if status.public_visibility? end + + return unless status.public_visibility? || status.unlisted_visibility? + + status.account.featured_tags.where(tag_id: records.map(&:id)).each do |featured_tag| + featured_tag.increment(status.created_at) + end end end diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index 28c5224b0..2012f15fc 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -131,6 +131,10 @@ class RemoveStatusService < BaseService end def remove_from_hashtags + @account.featured_tags.where(tag_id: @status.tags.pluck(:id)).each do |featured_tag| + featured_tag.decrement(@status.id) + end + return unless @status.public_visibility? @tags.each do |hashtag| diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index 0ee9dd7de..23a595205 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -63,4 +63,17 @@ - @endorsed_accounts.each do |account| = account_link_to account + - @account.featured_tags.each do |featured_tag| + .directory__tag{ class: params[:tag] == featured_tag.name ? 'active' : nil } + = link_to short_account_tag_path(@account, featured_tag.tag) do + %h4 + = fa_icon 'hashtag' + = featured_tag.name + %small + - if featured_tag.last_status_at.nil? + = t('accounts.nothing_here') + - else + %time{ datetime: featured_tag.last_status_at.iso8601, title: l(featured_tag.last_status_at) }= l featured_tag.last_status_at + .trends__item__current= number_to_human featured_tag.statuses_count, strip_insignificant_zeros: true + = render 'application/sidebar' diff --git a/app/views/settings/featured_tags/index.html.haml b/app/views/settings/featured_tags/index.html.haml new file mode 100644 index 000000000..5f69517f3 --- /dev/null +++ b/app/views/settings/featured_tags/index.html.haml @@ -0,0 +1,27 @@ +- content_for :page_title do + = t('settings.featured_tags') + += simple_form_for @featured_tag, url: settings_featured_tags_path do |f| + = render 'shared/error_messages', object: @featured_tag + + .fields-group + = f.input :name, wrapper: :with_block_label, hint: safe_join([t('simple_form.hints.featured_tag.name'), safe_join(@most_used_tags.map { |tag| link_to("##{tag.name}", settings_featured_tags_path(featured_tag: { name: tag.name }), method: :post) }, ', ')], ' ') + + .actions + = f.button :button, t('featured_tags.add_new'), type: :submit + +%hr.spacer/ + +- @featured_tags.each do |featured_tag| + .directory__tag{ class: params[:tag] == featured_tag.name ? 'active' : nil } + %div + %h4 + = fa_icon 'hashtag' + = featured_tag.name + %small + - if featured_tag.last_status_at.nil? + = t('accounts.nothing_here') + - else + %time{ datetime: featured_tag.last_status_at.iso8601, title: l(featured_tag.last_status_at) }= l featured_tag.last_status_at + = table_link_to 'trash', t('filters.index.delete'), settings_featured_tag_path(featured_tag), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } + .trends__item__current= number_to_human featured_tag.statuses_count, strip_insignificant_zeros: true diff --git a/config/locales/en.yml b/config/locales/en.yml index f23f867e5..c92fc781c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -588,6 +588,10 @@ en: lists: Lists mutes: You mute storage: Media storage + featured_tags: + add_new: Add new + errors: + limit: You have already featured the maximum amount of hashtags filters: contexts: home: Home timeline @@ -807,6 +811,7 @@ en: development: Development edit_profile: Edit profile export: Data export + featured_tags: Featured hashtags followers: Authorized followers import: Import migrate: Account migration diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 325114755..3a2746a53 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -37,6 +37,8 @@ en: setting_theme: Affects how Mastodon looks when you're logged in from any device. username: Your username will be unique on %{domain} whole_word: When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word + featured_tag: + name: 'You might want to use one of these:' imports: data: CSV file exported from another Mastodon instance sessions: @@ -110,6 +112,8 @@ en: username: Username username_or_email: Username or Email whole_word: Whole word + featured_tag: + name: Hashtag interactions: must_be_follower: Block notifications from non-followers must_be_following: Block notifications from people you don't follow diff --git a/config/navigation.rb b/config/navigation.rb index a9521f956..1be621ac2 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -6,6 +6,7 @@ SimpleNavigation::Configuration.run do |navigation| primary.item :settings, safe_join([fa_icon('cog fw'), t('settings.settings')]), settings_profile_url do |settings| settings.item :profile, safe_join([fa_icon('user fw'), t('settings.edit_profile')]), settings_profile_url, highlights_on: %r{/settings/profile|/settings/migration} + settings.item :featured_tags, safe_join([fa_icon('hashtag fw'), t('settings.featured_tags')]), settings_featured_tags_url settings.item :preferences, safe_join([fa_icon('sliders fw'), t('settings.preferences')]), settings_preferences_url settings.item :notifications, safe_join([fa_icon('bell fw'), t('settings.notifications')]), settings_notifications_url settings.item :password, safe_join([fa_icon('lock fw'), t('auth.security')]), edit_user_registration_url, highlights_on: %r{/auth/edit|/settings/delete} diff --git a/config/routes.rb b/config/routes.rb index af49845cc..ded62981d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -74,6 +74,7 @@ Rails.application.routes.draw do get '/@:username', to: 'accounts#show', as: :short_account get '/@:username/with_replies', to: 'accounts#show', as: :short_account_with_replies get '/@:username/media', to: 'accounts#show', as: :short_account_media + get '/@:username/tagged/:tag', to: 'accounts#show', as: :short_account_tag get '/@:account_username/:id', to: 'statuses#show', as: :short_account_status get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status @@ -116,6 +117,7 @@ Rails.application.routes.draw do resource :migration, only: [:show, :update] resources :sessions, only: [:destroy] + resources :featured_tags, only: [:index, :create, :destroy] end resources :media, only: [:show] do diff --git a/db/migrate/20171005102658_create_account_moderation_notes.rb b/db/migrate/20171005102658_create_account_moderation_notes.rb index d1802b5b3..974ed9940 100644 --- a/db/migrate/20171005102658_create_account_moderation_notes.rb +++ b/db/migrate/20171005102658_create_account_moderation_notes.rb @@ -7,6 +7,7 @@ class CreateAccountModerationNotes < ActiveRecord::Migration[5.1] t.timestamps end + add_foreign_key :account_moderation_notes, :accounts, column: :target_account_id end end diff --git a/db/migrate/20190203180359_create_featured_tags.rb b/db/migrate/20190203180359_create_featured_tags.rb new file mode 100644 index 000000000..b08410a3a --- /dev/null +++ b/db/migrate/20190203180359_create_featured_tags.rb @@ -0,0 +1,12 @@ +class CreateFeaturedTags < ActiveRecord::Migration[5.2] + def change + create_table :featured_tags do |t| + t.references :account, foreign_key: { on_delete: :cascade } + t.references :tag, foreign_key: { on_delete: :cascade } + t.bigint :statuses_count, default: 0, null: false + t.datetime :last_status_at + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 44e00df40..e9fb358f8 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: 2019_02_01_012802) do +ActiveRecord::Schema.define(version: 2019_02_03_180359) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -250,6 +250,17 @@ ActiveRecord::Schema.define(version: 2019_02_01_012802) do t.index ["status_id"], name: "index_favourites_on_status_id" end + create_table "featured_tags", force: :cascade do |t| + t.bigint "account_id" + t.bigint "tag_id" + t.bigint "statuses_count", default: 0, null: false + t.datetime "last_status_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["account_id"], name: "index_featured_tags_on_account_id" + t.index ["tag_id"], name: "index_featured_tags_on_tag_id" + end + create_table "follow_requests", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false @@ -708,6 +719,8 @@ ActiveRecord::Schema.define(version: 2019_02_01_012802) do add_foreign_key "custom_filters", "accounts", on_delete: :cascade add_foreign_key "favourites", "accounts", name: "fk_5eb6c2b873", on_delete: :cascade add_foreign_key "favourites", "statuses", name: "fk_b0e856845e", on_delete: :cascade + add_foreign_key "featured_tags", "accounts", on_delete: :cascade + add_foreign_key "featured_tags", "tags", on_delete: :cascade add_foreign_key "follow_requests", "accounts", column: "target_account_id", name: "fk_9291ec025d", on_delete: :cascade add_foreign_key "follow_requests", "accounts", name: "fk_76d644b0e7", on_delete: :cascade add_foreign_key "follows", "accounts", column: "target_account_id", name: "fk_745ca29eac", on_delete: :cascade diff --git a/spec/fabricators/featured_tag_fabricator.rb b/spec/fabricators/featured_tag_fabricator.rb new file mode 100644 index 000000000..25cbdaac0 --- /dev/null +++ b/spec/fabricators/featured_tag_fabricator.rb @@ -0,0 +1,6 @@ +Fabricator(:featured_tag) do + account + tag + statuses_count 1_337 + last_status_at Time.now.utc +end diff --git a/spec/models/featured_tag_spec.rb b/spec/models/featured_tag_spec.rb new file mode 100644 index 000000000..07533e0b9 --- /dev/null +++ b/spec/models/featured_tag_spec.rb @@ -0,0 +1,4 @@ +require 'rails_helper' + +RSpec.describe FeaturedTag, type: :model do +end -- cgit From f86413fce29e70532fbbe5eab9ee8ff053f5552c Mon Sep 17 00:00:00 2001 From: mike castleman Date: Mon, 4 Feb 2019 01:45:29 -0200 Subject: change "finish tutorial" text to "finish toot-orial" (#9905) --- app/javascript/mastodon/features/introduction/index.js | 2 +- app/javascript/mastodon/locales/defaultMessages.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/features/introduction/index.js b/app/javascript/mastodon/features/introduction/index.js index e712b2f7d..754477bb9 100644 --- a/app/javascript/mastodon/features/introduction/index.js +++ b/app/javascript/mastodon/features/introduction/index.js @@ -89,7 +89,7 @@ const FrameInteractions = ({ onNext }) => (
- +
); diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 89d4e6bba..2dc755e0f 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -1457,7 +1457,7 @@ "id": "introduction.interactions.favourite.text" }, { - "defaultMessage": "Finish tutorial!", + "defaultMessage": "Finish toot-orial!", "id": "introduction.interactions.action" } ], -- cgit From e02a13f64e5c2c93fa73a67a4ce32a7d1df24760 Mon Sep 17 00:00:00 2001 From: rinsuki <428rinsuki+git@gmail.com> Date: Tue, 5 Feb 2019 07:14:57 +0900 Subject: Fix not showing custom emojis in share page emoji picker (#9970) --- app/javascript/mastodon/containers/compose_container.js | 3 +++ 1 file changed, 3 insertions(+) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/containers/compose_container.js b/app/javascript/mastodon/containers/compose_container.js index 5ee1d2f14..7bc7bbaa4 100644 --- a/app/javascript/mastodon/containers/compose_container.js +++ b/app/javascript/mastodon/containers/compose_container.js @@ -7,6 +7,7 @@ import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from '../locales'; import Compose from '../features/standalone/compose'; import initialState from '../initial_state'; +import { fetchCustomEmojis } from '../actions/custom_emojis'; const { localeData, messages } = getLocale(); addLocaleData(localeData); @@ -17,6 +18,8 @@ if (initialState) { store.dispatch(hydrateStore(initialState)); } +store.dispatch(fetchCustomEmojis()); + export default class TimelineContainer extends React.PureComponent { static propTypes = { -- cgit From 76d41475a89ebdd19b08b9e8a81e5e3c7dbd4001 Mon Sep 17 00:00:00 2001 From: trwnh Date: Mon, 4 Feb 2019 21:46:18 -0600 Subject: [UI] Fix whitespace being applied to div instead of p (#9968) * fix large line breaks * fix ascii art posts --- app/javascript/styles/mastodon/components.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/javascript') diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 10e094648..32fd77385 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -638,7 +638,6 @@ font-weight: 400; overflow: hidden; text-overflow: ellipsis; - white-space: pre-wrap; padding-top: 2px; color: $primary-text-color; @@ -662,6 +661,7 @@ p { margin-bottom: 20px; + white-space: pre-wrap; &:last-child { margin-bottom: 0; -- cgit From 3eb17a3beafbfa998f4d09f6d3310658195638d6 Mon Sep 17 00:00:00 2001 From: marcin mikołajczak Date: Tue, 5 Feb 2019 15:13:34 +0100 Subject: i18n: Update Polish translation 🇵 (#9974) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Mikołajczak --- app/javascript/mastodon/locales/pl.json | 1 + config/locales/pl.yml | 11 +++++++++++ config/locales/simple_form.pl.yml | 6 ++++++ 3 files changed, 18 insertions(+) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 0d16f7cfc..a1cda6c2d 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -342,6 +342,7 @@ "ui.beforeunload": "Utracisz tworzony wpis, jeżeli opuścisz Mastodona.", "upload_area.title": "Przeciągnij i upuść aby wysłać", "upload_button.label": "Dodaj zawartość multimedialną (JPEG, PNG, GIF, WebM, MP4, MOV)", + "upload_error.limit": "Przekroczono limit plików do wysłania.", "upload_form.description": "Wprowadź opis dla niewidomych i niedowidzących", "upload_form.focus": "Dopasuj podgląd", "upload_form.undo": "Usuń", diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 188f5c22b..e110db50d 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -602,6 +602,10 @@ pl: lists: Listy mutes: Wyciszeni storage: Urządzenie przechowujące dane + featured_tags: + add_new: Dodaj nowy + errors: + limit: Już przekroczyłeś(-aś) maksymalną liczbę wyróżnionych hashtagów filters: contexts: home: Strona główna @@ -646,10 +650,16 @@ pl: one: Coś jest wciąż nie tak! Przyjrzyj się poniższemu błędowi other: Coś jest wciąż nie tak! Przejrzyj poniższe błędy (%{count}) imports: + modes: + merge: Połącz + merge_long: Zachowaj obecne wpisy i dodaj nowe + overwrite: Nadpisz + overwrite_long: Zastąp obecne wpisy nowymi preface: Możesz zaimportować pewne dane (np. lista kont, które śledzisz lub blokujesz) do swojego konta na tym serwerze, korzystając z danych wyeksportowanych z innego serwera. success: Twoje dane zostały załadowane i zostaną niebawem przetworzone types: blocking: Lista blokowanych + domain_blocking: Lista zablokowanych domen following: Lista śledzonych muting: Lista wyciszonych upload: Załaduj @@ -825,6 +835,7 @@ pl: development: Tworzenie aplikacji edit_profile: Edytuj profil export: Eksportowanie danych + featured_tags: Wyróżnione hashtagi followers: Autoryzowani śledzący import: Importowanie danych migrate: Migracja konta diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml index bbc55e8a9..0ab045068 100644 --- a/config/locales/simple_form.pl.yml +++ b/config/locales/simple_form.pl.yml @@ -33,9 +33,12 @@ pl: setting_display_media_show_all: Zawsze pokazuj zawartość multimedialną jako wrażliwą setting_hide_network: Informacje o tym, kto Cię śledzi i kogo śledzisz nie będą widoczne setting_noindex: Wpływa na widoczność strony profilu i Twoich wpisów + setting_show_application: W informacjach o wpisie będzie widoczna informacja o aplikacji, z której został wysłany setting_theme: Zmienia wygląd Mastodona po zalogowaniu z dowolnego urządzenia. username: Twoja nazwa użytkownika będzie niepowtarzalna na %{domain} whole_word: Jeśli słowo lub fraza składa się jedynie z liter lub cyfr, filtr będzie zastosowany tylko do pełnych wystąpień + featured_tag: + name: 'Sugerujemy użycie jednego z następujących:' imports: data: Plik CSV wyeksportowany z innej instancji Mastodona sessions: @@ -101,6 +104,7 @@ pl: setting_hide_network: Ukryj swoją sieć setting_noindex: Nie indeksuj mojego profilu w wyszukiwarkach internetowych setting_reduce_motion: Ogranicz ruch w animacjach + setting_show_application: Informuj o aplikacji z której wysłano wpisy setting_system_font_ui: Używaj domyślnej czcionki systemu setting_theme: Motyw strony setting_unfollow_modal: Pytaj o potwierdzenie przed cofnięciem śledzenia @@ -109,6 +113,8 @@ pl: username: Nazwa użytkownika username_or_email: Nazwa użytkownika lub adres e-mail whole_word: Całe słowo + featured_tag: + name: Hashtag interactions: must_be_follower: Nie wyświetlaj powiadomień od osób, które Cię nie śledzą must_be_following: Nie wyświetlaj powiadomień od osób, których nie śledzisz -- cgit From 46e806cd2f14a5e45d66b4c23040855202818984 Mon Sep 17 00:00:00 2001 From: mayaeh Date: Wed, 6 Feb 2019 03:11:24 +0900 Subject: Rename from instance to server. (#9938) --- .../mastodon/features/getting_started/index.js | 2 +- .../mastodon/features/public_timeline/index.js | 2 +- .../features/ui/components/report_modal.js | 2 +- .../mastodon/locales/defaultMessages.json | 6 +-- app/javascript/mastodon/locales/en.json | 6 +-- app/javascript/mastodon/locales/ja.json | 8 ++-- config/locales/devise.en.yml | 6 +-- config/locales/devise.ja.yml | 6 +-- config/locales/en.yml | 34 +++++++-------- config/locales/ja.yml | 48 +++++++++++----------- config/locales/simple_form.en.yml | 2 +- config/locales/simple_form.ja.yml | 2 +- 12 files changed, 62 insertions(+), 62 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index e277a73c7..e1f84de27 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -160,7 +160,7 @@ class GettingStarted extends ImmutablePureComponent { {invitesEnabled &&
  • ·
  • } {multiColumn &&
  • ·
  • }
  • ·
  • -
  • ·
  • +
  • ·
  • ·
  • ·
  • ·
  • diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js index d640033eb..2b7d9c56f 100644 --- a/app/javascript/mastodon/features/public_timeline/index.js +++ b/app/javascript/mastodon/features/public_timeline/index.js @@ -124,7 +124,7 @@ class PublicTimeline extends React.PureComponent { onLoadMore={this.handleLoadMore} trackScroll={!pinned} scrollKey={`public_timeline-${columnId}`} - emptyMessage={} + emptyMessage={} shouldUpdateScroll={shouldUpdateScroll} /> diff --git a/app/javascript/mastodon/features/ui/components/report_modal.js b/app/javascript/mastodon/features/ui/components/report_modal.js index bc6b18664..2e41f784d 100644 --- a/app/javascript/mastodon/features/ui/components/report_modal.js +++ b/app/javascript/mastodon/features/ui/components/report_modal.js @@ -97,7 +97,7 @@ class ReportModal extends ImmutablePureComponent {
    -

    +