Date: Fri, 1 Feb 2019 00:15:38 +0100
Subject: Fix link color in high-contrast theme, add underlines (#9949)
Improve sorting of default themes in the dropdown
---
app/javascript/styles/contrast/diff.scss | 51 ++++++++++++++++++++++++++++++++
config/locales/en.yml | 6 ++--
2 files changed, 54 insertions(+), 3 deletions(-)
(limited to 'config/locales/en.yml')
diff --git a/app/javascript/styles/contrast/diff.scss b/app/javascript/styles/contrast/diff.scss
index eee9ecc3e..7d8993a50 100644
--- a/app/javascript/styles/contrast/diff.scss
+++ b/app/javascript/styles/contrast/diff.scss
@@ -12,3 +12,54 @@
}
}
}
+
+.status__content a,
+.reply-indicator__content a {
+ color: lighten($ui-highlight-color, 12%);
+ text-decoration: underline;
+
+ &.mention {
+ text-decoration: none;
+ }
+
+ &.mention span {
+ text-decoration: underline;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+ }
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+
+ &.status__content__spoiler-link {
+ color: $secondary-text-color;
+ text-decoration: none;
+ }
+}
+
+.status__content__read-more-button {
+ text-decoration: underline;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+}
+
+.getting-started__footer a {
+ text-decoration: underline;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+}
diff --git a/config/locales/en.yml b/config/locales/en.yml
index fc0f63415..c7ef36598 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -929,9 +929,9 @@ en:
Originally adapted from the Discourse privacy policy .
title: "%{instance} Terms of Service and Privacy Policy"
themes:
- contrast: High contrast
- default: Mastodon
- mastodon-light: Mastodon (light)
+ contrast: Mastodon (High contrast)
+ default: Mastodon (Dark)
+ mastodon-light: Mastodon (Light)
time:
formats:
default: "%b %d, %Y, %H:%M"
--
cgit
From d14c276e58f0f223b0e4889d342a948c961081b2 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Sun, 3 Feb 2019 03:59:51 +0100
Subject: Add option to overwrite imported data (#9962)
* Add option to overwrite imported data
Fix #7465
* Add import for domain blocks
---
app/models/account_domain_block.rb | 1 +
app/models/concerns/domain_normalizable.rb | 2 +-
app/models/export.rb | 1 +
app/models/import.rb | 14 +++-
app/services/import_service.rb | 90 ++++++++++++++++++++++
app/views/settings/imports/show.html.haml | 7 +-
app/workers/import/relationship_worker.rb | 8 +-
app/workers/import_worker.rb | 38 +--------
config/locales/en.yml | 6 ++
.../20190201012802_add_overwrite_to_imports.rb | 17 ++++
db/schema.rb | 3 +-
spec/models/concerns/account_interactions_spec.rb | 4 +-
12 files changed, 148 insertions(+), 43 deletions(-)
create mode 100644 app/services/import_service.rb
create mode 100644 db/migrate/20190201012802_add_overwrite_to_imports.rb
(limited to 'config/locales/en.yml')
diff --git a/app/models/account_domain_block.rb b/app/models/account_domain_block.rb
index e352000c3..7c0d60379 100644
--- a/app/models/account_domain_block.rb
+++ b/app/models/account_domain_block.rb
@@ -12,6 +12,7 @@
class AccountDomainBlock < ApplicationRecord
include Paginable
+ include DomainNormalizable
belongs_to :account
validates :domain, presence: true, uniqueness: { scope: :account_id }
diff --git a/app/models/concerns/domain_normalizable.rb b/app/models/concerns/domain_normalizable.rb
index dff3e5414..fb84058fc 100644
--- a/app/models/concerns/domain_normalizable.rb
+++ b/app/models/concerns/domain_normalizable.rb
@@ -10,6 +10,6 @@ module DomainNormalizable
private
def normalize_domain
- self.domain = TagManager.instance.normalize_domain(domain)
+ self.domain = TagManager.instance.normalize_domain(domain&.strip)
end
end
diff --git a/app/models/export.rb b/app/models/export.rb
index a2520e9c2..fc4bb6964 100644
--- a/app/models/export.rb
+++ b/app/models/export.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'csv'
class Export
diff --git a/app/models/import.rb b/app/models/import.rb
index 55e970b0d..a7a0d8065 100644
--- a/app/models/import.rb
+++ b/app/models/import.rb
@@ -13,20 +13,30 @@
# data_file_size :integer
# data_updated_at :datetime
# account_id :bigint(8) not null
+# overwrite :boolean default(FALSE), not null
#
class Import < ApplicationRecord
- FILE_TYPES = ['text/plain', 'text/csv'].freeze
+ FILE_TYPES = %w(text/plain text/csv).freeze
+ MODES = %i(merge overwrite).freeze
self.inheritance_column = false
belongs_to :account
- enum type: [:following, :blocking, :muting]
+ enum type: [:following, :blocking, :muting, :domain_blocking]
validates :type, presence: true
has_attached_file :data
validates_attachment_content_type :data, content_type: FILE_TYPES
validates_attachment_presence :data
+
+ def mode
+ overwrite? ? :overwrite : :merge
+ end
+
+ def mode=(str)
+ self.overwrite = str.to_sym == :overwrite
+ end
end
diff --git a/app/services/import_service.rb b/app/services/import_service.rb
new file mode 100644
index 000000000..3f558626e
--- /dev/null
+++ b/app/services/import_service.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'csv'
+
+class ImportService < BaseService
+ ROWS_PROCESSING_LIMIT = 20_000
+
+ def call(import)
+ @import = import
+ @account = @import.account
+ @data = CSV.new(import_data).reject(&:blank?)
+
+ case @import.type
+ when 'following'
+ import_follows!
+ when 'blocking'
+ import_blocks!
+ when 'muting'
+ import_mutes!
+ when 'domain_blocking'
+ import_domain_blocks!
+ end
+ end
+
+ private
+
+ def import_follows!
+ import_relationships!('follow', 'unfollow', @account.following, follow_limit)
+ end
+
+ def import_blocks!
+ import_relationships!('block', 'unblock', @account.blocking, ROWS_PROCESSING_LIMIT)
+ end
+
+ def import_mutes!
+ import_relationships!('mute', 'unmute', @account.muting, ROWS_PROCESSING_LIMIT)
+ end
+
+ def import_domain_blocks!
+ items = @data.take(ROWS_PROCESSING_LIMIT).map { |row| row.first.strip }
+
+ if @import.overwrite?
+ presence_hash = items.each_with_object({}) { |id, mapping| mapping[id] = true }
+
+ @account.domain_blocks.find_each do |domain_block|
+ if presence_hash[domain_block.domain]
+ items.delete(domain_block.domain)
+ else
+ @account.unblock_domain!(domain_block.domain)
+ end
+ end
+ end
+
+ items.each do |domain|
+ @account.block_domain!(domain)
+ end
+
+ AfterAccountDomainBlockWorker.push_bulk(items) do |domain|
+ [@account.id, domain]
+ end
+ end
+
+ def import_relationships!(action, undo_action, overwrite_scope, limit)
+ items = @data.take(limit).map { |row| row.first.strip }
+
+ if @import.overwrite?
+ presence_hash = items.each_with_object({}) { |id, mapping| mapping[id] = true }
+
+ overwrite_scope.find_each do |target_account|
+ if presence_hash[target_account.acct]
+ items.delete(target_account.acct)
+ else
+ Import::RelationshipWorker.perform_async(@account.id, target_account.acct, undo_action)
+ end
+ end
+ end
+
+ Import::RelationshipWorker.push_bulk(items) do |acct|
+ [@account.id, acct, action]
+ end
+ end
+
+ def import_data
+ Paperclip.io_adapters.for(@import.data).read
+ end
+
+ def follow_limit
+ FollowLimitValidator.limit_for_account(@account)
+ end
+end
diff --git a/app/views/settings/imports/show.html.haml b/app/views/settings/imports/show.html.haml
index 4512fc714..7bb4beb01 100644
--- a/app/views/settings/imports/show.html.haml
+++ b/app/views/settings/imports/show.html.haml
@@ -5,8 +5,11 @@
.field-group
= f.input :type, collection: Import.types.keys, wrapper: :with_block_label, include_blank: false, label_method: lambda { |type| I18n.t("imports.types.#{type}") }, hint: t('imports.preface')
- .field-group
- = f.input :data, wrapper: :with_block_label, hint: t('simple_form.hints.imports.data')
+ .fields-row
+ .fields-group.fields-row__column.fields-row__column-6
+ = f.input :data, wrapper: :with_block_label, hint: t('simple_form.hints.imports.data')
+ .fields-group.fields-row__column.fields-row__column-6
+ = f.input :mode, as: :radio_buttons, collection: Import::MODES, label_method: lambda { |mode| safe_join([I18n.t("imports.modes.#{mode}"), content_tag(:span, I18n.t("imports.modes.#{mode}_long"), class: 'hint')]) }, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
.actions
= f.button :button, t('imports.upload'), type: :submit
diff --git a/app/workers/import/relationship_worker.rb b/app/workers/import/relationship_worker.rb
index 1dd8bf8fb..e9db20a46 100644
--- a/app/workers/import/relationship_worker.rb
+++ b/app/workers/import/relationship_worker.rb
@@ -13,11 +13,17 @@ class Import::RelationshipWorker
case relationship
when 'follow'
- FollowService.new.call(from_account, target_account.acct)
+ FollowService.new.call(from_account, target_account)
+ when 'unfollow'
+ UnfollowService.new.call(from_account, target_account)
when 'block'
BlockService.new.call(from_account, target_account)
+ when 'unblock'
+ UnblockService.new.call(from_account, target_account)
when 'mute'
MuteService.new.call(from_account, target_account)
+ when 'unmute'
+ UnmuteService.new.call(from_account, target_account)
end
rescue ActiveRecord::RecordNotFound
true
diff --git a/app/workers/import_worker.rb b/app/workers/import_worker.rb
index aeb221cf6..dfa71b29e 100644
--- a/app/workers/import_worker.rb
+++ b/app/workers/import_worker.rb
@@ -1,44 +1,14 @@
# frozen_string_literal: true
-require 'csv'
-
class ImportWorker
include Sidekiq::Worker
sidekiq_options queue: 'pull', retry: false
- attr_reader :import
-
def perform(import_id)
- @import = Import.find(import_id)
-
- Import::RelationshipWorker.push_bulk(import_rows) do |row|
- [@import.account_id, row.first, relationship_type]
- end
-
- @import.destroy
- end
-
- private
-
- def import_contents
- Paperclip.io_adapters.for(@import.data).read
- end
-
- def relationship_type
- case @import.type
- when 'following'
- 'follow'
- when 'blocking'
- 'block'
- when 'muting'
- 'mute'
- end
- end
-
- def import_rows
- rows = CSV.new(import_contents).reject(&:blank?)
- rows = rows.take(FollowLimitValidator.limit_for_account(@import.account)) if @import.type == 'following'
- rows
+ import = Import.find(import_id)
+ ImportService.new.call(import)
+ ensure
+ import&.destroy
end
end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index c7ef36598..f23f867e5 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -628,10 +628,16 @@ en:
one: Something isn't quite right yet! Please review the error below
other: Something isn't quite right yet! Please review %{count} errors below
imports:
+ modes:
+ merge: Merge
+ merge_long: Keep existing records and add new ones
+ overwrite: Overwrite
+ overwrite_long: Replace current records with the new ones
preface: You can import data that you have exported from another instance, such as a list of the people you are following or blocking.
success: Your data was successfully uploaded and will now be processed in due time
types:
blocking: Blocking list
+ domain_blocking: Domain blocking list
following: Following list
muting: Muting list
upload: Upload
diff --git a/db/migrate/20190201012802_add_overwrite_to_imports.rb b/db/migrate/20190201012802_add_overwrite_to_imports.rb
new file mode 100644
index 000000000..89b262cc7
--- /dev/null
+++ b/db/migrate/20190201012802_add_overwrite_to_imports.rb
@@ -0,0 +1,17 @@
+require Rails.root.join('lib', 'mastodon', 'migration_helpers')
+
+class AddOverwriteToImports < ActiveRecord::Migration[5.2]
+ include Mastodon::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ def up
+ safety_assured do
+ add_column_with_default :imports, :overwrite, :boolean, default: false, allow_null: false
+ end
+ end
+
+ def down
+ remove_column :imports, :overwrite, :boolean
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 3487adf08..44e00df40 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_01_17_114553) do
+ActiveRecord::Schema.define(version: 2019_02_01_012802) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -290,6 +290,7 @@ ActiveRecord::Schema.define(version: 2019_01_17_114553) do
t.integer "data_file_size"
t.datetime "data_updated_at"
t.bigint "account_id", null: false
+ t.boolean "overwrite", default: false, null: false
end
create_table "invites", force: :cascade do |t|
diff --git a/spec/models/concerns/account_interactions_spec.rb b/spec/models/concerns/account_interactions_spec.rb
index 8df52b770..e8ef61f66 100644
--- a/spec/models/concerns/account_interactions_spec.rb
+++ b/spec/models/concerns/account_interactions_spec.rb
@@ -237,9 +237,9 @@ describe AccountInteractions do
end
describe '#block_domain!' do
- let(:domain_block) { Fabricate(:domain_block) }
+ let(:domain) { 'example.com' }
- subject { account.block_domain!(domain_block) }
+ subject { account.block_domain!(domain) }
it 'creates and returns AccountDomainBlock' do
expect do
--
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 'config/locales/en.yml')
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 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 'config/locales/en.yml')
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 {