Merge branch 'master' into gs-master
commit
870d71b78b
|
@ -22,6 +22,14 @@ module Admin
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
if @custom_emoji.update(resource_params)
|
||||||
|
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.updated_msg')
|
||||||
|
else
|
||||||
|
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.update_failed_msg')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@custom_emoji.destroy
|
@custom_emoji.destroy
|
||||||
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.destroyed_msg')
|
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.destroyed_msg')
|
||||||
|
@ -56,7 +64,7 @@ module Admin
|
||||||
end
|
end
|
||||||
|
|
||||||
def resource_params
|
def resource_params
|
||||||
params.require(:custom_emoji).permit(:shortcode, :image)
|
params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker)
|
||||||
end
|
end
|
||||||
|
|
||||||
def filtered_custom_emojis
|
def filtered_custom_emojis
|
||||||
|
|
|
@ -9,6 +9,10 @@ module JsonLdHelper
|
||||||
value.is_a?(Array) ? value.first : value
|
value.is_a?(Array) ? value.first : value
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def as_array(value)
|
||||||
|
value.is_a?(Array) ? value : [value]
|
||||||
|
end
|
||||||
|
|
||||||
def value_or_id(value)
|
def value_or_id(value)
|
||||||
value.is_a?(String) || value.nil? ? value : value['id']
|
value.is_a?(String) || value.nil? ? value : value['id']
|
||||||
end
|
end
|
||||||
|
|
|
@ -46,7 +46,7 @@ const getFrequentlyUsedEmojis = createSelector([
|
||||||
|
|
||||||
const getCustomEmojis = createSelector([
|
const getCustomEmojis = createSelector([
|
||||||
state => state.get('custom_emojis'),
|
state => state.get('custom_emojis'),
|
||||||
], emojis => emojis.sort((a, b) => {
|
], emojis => emojis.filter(e => e.get('visible_in_picker')).sort((a, b) => {
|
||||||
const aShort = a.get('shortcode').toLowerCase();
|
const aShort = a.get('shortcode').toLowerCase();
|
||||||
const bShort = b.get('shortcode').toLowerCase();
|
const bShort = b.get('shortcode').toLowerCase();
|
||||||
|
|
||||||
|
|
|
@ -53,9 +53,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_tags(status)
|
def process_tags(status)
|
||||||
return unless @object['tag'].is_a?(Array)
|
return if @object['tag'].nil?
|
||||||
|
|
||||||
@object['tag'].each do |tag|
|
as_array(@object['tag']).each do |tag|
|
||||||
case tag['type']
|
case tag['type']
|
||||||
when 'Hashtag'
|
when 'Hashtag'
|
||||||
process_hashtag tag, status
|
process_hashtag tag, status
|
||||||
|
@ -103,9 +103,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_attachments(status)
|
def process_attachments(status)
|
||||||
return unless @object['attachment'].is_a?(Array)
|
return if @object['attachment'].nil?
|
||||||
|
|
||||||
@object['attachment'].each do |attachment|
|
as_array(@object['attachment']).each do |attachment|
|
||||||
next if unsupported_media_type?(attachment['mediaType']) || attachment['url'].blank?
|
next if unsupported_media_type?(attachment['mediaType']) || attachment['url'].blank?
|
||||||
|
|
||||||
href = Addressable::URI.parse(attachment['url']).normalize.to_s
|
href = Addressable::URI.parse(attachment['url']).normalize.to_s
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
# disabled :boolean default(FALSE), not null
|
# disabled :boolean default(FALSE), not null
|
||||||
# uri :string
|
# uri :string
|
||||||
# image_remote_url :string
|
# image_remote_url :string
|
||||||
|
# visible_in_picker :boolean default(TRUE), not null
|
||||||
#
|
#
|
||||||
|
|
||||||
class CustomEmoji < ApplicationRecord
|
class CustomEmoji < ApplicationRecord
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
class REST::CustomEmojiSerializer < ActiveModel::Serializer
|
class REST::CustomEmojiSerializer < ActiveModel::Serializer
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
|
|
||||||
attributes :shortcode, :url, :static_url
|
attributes :shortcode, :url, :static_url, :visible_in_picker
|
||||||
|
|
||||||
def url
|
def url
|
||||||
full_asset_url(object.image.url)
|
full_asset_url(object.image.url)
|
||||||
|
|
|
@ -9,7 +9,12 @@
|
||||||
- else
|
- else
|
||||||
= custom_emoji.domain
|
= custom_emoji.domain
|
||||||
%td
|
%td
|
||||||
- unless custom_emoji.local?
|
- if custom_emoji.local?
|
||||||
|
- if custom_emoji.visible_in_picker
|
||||||
|
= table_link_to 'eye', t('admin.custom_emojis.listed'), admin_custom_emoji_path(custom_emoji, custom_emoji: { visible_in_picker: false }), method: :patch
|
||||||
|
- else
|
||||||
|
= table_link_to 'eye-slash', t('admin.custom_emojis.unlisted'), admin_custom_emoji_path(custom_emoji, custom_emoji: { visible_in_picker: true }), method: :patch
|
||||||
|
- else
|
||||||
= table_link_to 'copy', t('admin.custom_emojis.copy'), copy_admin_custom_emoji_path(custom_emoji, page: params[:page]), method: :post
|
= table_link_to 'copy', t('admin.custom_emojis.copy'), copy_admin_custom_emoji_path(custom_emoji, page: params[:page]), method: :post
|
||||||
%td
|
%td
|
||||||
- if custom_emoji.disabled?
|
- if custom_emoji.disabled?
|
||||||
|
|
|
@ -130,11 +130,15 @@ en:
|
||||||
enable: Enable
|
enable: Enable
|
||||||
enabled_msg: Successfully enabled that emoji
|
enabled_msg: Successfully enabled that emoji
|
||||||
image_hint: PNG up to 50KB
|
image_hint: PNG up to 50KB
|
||||||
|
listed: Listed
|
||||||
new:
|
new:
|
||||||
title: Add new custom emoji
|
title: Add new custom emoji
|
||||||
shortcode: Shortcode
|
shortcode: Shortcode
|
||||||
shortcode_hint: At least 2 characters, only alphanumeric characters and underscores
|
shortcode_hint: At least 2 characters, only alphanumeric characters and underscores
|
||||||
title: Custom emojis
|
title: Custom emojis
|
||||||
|
unlisted: Unlisted
|
||||||
|
update_failed_msg: Could not update that emoji
|
||||||
|
updated_msg: Emoji successfully updated!
|
||||||
upload: Upload
|
upload: Upload
|
||||||
domain_blocks:
|
domain_blocks:
|
||||||
add_new: Add new
|
add_new: Add new
|
||||||
|
|
|
@ -147,7 +147,7 @@ Rails.application.routes.draw do
|
||||||
resource :two_factor_authentication, only: [:destroy]
|
resource :two_factor_authentication, only: [:destroy]
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :custom_emojis, only: [:index, :new, :create, :destroy] do
|
resources :custom_emojis, only: [:index, :new, :create, :update, :destroy] do
|
||||||
member do
|
member do
|
||||||
post :copy
|
post :copy
|
||||||
post :enable
|
post :enable
|
||||||
|
|
|
@ -3,48 +3,62 @@ class FixReblogsInFeeds < ActiveRecord::Migration[5.1]
|
||||||
redis = Redis.current
|
redis = Redis.current
|
||||||
fm = FeedManager.instance
|
fm = FeedManager.instance
|
||||||
|
|
||||||
|
# Old scheme:
|
||||||
|
# Each user's feed zset had a series of score:value entries,
|
||||||
|
# where "regular" statuses had the same score and value (their
|
||||||
|
# ID). Reblogs had a score of the reblogging status' ID, and a
|
||||||
|
# value of the reblogged status' ID.
|
||||||
|
|
||||||
|
# New scheme:
|
||||||
|
# The feed contains only entries with the same score and value.
|
||||||
|
# Reblogs result in the reblogging status being added to the
|
||||||
|
# feed, with an entry in a reblog tracking zset (where the score
|
||||||
|
# is once again set to the reblogging status' ID, and the value
|
||||||
|
# is set to the reblogged status' ID). This is safe for Redis'
|
||||||
|
# float coersion because in this reblog tracking zset, we only
|
||||||
|
# need the rebloggging status' ID to be able to stop tracking
|
||||||
|
# entries after they have gotten too far down the feed, which
|
||||||
|
# does not require an exact value.
|
||||||
|
|
||||||
|
# This process reads all feeds and writes 3 times for each reblogs.
|
||||||
|
# So we use Lua script to avoid overhead between Ruby and Redis.
|
||||||
|
script = <<-LUA
|
||||||
|
local timeline_key = KEYS[1]
|
||||||
|
local reblog_key = KEYS[2]
|
||||||
|
|
||||||
|
-- So, first, we iterate over the user's feed to find any reblogs.
|
||||||
|
local items = redis.call('zrange', timeline_key, 0, -1, 'withscores')
|
||||||
|
|
||||||
|
for i = 1, #items, 2 do
|
||||||
|
local reblogged_id = items[i]
|
||||||
|
local reblogging_id = items[i + 1]
|
||||||
|
if (reblogged_id ~= reblogging_id) then
|
||||||
|
|
||||||
|
-- The score and value don't match, so this is a reblog.
|
||||||
|
-- (note that we're transitioning from IDs < 53 bits so we
|
||||||
|
-- don't have to worry about the loss of precision)
|
||||||
|
|
||||||
|
-- Remove the old entry
|
||||||
|
redis.call('zrem', timeline_key, reblogged_id)
|
||||||
|
|
||||||
|
-- Add a new one for the reblogging status
|
||||||
|
redis.call('zadd', timeline_key, reblogging_id, reblogging_id)
|
||||||
|
|
||||||
|
-- Track the fact that this was a reblog
|
||||||
|
redis.call('zadd', reblog_key, reblogging_id, reblogged_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
LUA
|
||||||
|
script_hash = redis.script(:load, script)
|
||||||
|
|
||||||
# find_each is batched on the database side.
|
# find_each is batched on the database side.
|
||||||
User.includes(:account).find_each do |user|
|
User.includes(:account).find_each do |user|
|
||||||
account = user.account
|
account = user.account
|
||||||
|
|
||||||
# Old scheme:
|
|
||||||
# Each user's feed zset had a series of score:value entries,
|
|
||||||
# where "regular" statuses had the same score and value (their
|
|
||||||
# ID). Reblogs had a score of the reblogging status' ID, and a
|
|
||||||
# value of the reblogged status' ID.
|
|
||||||
|
|
||||||
# New scheme:
|
|
||||||
# The feed contains only entries with the same score and value.
|
|
||||||
# Reblogs result in the reblogging status being added to the
|
|
||||||
# feed, with an entry in a reblog tracking zset (where the score
|
|
||||||
# is once again set to the reblogging status' ID, and the value
|
|
||||||
# is set to the reblogged status' ID). This is safe for Redis'
|
|
||||||
# float coersion because in this reblog tracking zset, we only
|
|
||||||
# need the rebloggging status' ID to be able to stop tracking
|
|
||||||
# entries after they have gotten too far down the feed, which
|
|
||||||
# does not require an exact value.
|
|
||||||
|
|
||||||
# So, first, we iterate over the user's feed to find any reblogs.
|
|
||||||
timeline_key = fm.key(:home, account.id)
|
timeline_key = fm.key(:home, account.id)
|
||||||
reblog_key = fm.key(:home, account.id, 'reblogs')
|
reblog_key = fm.key(:home, account.id, 'reblogs')
|
||||||
redis.zrange(timeline_key, 0, -1, with_scores: true).each do |entry|
|
|
||||||
next if entry[0] == entry[1]
|
|
||||||
|
|
||||||
# The score and value don't match, so this is a reblog.
|
redis.evalsha(script_hash, [timeline_key, reblog_key])
|
||||||
# (note that we're transitioning from IDs < 53 bits so we
|
|
||||||
# don't have to worry about the loss of precision)
|
|
||||||
|
|
||||||
reblogged_id, reblogging_id = entry
|
|
||||||
|
|
||||||
# Remove the old entry
|
|
||||||
redis.zrem(timeline_key, reblogged_id)
|
|
||||||
|
|
||||||
# Add a new one for the reblogging status
|
|
||||||
redis.zadd(timeline_key, reblogging_id, reblogging_id)
|
|
||||||
|
|
||||||
# Track the fact that this was a reblog
|
|
||||||
redis.zadd(reblog_key, reblogging_id, reblogged_id)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
class AddVisibleInPickerToCustomEmoji < ActiveRecord::Migration[5.1]
|
||||||
|
def change
|
||||||
|
safety_assured {
|
||||||
|
add_column :custom_emojis, :visible_in_picker, :boolean, default: true, null: false
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -111,6 +111,7 @@ ActiveRecord::Schema.define(version: 20171021191900) do
|
||||||
t.boolean "disabled", default: false, null: false
|
t.boolean "disabled", default: false, null: false
|
||||||
t.string "uri"
|
t.string "uri"
|
||||||
t.string "image_remote_url"
|
t.string "image_remote_url"
|
||||||
|
t.boolean "visible_in_picker", default: true, null: false
|
||||||
t.index ["shortcode", "domain"], name: "index_custom_emojis_on_shortcode_and_domain", unique: true
|
t.index ["shortcode", "domain"], name: "index_custom_emojis_on_shortcode_and_domain", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue