From 9bd23dc4e51ba47283a8e3a66cd94b4e624a5235 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 27 May 2018 21:45:30 +0200 Subject: Track trending tags (#7638) * Track trending tags - Half-life of 1 day - Historical usage in daily buckets (last 7 days stored) - GET /api/v1/trends Fix #271 * Add trends to web UI * Don't render compose form on search route, adjust search results header * Disqualify tag from trends if it's in disallowed hashtags setting * Count distinct accounts using tag, ignore silenced accounts --- config/routes.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'config') diff --git a/config/routes.rb b/config/routes.rb index 3042b5ea0..2fcb885ed 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -254,6 +254,7 @@ Rails.application.routes.draw do resources :mutes, only: [:index] resources :favourites, only: [:index] resources :reports, only: [:index, :create] + resources :trends, only: [:index] namespace :apps do get :verify_credentials, to: 'credentials#show' -- cgit From d95642f6d913a99fc44f0ac0695d53534afb7962 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Tue, 29 May 2018 07:43:47 +0900 Subject: Cache attachments on external host with service worker (#7493) --- .env.production.sample | 8 ++++++++ .eslintrc.yml | 3 +++ app/javascript/mastodon/service_worker/entry.js | 2 +- config/webpack/production.js | 20 ++++++++++++++++++-- 4 files changed, 30 insertions(+), 3 deletions(-) (limited to 'config') diff --git a/.env.production.sample b/.env.production.sample index 24b6b0143..3047f7595 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -88,6 +88,10 @@ SMTP_FROM_ADDRESS=notifications@example.com # CDN_HOST=https://assets.example.com # S3 (optional) +# The attachment host must allow cross origin request from WEB_DOMAIN or +# LOCAL_DOMAIN if WEB_DOMAIN is not set. For example, the server may have the +# following header field: +# Access-Control-Allow-Origin: https://192.168.1.123:9000/ # S3_ENABLED=true # S3_BUCKET= # AWS_ACCESS_KEY_ID= @@ -97,6 +101,8 @@ SMTP_FROM_ADDRESS=notifications@example.com # S3_HOSTNAME=192.168.1.123:9000 # S3 (Minio Config (optional) Please check Minio instance for details) +# The attachment host must allow cross origin request - see the description +# above. # S3_ENABLED=true # S3_BUCKET= # AWS_ACCESS_KEY_ID= @@ -108,6 +114,8 @@ SMTP_FROM_ADDRESS=notifications@example.com # S3_SIGNATURE_VERSION= # Swift (optional) +# The attachment host must allow cross origin request - see the description +# above. # SWIFT_ENABLED=true # SWIFT_USERNAME= # For Keystone V3, the value for SWIFT_TENANT should be the project name diff --git a/.eslintrc.yml b/.eslintrc.yml index 576e5b70a..205c9460a 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -7,6 +7,9 @@ env: es6: true jest: true +globals: + ATTACHMENT_HOST: false + parser: babel-eslint plugins: diff --git a/app/javascript/mastodon/service_worker/entry.js b/app/javascript/mastodon/service_worker/entry.js index ce42271a9..c1854c1cd 100644 --- a/app/javascript/mastodon/service_worker/entry.js +++ b/app/javascript/mastodon/service_worker/entry.js @@ -49,7 +49,7 @@ self.addEventListener('fetch', function(event) { return response; })); - } else if (storageFreeable && process.env.CDN_HOST ? url.host === process.env.CDN_HOST : url.pathname.startsWith('/system/')) { + } else if (storageFreeable && (ATTACHMENT_HOST ? url.host === ATTACHMENT_HOST : url.pathname.startsWith('/system/'))) { event.respondWith(openSystemCache().then(cache => { return cache.match(event.request.url).then(cached => { if (cached === undefined) { diff --git a/config/webpack/production.js b/config/webpack/production.js index a82330791..408c56930 100644 --- a/config/webpack/production.js +++ b/config/webpack/production.js @@ -6,8 +6,9 @@ const CompressionPlugin = require('compression-webpack-plugin'); const sharedConfig = require('./shared.js'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const OfflinePlugin = require('offline-plugin'); -const { env, publicPath } = require('./configuration.js'); +const { publicPath } = require('./configuration.js'); const path = require('path'); +const { URL } = require('url'); let compressionAlgorithm; try { @@ -19,6 +20,21 @@ try { compressionAlgorithm = 'gzip'; } +let attachmentHost; + +if (process.env.S3_ENABLED === 'true') { + if (process.env.S3_CLOUDFRONT_HOST) { + attachmentHost = process.env.S3_CLOUDFRONT_HOST; + } else { + attachmentHost = process.env.S3_HOSTNAME || `s3-${process.env.S3_REGION || 'us-east-1'}.amazonaws.com`; + } +} else if (process.env.SWIFT_ENABLED === 'true') { + const { host } = new URL(process.env.SWIFT_OBJECT_URL); + attachmentHost = host; +} else { + attachmentHost = null; +} + module.exports = merge(sharedConfig, { output: { filename: '[name]-[chunkhash].js', @@ -90,7 +106,7 @@ module.exports = merge(sharedConfig, { '**/*.woff', ], ServiceWorker: { - entry: `imports-loader?process.env=>${encodeURIComponent(JSON.stringify(env))}!${encodeURI(path.join(__dirname, '../../app/javascript/mastodon/service_worker/entry.js'))}`, + entry: `imports-loader?ATTACHMENT_HOST=>${encodeURIComponent(JSON.stringify(attachmentHost))}!${encodeURI(path.join(__dirname, '../../app/javascript/mastodon/service_worker/entry.js'))}`, cacheName: 'mastodon', output: '../assets/sw.js', publicPath: '/sw.js', -- cgit From 8bb74e50beb3e6602c183e59086ccebe87f96fb2 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 29 May 2018 02:01:24 +0200 Subject: Add GET /api/v2/search which returns rich tag objects, adjust web UI (#7661) --- app/controllers/api/v2/search_controller.rb | 8 ++++ app/javascript/mastodon/actions/search.js | 2 +- .../features/compose/components/search_results.js | 56 +++++++++++----------- app/javascript/mastodon/reducers/search.js | 4 +- app/javascript/styles/mastodon/components.scss | 44 ++++++++--------- app/serializers/rest/v2/search_serializer.rb | 7 +++ config/routes.rb | 4 ++ 7 files changed, 69 insertions(+), 56 deletions(-) create mode 100644 app/controllers/api/v2/search_controller.rb create mode 100644 app/serializers/rest/v2/search_serializer.rb (limited to 'config') diff --git a/app/controllers/api/v2/search_controller.rb b/app/controllers/api/v2/search_controller.rb new file mode 100644 index 000000000..2e91d68ee --- /dev/null +++ b/app/controllers/api/v2/search_controller.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class Api::V2::SearchController < Api::V1::SearchController + def index + @search = Search.new(search) + render json: @search, serializer: REST::V2::SearchSerializer + end +end diff --git a/app/javascript/mastodon/actions/search.js b/app/javascript/mastodon/actions/search.js index 882c1709e..b670d25c3 100644 --- a/app/javascript/mastodon/actions/search.js +++ b/app/javascript/mastodon/actions/search.js @@ -33,7 +33,7 @@ export function submitSearch() { dispatch(fetchSearchRequest()); - api(getState).get('/api/v1/search', { + api(getState).get('/api/v2/search', { params: { q: value, resolve: true, diff --git a/app/javascript/mastodon/features/compose/components/search_results.js b/app/javascript/mastodon/features/compose/components/search_results.js index f2655c14d..445bf27bb 100644 --- a/app/javascript/mastodon/features/compose/components/search_results.js +++ b/app/javascript/mastodon/features/compose/components/search_results.js @@ -16,6 +16,28 @@ const shortNumberFormat = number => { } }; +const renderHashtag = hashtag => ( +
+
+ + #{hashtag.get('name')} + + + {shortNumberFormat(hashtag.getIn(['history', 0, 'accounts']))} }} /> +
+ +
+ {shortNumberFormat(hashtag.getIn(['history', 0, 'uses']))} +
+ +
+ day.get('uses')).toArray()}> + + +
+
+); + export default class SearchResults extends ImmutablePureComponent { static propTypes = { @@ -44,27 +66,7 @@ export default class SearchResults extends ImmutablePureComponent { - {trends && trends.map(hashtag => ( -
-
- - #{hashtag.get('name')} - - - {shortNumberFormat(hashtag.getIn(['history', 0, 'accounts']))} }} /> -
- -
- {shortNumberFormat(hashtag.getIn(['history', 0, 'uses']))} -
- -
- day.get('uses')).toArray()}> - - -
-
- ))} + {trends && trends.map(hashtag => renderHashtag(hashtag))} ); @@ -74,7 +76,7 @@ export default class SearchResults extends ImmutablePureComponent { count += results.get('accounts').size; accounts = (
-
+
{results.get('accounts').map(accountId => )}
@@ -85,7 +87,7 @@ export default class SearchResults extends ImmutablePureComponent { count += results.get('statuses').size; statuses = (
-
+
{results.get('statuses').map(statusId => )}
@@ -96,13 +98,9 @@ export default class SearchResults extends ImmutablePureComponent { count += results.get('hashtags').size; hashtags = (
-
+
- {results.get('hashtags').map(hashtag => ( - - {hashtag} - - ))} + {results.get('hashtags').map(hashtag => renderHashtag(hashtag))}
); } diff --git a/app/javascript/mastodon/reducers/search.js b/app/javascript/mastodon/reducers/search.js index 56fd7226b..4758defb1 100644 --- a/app/javascript/mastodon/reducers/search.js +++ b/app/javascript/mastodon/reducers/search.js @@ -9,7 +9,7 @@ import { COMPOSE_REPLY, COMPOSE_DIRECT, } from '../actions/compose'; -import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; const initialState = ImmutableMap({ value: '', @@ -39,7 +39,7 @@ export default function search(state = initialState, action) { return state.set('results', ImmutableMap({ accounts: ImmutableList(action.results.accounts.map(item => item.id)), statuses: ImmutableList(action.results.statuses.map(item => item.id)), - hashtags: ImmutableList(action.results.hashtags), + hashtags: fromJS(action.results.hashtags), })).set('submitted', true); default: return state; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index a2a18b5a0..c93d8e86a 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -3284,6 +3284,15 @@ a.status-card { } .search__icon { + &::-moz-focus-inner { + border: 0; + } + + &::-moz-focus-inner, + &:focus { + outline: 0 !important; + } + .fa { position: absolute; top: 10px; @@ -3333,7 +3342,6 @@ a.status-card { .search-results__header { color: $dark-text-color; background: lighten($ui-base-color, 2%); - border-bottom: 1px solid darken($ui-base-color, 4%); padding: 15px; font-weight: 500; font-size: 16px; @@ -3346,33 +3354,21 @@ a.status-card { } .search-results__section { - margin-bottom: 20px; + margin-bottom: 5px; h5 { - position: relative; - - &::before { - content: ""; - display: block; - position: absolute; - left: 0; - right: 0; - top: 50%; - width: 100%; - height: 0; - border-top: 1px solid lighten($ui-base-color, 8%); - } + background: darken($ui-base-color, 4%); + border-bottom: 1px solid lighten($ui-base-color, 8%); + cursor: default; + display: flex; + padding: 15px; + font-weight: 500; + font-size: 16px; + color: $dark-text-color; - span { + .fa { display: inline-block; - background: $ui-base-color; - color: $darker-text-color; - font-size: 14px; - font-weight: 500; - padding: 10px; - position: relative; - z-index: 1; - cursor: default; + margin-right: 5px; } } diff --git a/app/serializers/rest/v2/search_serializer.rb b/app/serializers/rest/v2/search_serializer.rb new file mode 100644 index 000000000..cdb6b3a53 --- /dev/null +++ b/app/serializers/rest/v2/search_serializer.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class REST::V2::SearchSerializer < ActiveModel::Serializer + has_many :accounts, serializer: REST::AccountSerializer + has_many :statuses, serializer: REST::StatusSerializer + has_many :hashtags, serializer: REST::TagSerializer +end diff --git a/config/routes.rb b/config/routes.rb index 2fcb885ed..31e90e2ff 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -315,6 +315,10 @@ Rails.application.routes.draw do end end + namespace :v2 do + get '/search', to: 'search#index', as: :search + end + namespace :web do resource :settings, only: [:update] resource :embed, only: [:create] -- cgit From 13b60e6a146bb4717586573d63fa27f93d03a8fd Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Tue, 29 May 2018 20:33:20 +0900 Subject: Use URL polyfill (#7664) --- config/webpack/production.js | 2 +- package.json | 3 ++- yarn.lock | 20 +++++++++++++++++++- 3 files changed, 22 insertions(+), 3 deletions(-) (limited to 'config') diff --git a/config/webpack/production.js b/config/webpack/production.js index 408c56930..1469a948f 100644 --- a/config/webpack/production.js +++ b/config/webpack/production.js @@ -8,7 +8,7 @@ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPl const OfflinePlugin = require('offline-plugin'); const { publicPath } = require('./configuration.js'); const path = require('path'); -const { URL } = require('url'); +const { URL } = require('whatwg-url'); let compressionAlgorithm; try { diff --git a/package.json b/package.json index 6ee6f98d3..893be3017 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,8 @@ "webpack-bundle-analyzer": "^2.9.1", "webpack-manifest-plugin": "^1.2.1", "webpack-merge": "^4.1.1", - "websocket.js": "^0.1.12" + "websocket.js": "^0.1.12", + "whatwg-url": "^6.4.1" }, "devDependencies": { "babel-eslint": "^8.2.1", diff --git a/yarn.lock b/yarn.lock index de48c995a..e0f167490 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4373,6 +4373,10 @@ lodash.restparam@^3.0.0: version "3.6.1" resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + lodash.tail@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664" @@ -7257,6 +7261,12 @@ tough-cookie@^2.3.2, tough-cookie@~2.3.0, tough-cookie@~2.3.3: dependencies: punycode "^1.4.1" +tr46@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" + dependencies: + punycode "^2.1.0" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -7494,7 +7504,7 @@ webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" -webidl-conversions@^4.0.0: +webidl-conversions@^4.0.0, webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" @@ -7637,6 +7647,14 @@ whatwg-url@^4.3.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" +whatwg-url@^6.4.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.4.1.tgz#fdb94b440fd4ad836202c16e9737d511f012fd67" + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + whet.extend@~0.9.9: version "0.9.9" resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1" -- cgit