From a337c5dbe50ea9fad8f45ae93513c970c46ff2d0 Mon Sep 17 00:00:00 2001 From: Beatrix Bitrot <=> Date: Sat, 22 Apr 2017 00:55:14 +0000 Subject: CORS tweaks --- config/application.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'config') diff --git a/config/application.rb b/config/application.rb index 6b121009e..ac63e0e94 100644 --- a/config/application.rb +++ b/config/application.rb @@ -67,9 +67,11 @@ module Mastodon config.active_job.queue_adapter = :sidekiq + #config.middleware.insert_before 0, Rack::Cors, debug: true, logger: (-> { Rails.logger }) do config.middleware.insert_before 0, Rack::Cors do allow do origins '*' + resource '/assets/*', headers: :any, methods: [:get, :head, :options] resource '/@:username', headers: :any, methods: [:get], credentials: false resource '/api/*', headers: :any, methods: [:post, :put, :delete, :get, :patch, :options], credentials: false, expose: ['Link', 'X-RateLimit-Reset', 'X-RateLimit-Limit', 'X-RateLimit-Remaining', 'X-Request-Id'] resource '/oauth/token', headers: :any, methods: [:post], credentials: false -- cgit From 9bc593d6753922d995867b632477d4d3b9e86e20 Mon Sep 17 00:00:00 2001 From: beatrix-bitrot Date: Sun, 14 May 2017 20:56:30 +0000 Subject: update local modifications for cors and cp --- config/application.rb | 5 ++++- config/environments/production.rb | 11 +++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) (limited to 'config') diff --git a/config/application.rb b/config/application.rb index ac63e0e94..dd63017b3 100644 --- a/config/application.rb +++ b/config/application.rb @@ -71,10 +71,13 @@ module Mastodon config.middleware.insert_before 0, Rack::Cors do allow do origins '*' - resource '/assets/*', headers: :any, methods: [:get, :head, :options] resource '/@:username', headers: :any, methods: [:get], credentials: false resource '/api/*', headers: :any, methods: [:post, :put, :delete, :get, :patch, :options], credentials: false, expose: ['Link', 'X-RateLimit-Reset', 'X-RateLimit-Limit', 'X-RateLimit-Remaining', 'X-Request-Id'] resource '/oauth/token', headers: :any, methods: [:post], credentials: false + resource '/assets/*', headers: :any, methods: [:get, :head, :options] + resource '/stylesheets/*', headers: :any, methods: [:get, :head, :options] + resource '/javascripts/*', headers: :any, methods: [:get, :head, :options] + resource '/packs/*', headers: :any, methods: [:get, :head, :options] end end diff --git a/config/environments/production.rb b/config/environments/production.rb index bd87d79a7..68229531d 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -93,9 +93,12 @@ Rails.application.configure do end config.action_dispatch.default_headers = { - 'Server' => 'Mastodon', - 'X-Frame-Options' => 'DENY', - 'X-Content-Type-Options' => 'nosniff', - 'X-XSS-Protection' => '1; mode=block', + 'Server' => 'Mastodon', + 'X-Frame-Options' => 'DENY', + 'X-Content-Type-Options' => 'nosniff', + 'X-XSS-Protection' => '1; mode=block', + 'Content-Security-Policy' => "frame-ancestors 'none'; object-src 'none'; script-src 'self' https://dev-static.glitch.social 'unsafe-inline'; base-uri 'none';" , + 'Referrer-Policy' => 'no-referrer, strict-origin-when-cross-origin', + 'Strict-Transport-Security' => 'max-age=63072000; includeSubDomains; preload' } end -- cgit From 65528fc54e2943aa259ec9129781d3fb1161ec63 Mon Sep 17 00:00:00 2001 From: Chronister Date: Mon, 29 May 2017 00:09:12 +0000 Subject: All cybrespace changes through 5/28 --- app/javascript/images/background-cybre.png | Bin 0 -> 237414 bytes app/javascript/images/logo-cybre.png | Bin 0 -> 187946 bytes .../mastodon/components/status_action_bar.js | 2 +- .../mastodon/features/getting_started/index.js | 22 ++++---- .../notifications/components/notification.js | 2 +- .../features/status/components/action_bar.js | 2 +- .../features/status/components/detailed_status.js | 2 +- app/javascript/mastodon/locales/en.json | 58 ++++++++++----------- app/javascript/styles/custom.scss | 18 +++++-- app/validators/status_length_validator.rb | 2 +- app/views/about/_links.html.haml | 2 +- app/views/about/show.html.haml | 8 +-- app/views/layouts/admin.html.haml | 2 +- app/views/layouts/auth.html.haml | 2 +- config/locales/en.yml | 36 ++++++------- config/locales/simple_form.en.yml | 6 +-- config/settings.yml | 2 +- public/500.html | 6 +-- public/android-chrome-192x192.png | Bin 14344 -> 41911 bytes public/apple-touch-icon.png | Bin 4217 -> 37614 bytes public/background-cybre.png | Bin 0 -> 237414 bytes public/browserconfig.xml | 2 +- public/clock.js | 22 ++++++++ public/emoji/1f418.png | Bin 1293 -> 7535 bytes public/emoji/1f418.svg | 18 ++++++- public/logo-cybre-glitch.gif | Bin 0 -> 837759 bytes public/mstile-150x150.png | Bin 6916 -> 27611 bytes 27 files changed, 132 insertions(+), 82 deletions(-) create mode 100644 app/javascript/images/background-cybre.png create mode 100644 app/javascript/images/logo-cybre.png create mode 100644 public/background-cybre.png create mode 100644 public/clock.js create mode 100644 public/logo-cybre-glitch.gif (limited to 'config') diff --git a/app/javascript/images/background-cybre.png b/app/javascript/images/background-cybre.png new file mode 100644 index 000000000..151fd5584 Binary files /dev/null and b/app/javascript/images/background-cybre.png differ diff --git a/app/javascript/images/logo-cybre.png b/app/javascript/images/logo-cybre.png new file mode 100644 index 000000000..41dd8fd4c Binary files /dev/null and b/app/javascript/images/logo-cybre.png differ diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index edb2d6eb0..a8ac7a70a 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -140,7 +140,7 @@ export default class StatusActionBar extends ImmutablePureComponent {
- +
diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index f8ea01024..c8490abe9 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -10,19 +10,19 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; const messages = defineMessages({ heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, - home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' }, - notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' }, - public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' }, + home_timeline: { id: 'tabs_bar.home', defaultMessage: '/timelines/home' }, + notifications: { id: 'tabs_bar.notifications', defaultMessage: '~/.notifications' }, + public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: '/timelines/federated' }, navigation_subheading: { id: 'column_subheading.navigation', defaultMessage: 'Navigation' }, settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' }, - community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, + community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: '/timelines/local' }, preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, - follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, - sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, - favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, - blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, - mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, - info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' }, + follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: '~/.follow-requests' }, + sign_out: { id: 'navigation_bar.logout', defaultMessage: 'exit' }, + favourites: { id: 'navigation_bar.favourites', defaultMessage: '~/.florps' }, + blocks: { id: 'navigation_bar.blocks', defaultMessage: '~/.blocked' }, + mutes: { id: 'navigation_bar.mutes', defaultMessage: '~/.muted' }, + info: { id: 'navigation_bar.info', defaultMessage: '/about/more' }, }); const mapStateToProps = state => ({ @@ -65,7 +65,7 @@ export default class GettingStarted extends ImmutablePureComponent { } navItems = navItems.concat([ - , + , ]); if (me.get('locked')) { diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js index 9d631644a..0a4ba0214 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.js +++ b/app/javascript/mastodon/features/notifications/components/notification.js @@ -39,7 +39,7 @@ export default class Notification extends ImmutablePureComponent {
- +
diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index 29080529d..03779c2c1 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -82,7 +82,7 @@ export default class ActionBar extends React.PureComponent {
-
+
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index 619957dbe..6f2415965 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -76,7 +76,7 @@ export default class DetailedStatus extends ImmutablePureComponent { · - + diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 608d911e9..84579f188 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -2,7 +2,7 @@ "account.block": "Block @{name}", "account.block_domain": "Hide everything from {domain}", "account.disclaimer": "This user is from another instance. This number may be larger.", - "account.edit_profile": "Edit profile", + "account.edit_profile": "edit ~/.profile", "account.follow": "Follow", "account.followers": "Followers", "account.follows": "Follows", @@ -10,7 +10,7 @@ "account.media": "Media", "account.mention": "Mention @{name}", "account.mute": "Mute @{name}", - "account.posts": "Posts", + "account.posts": "Pings", "account.report": "Report @{name}", "account.requested": "Awaiting approval", "account.unblock": "Unblock @{name}", @@ -18,14 +18,14 @@ "account.unfollow": "Unfollow", "account.unmute": "Unmute @{name}", "boost_modal.combo": "You can press {combo} to skip this next time", - "column.blocks": "Blocked users", - "column.community": "Local timeline", - "column.favourites": "Favourites", - "column.follow_requests": "Follow requests", - "column.home": "Home", - "column.mutes": "Muted users", - "column.notifications": "Notifications", - "column.public": "Federated timeline", + "column.blocks": "~/.blocked", + "column.community": "/timelines/local", + "column.favourites": "~/.florps", + "column.follow_requests": "~/.follow-requests", + "column.home": "/timelines/home", + "column.mutes": "~/.muted", + "column.notifications": "~/.notifications", + "column.public": "/timelines/federated", "column_back_button.label": "Back", "column_header.pin": "Pin", "column_header.unpin": "Unpin", @@ -33,9 +33,9 @@ "column_subheading.settings": "Settings", "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.", "compose_form.lock_disclaimer.lock": "locked", - "compose_form.placeholder": "What is on your mind?", + "compose_form.placeholder": "What is in your databanks?", "compose_form.privacy_disclaimer": "Your post will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is not a public post, and it may be boosted or otherwise made visible to unintended recipients.", - "compose_form.publish": "Toot", + "compose_form.publish": "Ping", "compose_form.publish_loud": "{publish}!", "compose_form.sensitive": "Mark media as sensitive", "compose_form.spoiler": "Hide text behind warning", @@ -61,7 +61,7 @@ "emoji_button.travel": "Travel & Places", "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", "empty_column.hashtag": "There is nothing in this hashtag yet.", - "empty_column.home": "You aren't following anyone yet. Visit {public} or use search to get started and meet other users.", + "empty_column.home": "You aren't following anyone yet. Visit {public} or use query to get started and meet other users.", "empty_column.home.inactivity": "Your home feed is empty. If you have been inactive for a while, it will be regenerated for you soon.", "empty_column.home.public_timeline": "the public timeline", "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", @@ -76,24 +76,24 @@ "home.column_settings.advanced": "Advanced", "home.column_settings.basic": "Basic", "home.column_settings.filter_regex": "Filter out by regular expressions", - "home.column_settings.show_reblogs": "Show boosts", + "home.column_settings.show_reblogs": "Show relays", "home.column_settings.show_replies": "Show replies", "home.settings": "Column settings", "lightbox.close": "Close", "loading_indicator.label": "Loading...", "media_gallery.toggle_visible": "Toggle visibility", "missing_indicator.label": "Not found", - "navigation_bar.blocks": "Blocked users", - "navigation_bar.community_timeline": "Local timeline", - "navigation_bar.edit_profile": "Edit profile", - "navigation_bar.favourites": "Favourites", - "navigation_bar.follow_requests": "Follow requests", - "navigation_bar.info": "About this instance", - "navigation_bar.logout": "Logout", - "navigation_bar.mutes": "Muted users", + "navigation_bar.blocks": "~/.blocks", + "navigation_bar.community_timeline": "/timelines/local", + "navigation_bar.edit_profile": "edit ~/.profile", + "navigation_bar.favourites": "~/.florps", + "navigation_bar.follow_requests": "~/.follow-requests", + "navigation_bar.info": "/about/more", + "navigation_bar.logout": "Jack out", + "navigation_bar.mutes": "~/.muted", "navigation_bar.preferences": "Preferences", - "navigation_bar.public_timeline": "Federated timeline", - "notification.favourite": "{name} favourited your status", + "navigation_bar.public_timeline": "/timelines/federated", + "notification.favourite": "{name} florped your ping", "notification.follow": "{name} followed you", "notification.mention": "{name} mentioned you", "notification.reblog": "{name} boosted your status", @@ -140,18 +140,18 @@ "report.placeholder": "Additional comments", "report.submit": "Submit", "report.target": "Reporting", - "search.placeholder": "Search", + "search.placeholder": "Query...", "search_results.total": "{count, number} {count, plural, one {result} other {results}}", - "status.cannot_reblog": "This post cannot be boosted", + "status.cannot_reblog": "This ping cannot be relayed", "status.delete": "Delete", - "status.favourite": "Favourite", + "status.favourite": "Florp", "status.load_more": "Load more", "status.media_hidden": "Media hidden", "status.mention": "Mention @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Expand this status", - "status.reblog": "Boost", - "status.reblogged_by": "{name} boosted", + "status.reblog": "Relay", + "status.reblogged_by": "{name} relayed", "status.reply": "Reply", "status.replyAll": "Reply to thread", "status.report": "Report @{name}", diff --git a/app/javascript/styles/custom.scss b/app/javascript/styles/custom.scss index 8c6c12316..aa3fb9f0c 100644 --- a/app/javascript/styles/custom.scss +++ b/app/javascript/styles/custom.scss @@ -12,13 +12,25 @@ body { @media screen and (min-width: 1300px) { .column { flex-grow: 1 !important; - max-width: 400px; + max-width: 500px; } .drawer { - width: 17%; - max-width: 400px; + width: 20%; } + + .columns-area { + justify-content: center; + } +} + +@media screen and (min-width: 1900px) { + .column, .drawer { + width: 400px; + border-radius: 4px; + height: 96vh; + margin-top: 2vh; + } } .muted { diff --git a/app/validators/status_length_validator.rb b/app/validators/status_length_validator.rb index 3f3e422d9..cd791e2f3 100644 --- a/app/validators/status_length_validator.rb +++ b/app/validators/status_length_validator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class StatusLengthValidator < ActiveModel::Validator - MAX_CHARS = 500 + MAX_CHARS = 512 def validate(status) return unless status.local? && !status.reblog? diff --git a/app/views/about/_links.html.haml b/app/views/about/_links.html.haml index fb3350539..d7fe317e6 100644 --- a/app/views/about/_links.html.haml +++ b/app/views/about/_links.html.haml @@ -9,4 +9,4 @@ %li= link_to t('about.get_started'), new_user_registration_path %li= link_to t('auth.login'), new_user_session_path %li= link_to t('about.terms'), terms_path - %li= link_to t('about.source_code'), 'https://github.com/tootsuite/mastodon' + %li= link_to t('about.source_code'), 'https://github.com/chronister/mastodon' diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml index d15b04163..87a729055 100644 --- a/app/views/about/show.html.haml +++ b/app/views/about/show.html.haml @@ -17,7 +17,7 @@ .wrapper %h1 - = image_tag asset_pack_path('logo.png') + = image_tag asset_pack_path('logo-cybre.png') = Setting.site_title %p!= t('about.about_mastodon') @@ -36,7 +36,7 @@ .info = link_to t('auth.login'), new_user_session_path, class: 'webapp-btn' · - = link_to t('about.other_instances'), 'https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/List-of-Mastodon-instances.md' + = link_to t('about.other_instances'), 'https://instances.mastodon.xyz/' · = link_to t('about.about_this'), about_more_path @@ -82,6 +82,6 @@ · = link_to t('about.apps'), 'https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' · - = link_to t('about.source_code'), 'https://github.com/tootsuite/mastodon' + = link_to t('about.source_code'), 'https://github.com/chronister/mastodon' · - = link_to t('about.other_instances'), 'https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/List-of-Mastodon-instances.md' + = link_to t('about.other_instances'), 'https://instances.mastodon.xyz/' diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index 59d95a0c6..b49aa83f5 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -6,7 +6,7 @@ .sidebar-wrapper .sidebar = link_to root_path do - = image_tag asset_pack_path('logo.png'), class: 'logo' + = image_tag asset_pack_path('logo-cybre.png'), class: 'logo' = render_navigation .content-wrapper diff --git a/app/views/layouts/auth.html.haml b/app/views/layouts/auth.html.haml index e5429a8ed..097fc6d08 100644 --- a/app/views/layouts/auth.html.haml +++ b/app/views/layouts/auth.html.haml @@ -6,7 +6,7 @@ .logo-container %h1 = link_to root_path do - = image_tag asset_pack_path('logo.png') + = image_tag asset_pack_path('logo-cybre.png') .form-container = render 'flashes' diff --git a/config/locales/en.yml b/config/locales/en.yml index 0d33aae3f..201651d17 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,30 +1,30 @@ --- en: about: - about_mastodon: Mastodon is a free, open-source social network. A decentralized alternative to commercial platforms, it avoids the risks of a single company monopolizing your communication. Pick a server that you trust — whichever you choose, you can interact with everyone else. Anyone can run their own Mastodon instance and participate in the social network seamlessly. + about_mastodon: Cybrespace is an instance of Mastodon, a free, open-source social network. A decentralized alternative to commercial platforms, it avoids the risks of a single company monopolizing your communication. Pick a server that you trust — whichever you choose, you can interact with everyone else. Anyone can run their own Mastodon instance and participate in the social network seamlessly. about_this: About this instance apps: Apps business_email: 'Business e-mail:' closed_registrations: Registrations are currently closed on this instance. contact: Contact - description_headline: What is %{domain}? + description_headline: What's special about %{domain}? domain_count_after: other instances domain_count_before: Connected to features: api: Open API for apps and services blocks: Rich block and muting tools - characters: 500 characters per post + characters: 512 characters per post chronology: Timelines are chronological ethics: 'Ethical design: no ads, no tracking' gifv: GIFV sets and short videos - privacy: Granular, per-post privacy settings + privacy: Granular, per-ping privacy settings public: Public timelines features_headline: What sets Mastodon apart get_started: Get started links: Links other_instances: Other instances source_code: Source code - status_count_after: statuses + status_count_after: pings status_count_before: Who authored terms: Terms user_count_after: users @@ -37,7 +37,7 @@ en: nothing_here: There is nothing here! people_followed_by: People whom %{name} follows people_who_follow: People who follow %{name} - posts: Posts + posts: Pings remote_follow: Remote follow reserved_username: The username is reserved unfollow: Unfollow @@ -205,9 +205,9 @@ en: delete_account_html: If you wish to delete your account, you can proceed here. You will be asked for confirmation. didnt_get_confirmation: Didn't receive confirmation instructions? forgot_password: Forgot your password? - login: Log in - logout: Logout - register: Sign up + login: Jack in + logout: Jack out + register: Apply for upload resend_confirmation: Resend confirmation instructions reset_password: Reset password set_new_password: Set new password @@ -255,7 +255,7 @@ en: storage: Media storage followers: domain: Domain - explanation_html: If you want to ensure the privacy of your statuses, you must be aware of who is following you. Your private statuses are delivered to all instances where you have followers. You may wish to review them, and remove followers if you do not trust your privacy to be respected by the staff or software of those instances. + explanation_html: If you want to ensure the privacy of your pings , you must be aware of who is following you. Your private pings are delivered to all instances where you have followers. You may wish to review them, and remove followers if you do not trust your privacy to be respected by the staff or software of those instances. followers_count: Number of followers lock_link: Lock your account purge: Remove from followers @@ -263,7 +263,7 @@ en: one: In the process of soft-blocking followers from one domain... other: In the process of soft-blocking followers from %{count} domains... true_privacy_html: Please mind that true privacy can only be achieved with end-to-end encryption. - unlocked_warning_html: Anyone can follow you to immediately view your private statuses. %{lock_link} to be able to review and reject followers. + unlocked_warning_html: Anyone can follow you to immediately view your private pings. %{lock_link} to be able to review and reject followers. unlocked_warning_title: Your account is not locked generic: changes_saved_msg: Changes successfully saved! @@ -284,7 +284,7 @@ en: landing_strip_signup_html: If you don't, you can sign up here. media_attachments: validations: - images_and_video: Cannot attach a video to a status that already contains images + images_and_video: Cannot attach a video to a ping that already contains images too_many: Cannot attach more than 4 files notification_mailer: digest: @@ -297,8 +297,8 @@ en: one: "1 new notification since your last visit \U0001F418" other: "%{count} new notifications since your last visit \U0001F418" favourite: - body: 'Your status was favourited by %{name}:' - subject: "%{name} favourited your status" + body: 'Your ping was florped by %{name}:' + subject: "%{name} florped your ping" follow: body: "%{name} is now following you!" subject: "%{name} is now following you" @@ -309,8 +309,8 @@ en: body: 'You were mentioned by %{name} in:' subject: You were mentioned by %{name} reblog: - body: 'Your status was boosted by %{name}:' - subject: "%{name} boosted your status" + body: 'Your ping was relayed by %{name}:' + subject: "%{name} relayed your ping" pagination: next: Next prev: Prev @@ -324,7 +324,7 @@ en: authorized_apps: Authorized apps back: Back to Mastodon delete: Account deletion - edit_profile: Edit profile + edit_profile: edit ~/.profile export: Data export followers: Authorized followers import: Import @@ -344,7 +344,7 @@ en: unlisted_long: Everyone can see, but not listed on public timelines stream_entries: click_to_show: Click to show - reblogged: boosted + reblogged: relayed sensitive_content: Sensitive content time: formats: diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 3e769fb96..99173e948 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -8,7 +8,7 @@ en: one: 1 character left other: %{count} characters left header: PNG, GIF or JPG. At most 2MB. Will be downscaled to 700x335px - locked: Requires you to manually approve followers and defaults post privacy to followers-only + locked: Requires you to manually approve followers and defaults ping privacy to followers-only note: one: 1 character left other: %{count} characters left @@ -46,11 +46,11 @@ en: must_be_following: Block notifications from people you don't follow notification_emails: digest: Send digest e-mails - favourite: Send e-mail when someone favourites your status + favourite: Send e-mail when someone florps your ping follow: Send e-mail when someone follows you follow_request: Send e-mail when someone requests to follow you mention: Send e-mail when someone mentions you - reblog: Send e-mail when someone boosts your status + reblog: Send e-mail when someone boosts your ping 'no': 'No' required: mark: "*" diff --git a/config/settings.yml b/config/settings.yml index 7b78b6cdb..13ac097c6 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -7,7 +7,7 @@ # For more information, see docs/Running-Mastodon/Administration-guide.md # defaults: &defaults - site_title: Mastodon + site_title: 'Cybrespace' site_description: '' site_extended_description: '' site_contact_username: '' diff --git a/public/500.html b/public/500.html index d085d490b..4197f5cdd 100644 --- a/public/500.html +++ b/public/500.html @@ -8,8 +8,8 @@ + + 1f418 + + + + diff --git a/public/logo-cybre-glitch.gif b/public/logo-cybre-glitch.gif new file mode 100644 index 000000000..abe9b2a9a Binary files /dev/null and b/public/logo-cybre-glitch.gif differ diff --git a/public/mstile-150x150.png b/public/mstile-150x150.png index 57eae8355..a79f11992 100644 Binary files a/public/mstile-150x150.png and b/public/mstile-150x150.png differ -- cgit From b27842dc70847cb936ae2b49777095ba12c5917b Mon Sep 17 00:00:00 2001 From: Charlotte Fields Date: Mon, 19 Jun 2017 11:23:25 +1000 Subject: cybre cleanup --- app/javascript/images/background-cybre.png | Bin 237414 -> 0 bytes app/javascript/images/logo-cybre.png | Bin 187946 -> 0 bytes .../mastodon/components/status_action_bar.js | 2 +- .../mastodon/features/getting_started/index.js | 22 ++++---- .../notifications/components/notification.js | 2 +- .../features/status/components/action_bar.js | 2 +- .../features/status/components/detailed_status.js | 2 +- app/javascript/mastodon/locales/en.json | 58 ++++++++++----------- app/javascript/styles/custom.scss | 19 ------- app/views/about/show.html.haml | 2 +- app/views/layouts/admin.html.haml | 2 +- app/views/layouts/auth.html.haml | 2 +- config/locales/en.yml | 36 ++++++------- config/locales/simple_form.en.yml | 6 +-- config/settings.yml | 2 +- public/500.html | 4 +- public/android-chrome-192x192.png | Bin 41911 -> 14344 bytes public/apple-touch-icon.png | Bin 37614 -> 4217 bytes public/emoji/1f418.png | Bin 7535 -> 1293 bytes public/emoji/1f418.svg | 18 +------ public/mstile-150x150.png | Bin 27611 -> 6916 bytes 21 files changed, 72 insertions(+), 107 deletions(-) delete mode 100644 app/javascript/images/background-cybre.png delete mode 100644 app/javascript/images/logo-cybre.png (limited to 'config') diff --git a/app/javascript/images/background-cybre.png b/app/javascript/images/background-cybre.png deleted file mode 100644 index 151fd5584..000000000 Binary files a/app/javascript/images/background-cybre.png and /dev/null differ diff --git a/app/javascript/images/logo-cybre.png b/app/javascript/images/logo-cybre.png deleted file mode 100644 index 41dd8fd4c..000000000 Binary files a/app/javascript/images/logo-cybre.png and /dev/null differ diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index a8ac7a70a..edb2d6eb0 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -140,7 +140,7 @@ export default class StatusActionBar extends ImmutablePureComponent {
- +
diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index c8490abe9..f8ea01024 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -10,19 +10,19 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; const messages = defineMessages({ heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, - home_timeline: { id: 'tabs_bar.home', defaultMessage: '/timelines/home' }, - notifications: { id: 'tabs_bar.notifications', defaultMessage: '~/.notifications' }, - public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: '/timelines/federated' }, + home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' }, + notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' }, + public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' }, navigation_subheading: { id: 'column_subheading.navigation', defaultMessage: 'Navigation' }, settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' }, - community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: '/timelines/local' }, + community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, - follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: '~/.follow-requests' }, - sign_out: { id: 'navigation_bar.logout', defaultMessage: 'exit' }, - favourites: { id: 'navigation_bar.favourites', defaultMessage: '~/.florps' }, - blocks: { id: 'navigation_bar.blocks', defaultMessage: '~/.blocked' }, - mutes: { id: 'navigation_bar.mutes', defaultMessage: '~/.muted' }, - info: { id: 'navigation_bar.info', defaultMessage: '/about/more' }, + follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, + sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, + favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, + blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, + mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, + info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' }, }); const mapStateToProps = state => ({ @@ -65,7 +65,7 @@ export default class GettingStarted extends ImmutablePureComponent { } navItems = navItems.concat([ - , + , ]); if (me.get('locked')) { diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js index 0a4ba0214..9d631644a 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.js +++ b/app/javascript/mastodon/features/notifications/components/notification.js @@ -39,7 +39,7 @@ export default class Notification extends ImmutablePureComponent {
- +
diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index 03779c2c1..29080529d 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -82,7 +82,7 @@ export default class ActionBar extends React.PureComponent {
-
+
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index 6f2415965..619957dbe 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -76,7 +76,7 @@ export default class DetailedStatus extends ImmutablePureComponent { · - + diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 84579f188..608d911e9 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -2,7 +2,7 @@ "account.block": "Block @{name}", "account.block_domain": "Hide everything from {domain}", "account.disclaimer": "This user is from another instance. This number may be larger.", - "account.edit_profile": "edit ~/.profile", + "account.edit_profile": "Edit profile", "account.follow": "Follow", "account.followers": "Followers", "account.follows": "Follows", @@ -10,7 +10,7 @@ "account.media": "Media", "account.mention": "Mention @{name}", "account.mute": "Mute @{name}", - "account.posts": "Pings", + "account.posts": "Posts", "account.report": "Report @{name}", "account.requested": "Awaiting approval", "account.unblock": "Unblock @{name}", @@ -18,14 +18,14 @@ "account.unfollow": "Unfollow", "account.unmute": "Unmute @{name}", "boost_modal.combo": "You can press {combo} to skip this next time", - "column.blocks": "~/.blocked", - "column.community": "/timelines/local", - "column.favourites": "~/.florps", - "column.follow_requests": "~/.follow-requests", - "column.home": "/timelines/home", - "column.mutes": "~/.muted", - "column.notifications": "~/.notifications", - "column.public": "/timelines/federated", + "column.blocks": "Blocked users", + "column.community": "Local timeline", + "column.favourites": "Favourites", + "column.follow_requests": "Follow requests", + "column.home": "Home", + "column.mutes": "Muted users", + "column.notifications": "Notifications", + "column.public": "Federated timeline", "column_back_button.label": "Back", "column_header.pin": "Pin", "column_header.unpin": "Unpin", @@ -33,9 +33,9 @@ "column_subheading.settings": "Settings", "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.", "compose_form.lock_disclaimer.lock": "locked", - "compose_form.placeholder": "What is in your databanks?", + "compose_form.placeholder": "What is on your mind?", "compose_form.privacy_disclaimer": "Your post will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is not a public post, and it may be boosted or otherwise made visible to unintended recipients.", - "compose_form.publish": "Ping", + "compose_form.publish": "Toot", "compose_form.publish_loud": "{publish}!", "compose_form.sensitive": "Mark media as sensitive", "compose_form.spoiler": "Hide text behind warning", @@ -61,7 +61,7 @@ "emoji_button.travel": "Travel & Places", "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", "empty_column.hashtag": "There is nothing in this hashtag yet.", - "empty_column.home": "You aren't following anyone yet. Visit {public} or use query to get started and meet other users.", + "empty_column.home": "You aren't following anyone yet. Visit {public} or use search to get started and meet other users.", "empty_column.home.inactivity": "Your home feed is empty. If you have been inactive for a while, it will be regenerated for you soon.", "empty_column.home.public_timeline": "the public timeline", "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", @@ -76,24 +76,24 @@ "home.column_settings.advanced": "Advanced", "home.column_settings.basic": "Basic", "home.column_settings.filter_regex": "Filter out by regular expressions", - "home.column_settings.show_reblogs": "Show relays", + "home.column_settings.show_reblogs": "Show boosts", "home.column_settings.show_replies": "Show replies", "home.settings": "Column settings", "lightbox.close": "Close", "loading_indicator.label": "Loading...", "media_gallery.toggle_visible": "Toggle visibility", "missing_indicator.label": "Not found", - "navigation_bar.blocks": "~/.blocks", - "navigation_bar.community_timeline": "/timelines/local", - "navigation_bar.edit_profile": "edit ~/.profile", - "navigation_bar.favourites": "~/.florps", - "navigation_bar.follow_requests": "~/.follow-requests", - "navigation_bar.info": "/about/more", - "navigation_bar.logout": "Jack out", - "navigation_bar.mutes": "~/.muted", + "navigation_bar.blocks": "Blocked users", + "navigation_bar.community_timeline": "Local timeline", + "navigation_bar.edit_profile": "Edit profile", + "navigation_bar.favourites": "Favourites", + "navigation_bar.follow_requests": "Follow requests", + "navigation_bar.info": "About this instance", + "navigation_bar.logout": "Logout", + "navigation_bar.mutes": "Muted users", "navigation_bar.preferences": "Preferences", - "navigation_bar.public_timeline": "/timelines/federated", - "notification.favourite": "{name} florped your ping", + "navigation_bar.public_timeline": "Federated timeline", + "notification.favourite": "{name} favourited your status", "notification.follow": "{name} followed you", "notification.mention": "{name} mentioned you", "notification.reblog": "{name} boosted your status", @@ -140,18 +140,18 @@ "report.placeholder": "Additional comments", "report.submit": "Submit", "report.target": "Reporting", - "search.placeholder": "Query...", + "search.placeholder": "Search", "search_results.total": "{count, number} {count, plural, one {result} other {results}}", - "status.cannot_reblog": "This ping cannot be relayed", + "status.cannot_reblog": "This post cannot be boosted", "status.delete": "Delete", - "status.favourite": "Florp", + "status.favourite": "Favourite", "status.load_more": "Load more", "status.media_hidden": "Media hidden", "status.mention": "Mention @{name}", "status.mute_conversation": "Mute conversation", "status.open": "Expand this status", - "status.reblog": "Relay", - "status.reblogged_by": "{name} relayed", + "status.reblog": "Boost", + "status.reblogged_by": "{name} boosted", "status.reply": "Reply", "status.replyAll": "Reply to thread", "status.report": "Report @{name}", diff --git a/app/javascript/styles/custom.scss b/app/javascript/styles/custom.scss index 8c6c12316..5bfe2a412 100644 --- a/app/javascript/styles/custom.scss +++ b/app/javascript/styles/custom.scss @@ -1,14 +1,5 @@ -$ui-base-color: #181818; // darkest -$ui-secondary-color: #dae8da; // lightest -$ui-primary-color: #dae8da; // lighter -$ui-highlight-color: #1ea21e; // vibrant - @import 'application'; -body { - background: $ui-base-color url('../images/background-cybre.png'); -} - @media screen and (min-width: 1300px) { .column { flex-grow: 1 !important; @@ -59,16 +50,6 @@ body { .screenshot-with-signup .closed-registrations-message .clock { font-size:150%; -} - -.column .static-content.getting-started { - background-image: url('../images/logo-cybre.png'), url('../images/background-cybre.png'); - background-position: 50% 50%; - background-size:cover; -} - -.columns-area { - background: $ui-base-color url('../images/background-cybre.png'); } .drawer .drawer__inner { diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml index 87a729055..2a7f8c752 100644 --- a/app/views/about/show.html.haml +++ b/app/views/about/show.html.haml @@ -17,7 +17,7 @@ .wrapper %h1 - = image_tag asset_pack_path('logo-cybre.png') + = image_tag asset_pack_path('logo.png') = Setting.site_title %p!= t('about.about_mastodon') diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index b49aa83f5..59d95a0c6 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -6,7 +6,7 @@ .sidebar-wrapper .sidebar = link_to root_path do - = image_tag asset_pack_path('logo-cybre.png'), class: 'logo' + = image_tag asset_pack_path('logo.png'), class: 'logo' = render_navigation .content-wrapper diff --git a/app/views/layouts/auth.html.haml b/app/views/layouts/auth.html.haml index 097fc6d08..e5429a8ed 100644 --- a/app/views/layouts/auth.html.haml +++ b/app/views/layouts/auth.html.haml @@ -6,7 +6,7 @@ .logo-container %h1 = link_to root_path do - = image_tag asset_pack_path('logo-cybre.png') + = image_tag asset_pack_path('logo.png') .form-container = render 'flashes' diff --git a/config/locales/en.yml b/config/locales/en.yml index 201651d17..0d33aae3f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,30 +1,30 @@ --- en: about: - about_mastodon: Cybrespace is an instance of Mastodon, a free, open-source social network. A decentralized alternative to commercial platforms, it avoids the risks of a single company monopolizing your communication. Pick a server that you trust — whichever you choose, you can interact with everyone else. Anyone can run their own Mastodon instance and participate in the social network seamlessly. + about_mastodon: Mastodon is a free, open-source social network. A decentralized alternative to commercial platforms, it avoids the risks of a single company monopolizing your communication. Pick a server that you trust — whichever you choose, you can interact with everyone else. Anyone can run their own Mastodon instance and participate in the social network seamlessly. about_this: About this instance apps: Apps business_email: 'Business e-mail:' closed_registrations: Registrations are currently closed on this instance. contact: Contact - description_headline: What's special about %{domain}? + description_headline: What is %{domain}? domain_count_after: other instances domain_count_before: Connected to features: api: Open API for apps and services blocks: Rich block and muting tools - characters: 512 characters per post + characters: 500 characters per post chronology: Timelines are chronological ethics: 'Ethical design: no ads, no tracking' gifv: GIFV sets and short videos - privacy: Granular, per-ping privacy settings + privacy: Granular, per-post privacy settings public: Public timelines features_headline: What sets Mastodon apart get_started: Get started links: Links other_instances: Other instances source_code: Source code - status_count_after: pings + status_count_after: statuses status_count_before: Who authored terms: Terms user_count_after: users @@ -37,7 +37,7 @@ en: nothing_here: There is nothing here! people_followed_by: People whom %{name} follows people_who_follow: People who follow %{name} - posts: Pings + posts: Posts remote_follow: Remote follow reserved_username: The username is reserved unfollow: Unfollow @@ -205,9 +205,9 @@ en: delete_account_html: If you wish to delete your account, you can proceed here. You will be asked for confirmation. didnt_get_confirmation: Didn't receive confirmation instructions? forgot_password: Forgot your password? - login: Jack in - logout: Jack out - register: Apply for upload + login: Log in + logout: Logout + register: Sign up resend_confirmation: Resend confirmation instructions reset_password: Reset password set_new_password: Set new password @@ -255,7 +255,7 @@ en: storage: Media storage followers: domain: Domain - explanation_html: If you want to ensure the privacy of your pings , you must be aware of who is following you. Your private pings are delivered to all instances where you have followers. You may wish to review them, and remove followers if you do not trust your privacy to be respected by the staff or software of those instances. + explanation_html: If you want to ensure the privacy of your statuses, you must be aware of who is following you. Your private statuses are delivered to all instances where you have followers. You may wish to review them, and remove followers if you do not trust your privacy to be respected by the staff or software of those instances. followers_count: Number of followers lock_link: Lock your account purge: Remove from followers @@ -263,7 +263,7 @@ en: one: In the process of soft-blocking followers from one domain... other: In the process of soft-blocking followers from %{count} domains... true_privacy_html: Please mind that true privacy can only be achieved with end-to-end encryption. - unlocked_warning_html: Anyone can follow you to immediately view your private pings. %{lock_link} to be able to review and reject followers. + unlocked_warning_html: Anyone can follow you to immediately view your private statuses. %{lock_link} to be able to review and reject followers. unlocked_warning_title: Your account is not locked generic: changes_saved_msg: Changes successfully saved! @@ -284,7 +284,7 @@ en: landing_strip_signup_html: If you don't, you can sign up here. media_attachments: validations: - images_and_video: Cannot attach a video to a ping that already contains images + images_and_video: Cannot attach a video to a status that already contains images too_many: Cannot attach more than 4 files notification_mailer: digest: @@ -297,8 +297,8 @@ en: one: "1 new notification since your last visit \U0001F418" other: "%{count} new notifications since your last visit \U0001F418" favourite: - body: 'Your ping was florped by %{name}:' - subject: "%{name} florped your ping" + body: 'Your status was favourited by %{name}:' + subject: "%{name} favourited your status" follow: body: "%{name} is now following you!" subject: "%{name} is now following you" @@ -309,8 +309,8 @@ en: body: 'You were mentioned by %{name} in:' subject: You were mentioned by %{name} reblog: - body: 'Your ping was relayed by %{name}:' - subject: "%{name} relayed your ping" + body: 'Your status was boosted by %{name}:' + subject: "%{name} boosted your status" pagination: next: Next prev: Prev @@ -324,7 +324,7 @@ en: authorized_apps: Authorized apps back: Back to Mastodon delete: Account deletion - edit_profile: edit ~/.profile + edit_profile: Edit profile export: Data export followers: Authorized followers import: Import @@ -344,7 +344,7 @@ en: unlisted_long: Everyone can see, but not listed on public timelines stream_entries: click_to_show: Click to show - reblogged: relayed + reblogged: boosted sensitive_content: Sensitive content time: formats: diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 99173e948..3e769fb96 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -8,7 +8,7 @@ en: one: 1 character left other: %{count} characters left header: PNG, GIF or JPG. At most 2MB. Will be downscaled to 700x335px - locked: Requires you to manually approve followers and defaults ping privacy to followers-only + locked: Requires you to manually approve followers and defaults post privacy to followers-only note: one: 1 character left other: %{count} characters left @@ -46,11 +46,11 @@ en: must_be_following: Block notifications from people you don't follow notification_emails: digest: Send digest e-mails - favourite: Send e-mail when someone florps your ping + favourite: Send e-mail when someone favourites your status follow: Send e-mail when someone follows you follow_request: Send e-mail when someone requests to follow you mention: Send e-mail when someone mentions you - reblog: Send e-mail when someone boosts your ping + reblog: Send e-mail when someone boosts your status 'no': 'No' required: mark: "*" diff --git a/config/settings.yml b/config/settings.yml index 13ac097c6..19d2ca7be 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -7,7 +7,7 @@ # For more information, see docs/Running-Mastodon/Administration-guide.md # defaults: &defaults - site_title: 'Cybrespace' + site_title: 'dev.glitch.social' site_description: '' site_extended_description: '' site_contact_username: '' diff --git a/public/500.html b/public/500.html index 4197f5cdd..5812bb476 100644 --- a/public/500.html +++ b/public/500.html @@ -8,7 +8,7 @@ - - 1f418 - - - - + \ No newline at end of file diff --git a/public/mstile-150x150.png b/public/mstile-150x150.png index a79f11992..57eae8355 100644 Binary files a/public/mstile-150x150.png and b/public/mstile-150x150.png differ -- cgit From 75aafc932e42b5dba1030700bb47be0db41b1ab5 Mon Sep 17 00:00:00 2001 From: Ondřej Hruška Date: Fri, 14 Jul 2017 17:03:43 +0200 Subject: Added buttons and menu items to dismiss individual notifications (#76) * Added DELETE verb for notifications * Added notification dismiss button to status dropdown * Added reveal-on-hover notif dismiss button, added FollowNotification component --- app/controllers/api/v1/notifications_controller.rb | 4 ++ .../glitch/components/notification/container.js | 9 ++- .../components/notification/follow_notification.js | 78 ++++++++++++++++++++++ .../glitch/components/notification/index.js | 31 +++------ .../glitch/components/status/action_bar.js | 8 +++ .../glitch/components/status/container.js | 4 ++ app/javascript/glitch/components/status/index.js | 4 ++ app/javascript/glitch/components/status/prepend.js | 29 +++++++- app/javascript/mastodon/actions/notifications.js | 17 +++++ app/javascript/mastodon/locales/en.json | 1 + app/javascript/mastodon/reducers/notifications.js | 7 ++ app/javascript/styles/components.scss | 21 ++++++ config/routes.rb | 2 +- 13 files changed, 192 insertions(+), 23 deletions(-) create mode 100644 app/javascript/glitch/components/notification/follow_notification.js (limited to 'config') diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb index 8910b77e9..55f35fa4b 100644 --- a/app/controllers/api/v1/notifications_controller.rb +++ b/app/controllers/api/v1/notifications_controller.rb @@ -24,6 +24,10 @@ class Api::V1::NotificationsController < Api::BaseController render_empty end + def destroy + dismiss + end + def dismiss current_account.notifications.find_by!(id: params[:id]).destroy! render_empty diff --git a/app/javascript/glitch/components/notification/container.js b/app/javascript/glitch/components/notification/container.js index c58ef4bd2..60303537d 100644 --- a/app/javascript/glitch/components/notification/container.js +++ b/app/javascript/glitch/components/notification/container.js @@ -6,6 +6,7 @@ import { makeGetNotification } from '../../../mastodon/selectors'; // Our imports // import Notification from '.'; +import { deleteNotification } from '../../../mastodon/actions/notifications'; const makeMapStateToProps = () => { const getNotification = makeGetNotification(); @@ -18,4 +19,10 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -export default connect(makeMapStateToProps)(Notification); +const mapDispatchToProps = (dispatch) => ({ + onDeleteNotification (id) { + dispatch(deleteNotification(id)); + }, +}); + +export default connect(makeMapStateToProps, mapDispatchToProps)(Notification); diff --git a/app/javascript/glitch/components/notification/follow_notification.js b/app/javascript/glitch/components/notification/follow_notification.js new file mode 100644 index 000000000..7cabd91f6 --- /dev/null +++ b/app/javascript/glitch/components/notification/follow_notification.js @@ -0,0 +1,78 @@ +// Package imports // +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import escapeTextContentForBrowser from 'escape-html'; +import ImmutablePureComponent from 'react-immutable-pure-component'; + +// Mastodon imports // +import emojify from '../../../mastodon/emoji'; +import Permalink from '../../../mastodon/components/permalink'; +import AccountContainer from '../../../mastodon/containers/account_container'; + +const messages = defineMessages({ + deleteNotification: { id: 'status.dismiss_notification', defaultMessage: 'Dismiss notification' }, +}); + + +@injectIntl +export default class FollowNotification extends ImmutablePureComponent { + + static contextTypes = { + router: PropTypes.object, + }; + + static propTypes = { + notificationId: PropTypes.number.isRequired, + onDeleteNotification: PropTypes.func.isRequired, + account: ImmutablePropTypes.map.isRequired, + intl: PropTypes.object.isRequired, + }; + + // Avoid checking props that are functions (and whose equality will always + // evaluate to false. See react-immutable-pure-component for usage. + updateOnProps = [ + 'account', + ] + + handleNotificationDeleteClick = () => { + this.props.onDeleteNotification(this.props.notificationId); + } + + render () { + const { account, intl } = this.props; + + const dismissTitle = intl.formatMessage(messages.deleteNotification); + const dismiss = ( + + ); + + const displayName = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username'); + const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) }; + const link = ; + return ( +
+
+
+ +
+ + + + {dismiss} +
+ + +
+ ); + } + +} diff --git a/app/javascript/glitch/components/notification/index.js b/app/javascript/glitch/components/notification/index.js index 83ac8dfc1..0cdc03cbe 100644 --- a/app/javascript/glitch/components/notification/index.js +++ b/app/javascript/glitch/components/notification/index.js @@ -1,42 +1,30 @@ // Package imports // import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { FormattedMessage } from 'react-intl'; -import escapeTextContentForBrowser from 'escape-html'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import PropTypes from 'prop-types'; // Mastodon imports // -import AccountContainer from '../../../mastodon/containers/account_container'; -import Permalink from '../../../mastodon/components/permalink'; -import emojify from '../../../mastodon/emoji'; // Our imports // import StatusContainer from '../status/container'; +import FollowNotification from './follow_notification'; export default class Notification extends ImmutablePureComponent { static propTypes = { notification: ImmutablePropTypes.map.isRequired, settings: ImmutablePropTypes.map.isRequired, + onDeleteNotification: PropTypes.func.isRequired, }; renderFollow (notification) { - const account = notification.get('account'); - const displayName = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username'); - const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) }; - const link = ; return ( -
-
-
- -
- - -
- - -
+ ); } @@ -44,6 +32,7 @@ export default class Notification extends ImmutablePureComponent { return ( ); @@ -56,6 +45,7 @@ export default class Notification extends ImmutablePureComponent { account={notification.get('account')} prepend='favourite' muted + notificationId={notification.get('id')} withDismiss /> ); @@ -68,6 +58,7 @@ export default class Notification extends ImmutablePureComponent { account={notification.get('account')} prepend='reblog' muted + notificationId={notification.get('id')} withDismiss /> ); diff --git a/app/javascript/glitch/components/status/action_bar.js b/app/javascript/glitch/components/status/action_bar.js index f298dcaa8..6aa088c04 100644 --- a/app/javascript/glitch/components/status/action_bar.js +++ b/app/javascript/glitch/components/status/action_bar.js @@ -24,6 +24,7 @@ const messages = defineMessages({ report: { id: 'status.report', defaultMessage: 'Report @{name}' }, muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' }, unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' }, + deleteNotification: { id: 'status.dismiss_notification', defaultMessage: 'Dismiss notification' }, }); @injectIntl @@ -35,6 +36,7 @@ export default class StatusActionBar extends ImmutablePureComponent { static propTypes = { status: ImmutablePropTypes.map.isRequired, + notificationId: PropTypes.number, onReply: PropTypes.func, onFavourite: PropTypes.func, onReblog: PropTypes.func, @@ -44,6 +46,7 @@ export default class StatusActionBar extends ImmutablePureComponent { onBlock: PropTypes.func, onReport: PropTypes.func, onMuteConversation: PropTypes.func, + onDeleteNotification: PropTypes.func, me: PropTypes.number.isRequired, withDismiss: PropTypes.bool, intl: PropTypes.object.isRequired, @@ -97,6 +100,10 @@ export default class StatusActionBar extends ImmutablePureComponent { this.props.onMuteConversation(this.props.status); } + handleNotificationDeleteClick = () => { + this.props.onDeleteNotification(this.props.notificationId); + } + render () { const { status, me, intl, withDismiss } = this.props; const reblogDisabled = status.get('visibility') === 'private' || status.get('visibility') === 'direct'; @@ -112,6 +119,7 @@ export default class StatusActionBar extends ImmutablePureComponent { if (withDismiss) { menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick }); + menu.push({ text: intl.formatMessage(messages.deleteNotification), action: this.handleNotificationDeleteClick }); menu.push(null); } diff --git a/app/javascript/glitch/components/status/container.js b/app/javascript/glitch/components/status/container.js index a8aa6efe9..c45b2e0ec 100644 --- a/app/javascript/glitch/components/status/container.js +++ b/app/javascript/glitch/components/status/container.js @@ -50,6 +50,7 @@ import { } from '../../../mastodon/actions/statuses'; import { initReport } from '../../../mastodon/actions/reports'; import { openModal } from '../../../mastodon/actions/modal'; +import { deleteNotification } from '../../../mastodon/actions/notifications'; // Our imports // import Status from '.'; @@ -245,6 +246,9 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } }, + onDeleteNotification (id) { + dispatch(deleteNotification(id)); + }, }); export default injectIntl( diff --git a/app/javascript/glitch/components/status/index.js b/app/javascript/glitch/components/status/index.js index 1d135754a..314e8b51c 100644 --- a/app/javascript/glitch/components/status/index.js +++ b/app/javascript/glitch/components/status/index.js @@ -170,6 +170,7 @@ export default class Status extends ImmutablePureComponent { onReport : PropTypes.func, onOpenMedia : PropTypes.func, onOpenVideo : PropTypes.func, + onDeleteNotification : PropTypes.func, reblogModal : PropTypes.bool, deleteModal : PropTypes.bool, autoPlayGif : PropTypes.bool, @@ -177,6 +178,7 @@ export default class Status extends ImmutablePureComponent { collapse : PropTypes.bool, prepend : PropTypes.string, withDismiss : PropTypes.bool, + notificationId : PropTypes.number, intersectionObserverWrapper : PropTypes.object, }; @@ -685,6 +687,8 @@ collapsed. type={prepend} account={account} parseClick={parseClick} + notificationId={this.props.notificationId} + onDeleteNotification={this.props.onDeleteNotification} /> ) : null} { + this.props.onDeleteNotification(this.props.notificationId); + } + /* #### ``. @@ -145,7 +159,19 @@ the `` inside of an
+ {dismiss} ); } diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index c7d248122..b2a0f7ac3 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -6,6 +6,8 @@ import { defineMessages } from 'react-intl'; export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; +export const NOTIFICATION_DELETE_SUCCESS = 'NOTIFICATION_DELETE_SUCCESS'; + export const NOTIFICATIONS_REFRESH_REQUEST = 'NOTIFICATIONS_REFRESH_REQUEST'; export const NOTIFICATIONS_REFRESH_SUCCESS = 'NOTIFICATIONS_REFRESH_SUCCESS'; export const NOTIFICATIONS_REFRESH_FAIL = 'NOTIFICATIONS_REFRESH_FAIL'; @@ -187,3 +189,18 @@ export function scrollTopNotifications(top) { top, }; }; + +export function deleteNotification(id) { + return (dispatch, getState) => { + api(getState).delete(`/api/v1/notifications/${id}`).then(() => { + dispatch(deleteNotificationSuccess(id)); + }); + }; +}; + +export function deleteNotificationSuccess(id) { + return { + type: NOTIFICATION_DELETE_SUCCESS, + id: id, + }; +}; diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index cf29e38da..d2e5f90ea 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -190,6 +190,7 @@ "status.show_more": "Show more", "status.uncollapse": "Uncollapse", "status.unmute_conversation": "Unmute conversation", + "status.dismiss_notification": "Dismiss notification", "tabs_bar.compose": "Compose", "tabs_bar.federated_timeline": "Federated", "tabs_bar.home": "Home", diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js index 0063d24e4..da5fcde84 100644 --- a/app/javascript/mastodon/reducers/notifications.js +++ b/app/javascript/mastodon/reducers/notifications.js @@ -8,6 +8,7 @@ import { NOTIFICATIONS_EXPAND_FAIL, NOTIFICATIONS_CLEAR, NOTIFICATIONS_SCROLL_TOP, + NOTIFICATION_DELETE_SUCCESS, } from '../actions/notifications'; import { ACCOUNT_BLOCK_SUCCESS } from '../actions/accounts'; import { TIMELINE_DELETE } from '../actions/timelines'; @@ -92,6 +93,10 @@ const deleteByStatus = (state, statusId) => { return state.update('items', list => list.filterNot(item => item.get('status') === statusId)); }; +const deleteById = (state, notificationId) => { + return state.update('items', list => list.filterNot(item => item.get('id') === notificationId)); +}; + export default function notifications(state = initialState, action) { switch(action.type) { case NOTIFICATIONS_REFRESH_REQUEST: @@ -113,6 +118,8 @@ export default function notifications(state = initialState, action) { return state.set('items', ImmutableList()).set('next', null); case TIMELINE_DELETE: return deleteByStatus(state, action.id); + case NOTIFICATION_DELETE_SUCCESS: + return deleteById(state, action.id); default: return state; } diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index 6cca3666a..b06c99c95 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -617,6 +617,27 @@ position: absolute; } +.status__prepend-dismiss-button { + border: 0; + background: transparent; + position: absolute; + right: -3px; + opacity: 0; + transition: opacity 0.1s ease-in-out; + + i.fa { + color: crimson; + } + + .notification__message:hover & { + opacity: 1; + } + + .notification-follow & { + right: 6px; + } +} + .status { padding: 8px 10px; padding-left: 68px; diff --git a/config/routes.rb b/config/routes.rb index 963fedcb4..a63fb3ae6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -175,7 +175,7 @@ Rails.application.routes.draw do end end - resources :notifications, only: [:index, :show] do + resources :notifications, only: [:index, :show, :destroy] do collection do post :clear post :dismiss -- cgit From 3ea02314b951b42b1d5ee0cfc0a7aa9104050665 Mon Sep 17 00:00:00 2001 From: Ondřej Hruška Date: Sun, 16 Jul 2017 01:15:25 +0200 Subject: split added glitch locales from vanilla (#82) * Locale script now accepts overrides and new keys from glitch/locales * Revert glitchsoc changes to mastodon/locales to prevent future merge conflicts --- app/javascript/glitch/locales/en.json | 32 ++++++ .../mastodon/locales/defaultMessages.json | 115 ++------------------- app/javascript/mastodon/locales/en.json | 34 +----- config/webpack/generateLocalePacks.js | 20 +++- 4 files changed, 62 insertions(+), 139 deletions(-) create mode 100644 app/javascript/glitch/locales/en.json (limited to 'config') diff --git a/app/javascript/glitch/locales/en.json b/app/javascript/glitch/locales/en.json new file mode 100644 index 000000000..80fdc3a39 --- /dev/null +++ b/app/javascript/glitch/locales/en.json @@ -0,0 +1,32 @@ +{ + "getting_started.open_source_notice": "Glitchsoc is free open source software forked from {Mastodon}. You can contribute or report issues on GitHub at {github}.", + "layout.auto": "Auto", + "layout.current_is": "Your current layout is:", + "layout.desktop": "Desktop", + "layout.mobile": "Mobile", + "navigation_bar.app_settings": "App settings", + "onboarding.page_one.federation": "{domain} is an 'instance' of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.", + "onboarding.page_one.welcome": "Welcome to {domain}!", + "onboarding.page_six.github": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}, and is compatible with any Mastodon instance or app. Glitchsoc is entirely free and open-source. You can report bugs, request features, or contribute to the code on {github}.", + "settings.auto_collapse": "Automatic collapsing", + "settings.auto_collapse_all": "Everything", + "settings.auto_collapse_lengthy": "Lengthy toots", + "settings.auto_collapse_media": "Toots with media", + "settings.auto_collapse_notifications": "Notifications", + "settings.auto_collapse_replies": "Replies", + "settings.close": "Close", + "settings.collapsed_statuses": "Collapsed toots", + "settings.enable_collapsed": "Enable collapsed toots", + "settings.general": "General", + "settings.image_backgrounds": "Image backgrounds", + "settings.image_backgrounds_media": "Preview collapsed toot media", + "settings.image_backgrounds_users": "Give collapsed toots an image background", + "settings.media": "Media", + "settings.media_letterbox": "Letterbox media", + "settings.media_fullwidth": "Full-width media previews", + "settings.preferences": "User preferences", + "settings.wide_view": "Wide view (Desktop mode only)", + "status.collapse": "Collapse", + "status.uncollapse": "Uncollapse", + "status.dismiss_notification": "Dismiss notification" +} diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 368f68193..5c4daa8e7 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -188,14 +188,6 @@ }, { "descriptors": [ - { - "defaultMessage": "Collapse", - "id": "status.collapse" - }, - { - "defaultMessage": "Uncollapse", - "id": "status.uncollapse" - }, { "defaultMessage": "{name} boosted", "id": "status.reblogged_by" @@ -660,28 +652,12 @@ "id": "navigation_bar.community_timeline" }, { - "defaultMessage": "App settings", - "id": "navigation_bar.app_settings" + "defaultMessage": "Preferences", + "id": "navigation_bar.preferences" }, { "defaultMessage": "Logout", "id": "navigation_bar.logout" - }, - { - "defaultMessage": "Your current layout is:", - "id": "layout.current_is" - }, - { - "defaultMessage": "Mobile", - "id": "layout.mobile" - }, - { - "defaultMessage": "Auto", - "id": "layout.auto" - }, - { - "defaultMessage": "Desktop", - "id": "layout.desktop" } ], "path": "app/javascript/mastodon/features/compose/index.json" @@ -751,10 +727,6 @@ "defaultMessage": "Preferences", "id": "navigation_bar.preferences" }, - { - "defaultMessage": "App settings", - "id": "navigation_bar.app_settings" - }, { "defaultMessage": "Follow requests", "id": "navigation_bar.follow_requests" @@ -792,7 +764,7 @@ "id": "getting_started.appsshort" }, { - "defaultMessage": "Glitchsoc is open source software, a friendly fork of {Mastodon}. You can contribute or report issues on GitHub at {github}.", + "defaultMessage": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.", "id": "getting_started.open_source_notice" } ], @@ -1111,11 +1083,11 @@ "id": "column.public" }, { - "defaultMessage": "Welcome to {domain}!", + "defaultMessage": "Welcome to Mastodon!", "id": "onboarding.page_one.welcome" }, { - "defaultMessage": "{domain} is an \"instance\" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.", + "defaultMessage": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.", "id": "onboarding.page_one.federation" }, { @@ -1163,7 +1135,7 @@ "id": "onboarding.page_six.almost_done" }, { - "defaultMessage": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.", + "defaultMessage": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.", "id": "onboarding.page_six.github" }, { @@ -1210,79 +1182,6 @@ ], "path": "app/javascript/mastodon/features/ui/components/report_modal.json" }, - { - "descriptors": [ - { - "defaultMessage": "General", - "id": "settings.general" - }, - { - "defaultMessage": "Wide view (Desktop mode only)", - "id": "settings.wide_view" - }, - { - "defaultMessage": "Collapsed toots", - "id": "settings.collapsed_statuses" - }, - { - "defaultMessage": "Enable collapsed toots", - "id": "settings.enable_collapsed" - }, - { - "defaultMessage": "Automatic collapsing", - "id": "settings.auto_collapse" - }, - { - "defaultMessage": "Everything", - "id": "settings.auto_collapse_all" - }, - { - "defaultMessage": "Notifications", - "id": "settings.auto_collapse_notifications" - }, - { - "defaultMessage": "Lengthy toots", - "id": "settings.auto_collapse_lengthy" - }, - { - "defaultMessage": "Replies", - "id": "settings.auto_collapse_replies" - }, - { - "defaultMessage": "Toots with media", - "id": "settings.auto_collapse_media" - }, - { - "defaultMessage": "Image backgrounds", - "id": "settings.image_backgrounds" - }, - { - "defaultMessage": "Give collapsed toots an image background", - "id": "settings.image_backgrounds_users" - }, - { - "defaultMessage": "Preview collapsed toot media", - "id": "settings.image_backgrounds_media" - }, - { - "defaultMessage": "Media", - "id": "settings.media" - }, - { - "defaultMessage": "Letterbox media", - "id": "settings.media_letterbox" - }, - { - "defaultMessage": "User preferences", - "id": "settings.preferences" - }, - { - "defaultMessage": "Close", - "id": "settings.close" - } - ], - "path": "app/javascript/mastodon/features/ui/components/settings_modal.json" - }, { "descriptors": [ { @@ -1326,4 +1225,4 @@ ], "path": "app/javascript/mastodon/features/ui/components/video_modal.json" } -] \ No newline at end of file +] diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 1d553d514..15afe2309 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -77,7 +77,7 @@ "getting_started.appsshort": "Apps", "getting_started.faq": "FAQ", "getting_started.heading": "Getting started", - "getting_started.open_source_notice": "Glitchsoc is free open source software forked from {Mastodon}. You can contribute or report issues on GitHub at {github}.", + "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.", "getting_started.userguide": "User Guide", "home.column_settings.advanced": "Advanced", "home.column_settings.basic": "Basic", @@ -85,15 +85,10 @@ "home.column_settings.show_reblogs": "Show boosts", "home.column_settings.show_replies": "Show replies", "home.settings": "Column settings", - "layout.auto": "Auto", - "layout.current_is": "Your current layout is:", - "layout.desktop": "Desktop", - "layout.mobile": "Mobile", "lightbox.close": "Close", "loading_indicator.label": "Loading...", "media_gallery.toggle_visible": "Toggle visibility", "missing_indicator.label": "Not found", - "navigation_bar.app_settings": "App settings", "navigation_bar.blocks": "Blocked users", "navigation_bar.community_timeline": "Local timeline", "navigation_bar.edit_profile": "Edit profile", @@ -124,14 +119,14 @@ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.", "onboarding.page_four.home": "The home timeline shows posts from people you follow.", "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.", - "onboarding.page_one.federation": "{domain} is an 'instance' of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.", + "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.", "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}", - "onboarding.page_one.welcome": "Welcome to {domain}!", + "onboarding.page_one.welcome": "Welcome to Mastodon!", "onboarding.page_six.admin": "Your instance's admin is {admin}.", "onboarding.page_six.almost_done": "Almost done...", "onboarding.page_six.appetoot": "Bon Appetoot!", "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.", - "onboarding.page_six.github": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}, and is compatible with any Mastodon instance or app. Glitchsoc is entirely free and open-source. You can report bugs, request features, or contribute to the code on {github}.", + "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.", "onboarding.page_six.guidelines": "community guidelines", "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!", "onboarding.page_six.various_app": "mobile apps", @@ -154,27 +149,8 @@ "report.target": "Reporting {target}", "search.placeholder": "Search", "search_results.total": "{count, number} {count, plural, one {result} other {results}}", - "settings.auto_collapse": "Automatic collapsing", - "settings.auto_collapse_all": "Everything", - "settings.auto_collapse_lengthy": "Lengthy toots", - "settings.auto_collapse_media": "Toots with media", - "settings.auto_collapse_notifications": "Notifications", - "settings.auto_collapse_replies": "Replies", - "settings.close": "Close", - "settings.collapsed_statuses": "Collapsed toots", - "settings.enable_collapsed": "Enable collapsed toots", - "settings.general": "General", - "settings.image_backgrounds": "Image backgrounds", - "settings.image_backgrounds_media": "Preview collapsed toot media", - "settings.image_backgrounds_users": "Give collapsed toots an image background", - "settings.media": "Media", - "settings.media_letterbox": "Letterbox media", - "settings.media_fullwidth": "Full-width media previews", - "settings.preferences": "User preferences", - "settings.wide_view": "Wide view (Desktop mode only)", "standalone.public_title": "A look inside...", "status.cannot_reblog": "This post cannot be boosted", - "status.collapse": "Collapse", "status.delete": "Delete", "status.favourite": "Favourite", "status.load_more": "Load more", @@ -191,9 +167,7 @@ "status.sensitive_warning": "Sensitive content", "status.show_less": "Show less", "status.show_more": "Show more", - "status.uncollapse": "Uncollapse", "status.unmute_conversation": "Unmute conversation", - "status.dismiss_notification": "Dismiss notification", "tabs_bar.compose": "Compose", "tabs_bar.federated_timeline": "Federated", "tabs_bar.home": "Home", diff --git a/config/webpack/generateLocalePacks.js b/config/webpack/generateLocalePacks.js index b71cf2ade..cd3bed50c 100644 --- a/config/webpack/generateLocalePacks.js +++ b/config/webpack/generateLocalePacks.js @@ -34,6 +34,23 @@ locales.forEach(locale => { ].filter(filename => fs.existsSync(path.join(outPath, filename))) .map(filename => filename.replace(/..\/..\/node_modules\//, ''))[0]; + let glitchInject = ` +const mergedMessages = messages; +`; + + const glitchPath = `../../app/javascript/glitch/locales/${locale}.json`; + if (fs.existsSync(path.join(outPath, glitchPath))) { + glitchInject = ` +import glitchMessages from ${JSON.stringify(glitchPath)}; + +let mergedMessages = messages; +Object.keys(glitchMessages).forEach(function (key) { + mergedMessages[key] = glitchMessages[key]; +}); + +`; + } + const localeContent = `// // locale_${locale}.js // automatically generated by generateLocalePacks.js @@ -41,7 +58,8 @@ locales.forEach(locale => { import messages from '../../app/javascript/mastodon/locales/${locale}.json'; import localeData from ${JSON.stringify(localeDataPath)}; import { setLocale } from '../../app/javascript/mastodon/locales'; -setLocale({messages, localeData}); +${glitchInject} +setLocale({messages: mergedMessages, localeData: localeData}); `; fs.writeFileSync(localePath, localeContent, 'utf8'); outPaths.push(localePath); -- cgit From 7a77f7b3bbafff6b8c9899e1c4404ab5e5cde853 Mon Sep 17 00:00:00 2001 From: Surinna Curtis Date: Mon, 17 Jul 2017 22:06:31 -0500 Subject: Add sourceRoot/includePaths to loaders Use the settings modal as an example/testcase --- .../glitch/components/settings/container.js | 6 +- app/javascript/glitch/components/settings/index.js | 2 + .../glitch/components/settings/stylesheet.scss | 84 ++++++++++++++++++++++ app/javascript/styles/components.scss | 83 --------------------- config/webpack/loaders/babel.js | 1 + config/webpack/loaders/sass.js | 2 +- 6 files changed, 91 insertions(+), 87 deletions(-) create mode 100644 app/javascript/glitch/components/settings/stylesheet.scss (limited to 'config') diff --git a/app/javascript/glitch/components/settings/container.js b/app/javascript/glitch/components/settings/container.js index 6034935eb..5dfe228c0 100644 --- a/app/javascript/glitch/components/settings/container.js +++ b/app/javascript/glitch/components/settings/container.js @@ -2,11 +2,11 @@ import { connect } from 'react-redux'; // Mastodon imports // -import { closeModal } from '../../../mastodon/actions/modal'; +import { closeModal } from 'mastodon/actions/modal'; // Our imports // -import { changeLocalSetting } from '../../actions/local_settings'; -import Settings from '../../components/settings'; +import { changeLocalSetting } from 'glitch/actions/local_settings'; +import Settings from 'glitch/components/settings'; const mapStateToProps = state => ({ settings: state.get('local_settings'), diff --git a/app/javascript/glitch/components/settings/index.js b/app/javascript/glitch/components/settings/index.js index afe7e9a87..04c05704e 100644 --- a/app/javascript/glitch/components/settings/index.js +++ b/app/javascript/glitch/components/settings/index.js @@ -4,6 +4,8 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { injectIntl, defineMessages, FormattedMessage } from 'react-intl'; +import './stylesheet'; + // Our imports // import SettingsItem from './item'; diff --git a/app/javascript/glitch/components/settings/stylesheet.scss b/app/javascript/glitch/components/settings/stylesheet.scss new file mode 100644 index 000000000..48cc37984 --- /dev/null +++ b/app/javascript/glitch/components/settings/stylesheet.scss @@ -0,0 +1,84 @@ +@import 'variables'; + +.settings-modal { + position: relative; + display: flex; + flex-direction: row; + background: $ui-secondary-color; + color: $ui-base-color; + border-radius: 8px; + height: 80vh; + width: 80vw; + max-width: 740px; + max-height: 450px; + overflow: hidden; + + label { + display: block; + } + + h1 { + font-size: 18px; + font-weight: 500; + line-height: 24px; + margin-bottom: 20px; + } + + h2 { + font-size: 15px; + font-weight: 500; + line-height: 20px; + margin-top: 20px; + margin-bottom: 10px; + } +} + +.settings-modal__navigation { + background: $primary-text-color; + color: $ui-base-color; + width: 200px; + font-size: 15px; + line-height: 20px; + overflow-y: auto; + + .settings-modal__navigation-item, .settings-modal__navigation-close { + display: block; + padding: 15px 20px; + cursor: pointer; + outline: none; + text-decoration: none; + } + + .settings-modal__navigation-item { + background: $primary-text-color; + color: inherit; + border-bottom: 1px $ui-primary-color solid; + transition: background .3s; + + &:hover { + background: $ui-secondary-color; + } + + &.active { + background: $ui-highlight-color; + color: $primary-text-color; + } + } + + .settings-modal__navigation-close { + background: $error-value-color; + color: $primary-text-color; + } +} + +.settings-modal__content { + display: block; + flex: auto; + padding: 15px 20px 15px 20px; + width: 360px; + overflow-y: auto; + + select { + margin-bottom: 5px; + } +} \ No newline at end of file diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index a09a33e00..0a8fa5e6d 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -3659,89 +3659,6 @@ button.icon-button.active i.fa-retweet { margin-bottom: 20px; } -.settings-modal { - position: relative; - display: flex; - flex-direction: row; - background: $ui-secondary-color; - color: $ui-base-color; - border-radius: 8px; - height: 80vh; - width: 80vw; - max-width: 740px; - max-height: 450px; - overflow: hidden; - - label { - display: block; - } - - h1 { - font-size: 18px; - font-weight: 500; - line-height: 24px; - margin-bottom: 20px; - } - - h2 { - font-size: 15px; - font-weight: 500; - line-height: 20px; - margin-top: 20px; - margin-bottom: 10px; - } -} - -.settings-modal__navigation { - background: $primary-text-color; - color: $ui-base-color; - width: 200px; - font-size: 15px; - line-height: 20px; - overflow-y: auto; - - .settings-modal__navigation-item, .settings-modal__navigation-close { - display: block; - padding: 15px 20px; - cursor: pointer; - outline: none; - text-decoration: none; - } - - .settings-modal__navigation-item { - background: $primary-text-color; - color: inherit; - border-bottom: 1px $ui-primary-color solid; - transition: background .3s; - - &:hover { - background: $ui-secondary-color; - } - - &.active { - background: $ui-highlight-color; - color: $primary-text-color; - } - } - - .settings-modal__navigation-close { - background: $error-value-color; - color: $primary-text-color; - } -} - -.settings-modal__content { - display: block; - flex: auto; - padding: 15px 20px 15px 20px; - width: 360px; - overflow-y: auto; - - select { - margin-bottom: 5px; - } -} - .onboard-sliders { display: inline-block; max-width: 30px; diff --git a/config/webpack/loaders/babel.js b/config/webpack/loaders/babel.js index a1992a450..49b191d26 100644 --- a/config/webpack/loaders/babel.js +++ b/config/webpack/loaders/babel.js @@ -8,5 +8,6 @@ module.exports = { loader: 'babel-loader', options: { forceEnv: process.env.NODE_ENV || 'development', + sourceRoot: 'app/javascript', }, }; diff --git a/config/webpack/loaders/sass.js b/config/webpack/loaders/sass.js index 88d94c684..40e81b43b 100644 --- a/config/webpack/loaders/sass.js +++ b/config/webpack/loaders/sass.js @@ -9,7 +9,7 @@ module.exports = { { loader: 'css-loader', options: { minimize: env.NODE_ENV === 'production' } }, { loader: 'postcss-loader', options: { sourceMap: true } }, 'resolve-url-loader', - 'sass-loader', + { loader: 'sass-loader', options: { includePaths: ['app/javascript/styles'] } }, ], }), }; -- cgit From 0a678cf377c6dd91c439fa91f75782e457836639 Mon Sep 17 00:00:00 2001 From: kibigo! Date: Tue, 18 Jul 2017 11:21:04 -0700 Subject: Fix for stylesheet split --- app/javascript/glitch/components/settings/index.js | 9 +-- .../glitch/components/settings/style.scss | 84 ++++++++++++++++++++++ .../glitch/components/settings/stylesheet.scss | 84 ---------------------- app/views/home/index.html.haml | 1 + config/webpack/shared.js | 5 +- 5 files changed, 94 insertions(+), 89 deletions(-) create mode 100644 app/javascript/glitch/components/settings/style.scss delete mode 100644 app/javascript/glitch/components/settings/stylesheet.scss (limited to 'config') diff --git a/app/javascript/glitch/components/settings/index.js b/app/javascript/glitch/components/settings/index.js index 04c05704e..ab2e0fb87 100644 --- a/app/javascript/glitch/components/settings/index.js +++ b/app/javascript/glitch/components/settings/index.js @@ -1,14 +1,15 @@ -// Package imports // +// Package imports import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { injectIntl, defineMessages, FormattedMessage } from 'react-intl'; -import './stylesheet'; - -// Our imports // +// Our imports import SettingsItem from './item'; +// Stylesheet imports +import './style'; + const messages = defineMessages({ layout_auto: { id: 'layout.auto', defaultMessage: 'Auto' }, layout_desktop: { id: 'layout.desktop', defaultMessage: 'Desktop' }, diff --git a/app/javascript/glitch/components/settings/style.scss b/app/javascript/glitch/components/settings/style.scss new file mode 100644 index 000000000..48cc37984 --- /dev/null +++ b/app/javascript/glitch/components/settings/style.scss @@ -0,0 +1,84 @@ +@import 'variables'; + +.settings-modal { + position: relative; + display: flex; + flex-direction: row; + background: $ui-secondary-color; + color: $ui-base-color; + border-radius: 8px; + height: 80vh; + width: 80vw; + max-width: 740px; + max-height: 450px; + overflow: hidden; + + label { + display: block; + } + + h1 { + font-size: 18px; + font-weight: 500; + line-height: 24px; + margin-bottom: 20px; + } + + h2 { + font-size: 15px; + font-weight: 500; + line-height: 20px; + margin-top: 20px; + margin-bottom: 10px; + } +} + +.settings-modal__navigation { + background: $primary-text-color; + color: $ui-base-color; + width: 200px; + font-size: 15px; + line-height: 20px; + overflow-y: auto; + + .settings-modal__navigation-item, .settings-modal__navigation-close { + display: block; + padding: 15px 20px; + cursor: pointer; + outline: none; + text-decoration: none; + } + + .settings-modal__navigation-item { + background: $primary-text-color; + color: inherit; + border-bottom: 1px $ui-primary-color solid; + transition: background .3s; + + &:hover { + background: $ui-secondary-color; + } + + &.active { + background: $ui-highlight-color; + color: $primary-text-color; + } + } + + .settings-modal__navigation-close { + background: $error-value-color; + color: $primary-text-color; + } +} + +.settings-modal__content { + display: block; + flex: auto; + padding: 15px 20px 15px 20px; + width: 360px; + overflow-y: auto; + + select { + margin-bottom: 5px; + } +} \ No newline at end of file diff --git a/app/javascript/glitch/components/settings/stylesheet.scss b/app/javascript/glitch/components/settings/stylesheet.scss deleted file mode 100644 index 48cc37984..000000000 --- a/app/javascript/glitch/components/settings/stylesheet.scss +++ /dev/null @@ -1,84 +0,0 @@ -@import 'variables'; - -.settings-modal { - position: relative; - display: flex; - flex-direction: row; - background: $ui-secondary-color; - color: $ui-base-color; - border-radius: 8px; - height: 80vh; - width: 80vw; - max-width: 740px; - max-height: 450px; - overflow: hidden; - - label { - display: block; - } - - h1 { - font-size: 18px; - font-weight: 500; - line-height: 24px; - margin-bottom: 20px; - } - - h2 { - font-size: 15px; - font-weight: 500; - line-height: 20px; - margin-top: 20px; - margin-bottom: 10px; - } -} - -.settings-modal__navigation { - background: $primary-text-color; - color: $ui-base-color; - width: 200px; - font-size: 15px; - line-height: 20px; - overflow-y: auto; - - .settings-modal__navigation-item, .settings-modal__navigation-close { - display: block; - padding: 15px 20px; - cursor: pointer; - outline: none; - text-decoration: none; - } - - .settings-modal__navigation-item { - background: $primary-text-color; - color: inherit; - border-bottom: 1px $ui-primary-color solid; - transition: background .3s; - - &:hover { - background: $ui-secondary-color; - } - - &.active { - background: $ui-highlight-color; - color: $primary-text-color; - } - } - - .settings-modal__navigation-close { - background: $error-value-color; - color: $primary-text-color; - } -} - -.settings-modal__content { - display: block; - flex: auto; - padding: 15px 20px 15px 20px; - width: 360px; - overflow-y: auto; - - select { - margin-bottom: 5px; - } -} \ No newline at end of file diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 13ca9ea79..f0f7bd619 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -3,6 +3,7 @@ %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json) = javascript_pack_tag 'application', integrity: true, crossorigin: 'anonymous' + = stylesheet_pack_tag 'application', media: 'all' .app-holder#mastodon{ data: { props: Oj.dump(default_props) } } %noscript diff --git a/config/webpack/shared.js b/config/webpack/shared.js index 4d865b816..98e864a66 100644 --- a/config/webpack/shared.js +++ b/config/webpack/shared.js @@ -44,7 +44,10 @@ module.exports = { plugins: [ new webpack.EnvironmentPlugin(JSON.parse(JSON.stringify(env))), - new ExtractTextPlugin(env.NODE_ENV === 'production' ? '[name]-[hash].css' : '[name].css'), + new ExtractTextPlugin({ + filename: env.NODE_ENV === 'production' ? '[name]-[hash].css' : '[name].css', + allChunks: true, + }), new ManifestPlugin({ publicPath: output.publicPath, writeToFileEmit: true, -- cgit From 0efd7e740602dd684712563b7ad0b41c23d86d69 Mon Sep 17 00:00:00 2001 From: beatrix Date: Thu, 20 Jul 2017 17:26:00 -0400 Subject: disable about page timeline --- config/settings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'config') diff --git a/config/settings.yml b/config/settings.yml index d677e1f84..38871c772 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -16,7 +16,7 @@ defaults: &defaults open_registrations: true closed_registrations_message: '' open_deletion: true - timeline_preview: true + timeline_preview: false boost_modal: false auto_play_gif: false delete_modal: true -- cgit From 604654ccb417ffdc9b48d876bea76c8bec14f360 Mon Sep 17 00:00:00 2001 From: Ondřej Hruška Date: Fri, 21 Jul 2017 20:33:16 +0200 Subject: New notification cleaning mode (#89) This PR adds a new notification cleaning mode, super perfectly tuned for accessibility, and removes the previous notification cleaning functionality as it's now redundant. * w.i.p. notif clearing mode * Better CSS for selected notification and shorter text if Stretch is off * wip for rebase ~ * all working in notif clearing mode, except the actual removal * bulk delete route for piggo * cleaning + refactor. endpoint gives 422 for some reason * formatting * use the right route * fix broken destroy_multiple * load more notifs after succ cleaning * satisfy eslint * Removed CSS for the old notif delete button * Tabindex=0 is mandatory In order to make it possible to tab to this element you must have tab index = 0. Removing this violates WCAG and makes it impossible to use the interface without good eyesight and a mouse. So nobody with certain mobility impairments, vision impairments, or brain injuries would be able to use this feature if you don't have tabindex=0 * Corrected aria-label Previous label implied a different behavior from what actually happens * aria role localization & made the overlay behave like a checkbox * checkboxes css and better contrast * color tuning for the notif overlay * fanceh checkboxes etc and nice backgrounds * SHUT UP TRAVIS --- app/controllers/api/v1/notifications_controller.rb | 5 ++ .../column/notif_cleaning_widget/container.js | 56 ++++++++++++ .../notification_purge_buttons.js | 100 +++++++++++++++++++++ .../glitch/components/notification/container.js | 20 +---- .../glitch/components/notification/follow.js | 61 ++----------- .../glitch/components/notification/index.js | 10 +-- .../components/notification/overlay/container.js | 49 ++++++++++ .../notification/overlay/notification_overlay.js | 59 ++++++++++++ .../glitch/components/status/action_bar.js | 8 -- .../glitch/components/status/container.js | 5 -- app/javascript/glitch/components/status/index.js | 20 ++++- app/javascript/glitch/components/status/prepend.js | 28 +----- app/javascript/glitch/locales/en.json | 2 +- app/javascript/mastodon/actions/notifications.js | 64 +++++++++++-- app/javascript/mastodon/components/column.js | 7 +- .../mastodon/components/column_header.js | 26 +++++- .../mastodon/features/notifications/index.js | 16 +++- app/javascript/mastodon/reducers/notifications.js | 42 +++++++-- app/javascript/styles/components.scss | 90 +++++++++++++++---- config/routes.rb | 1 + 20 files changed, 513 insertions(+), 156 deletions(-) create mode 100644 app/javascript/glitch/components/column/notif_cleaning_widget/container.js create mode 100644 app/javascript/glitch/components/column/notif_cleaning_widget/notification_purge_buttons.js create mode 100644 app/javascript/glitch/components/notification/overlay/container.js create mode 100644 app/javascript/glitch/components/notification/overlay/notification_overlay.js (limited to 'config') diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb index 55f35fa4b..a949752fb 100644 --- a/app/controllers/api/v1/notifications_controller.rb +++ b/app/controllers/api/v1/notifications_controller.rb @@ -33,6 +33,11 @@ class Api::V1::NotificationsController < Api::BaseController render_empty end + def destroy_multiple + current_account.notifications.where(id: params[:ids]).destroy_all + render_empty + end + private def load_notifications diff --git a/app/javascript/glitch/components/column/notif_cleaning_widget/container.js b/app/javascript/glitch/components/column/notif_cleaning_widget/container.js new file mode 100644 index 000000000..bf079e3c4 --- /dev/null +++ b/app/javascript/glitch/components/column/notif_cleaning_widget/container.js @@ -0,0 +1,56 @@ +/* + +`` +========================= + +This container connects ``s to the Redux store. + +*/ + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Imports: +-------- + +*/ + +// Package imports // +import { connect } from 'react-redux'; + +// Our imports // +import NotificationPurgeButtons from './notification_purge_buttons'; +import { + deleteMarkedNotifications, + enterNotificationClearingMode, +} from '../../../../mastodon/actions/notifications'; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Dispatch mapping: +----------------- + +The `mapDispatchToProps()` function maps dispatches to our store to the +various props of our component. We only need to provide a dispatch for +deleting notifications. + +*/ + +const mapDispatchToProps = dispatch => ({ + onEnterCleaningMode(yes) { + dispatch(enterNotificationClearingMode(yes)); + }, + + onDeleteMarkedNotifications() { + dispatch(deleteMarkedNotifications()); + }, +}); + +const mapStateToProps = state => ({ + active: state.getIn(['notifications', 'cleaningMode']), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(NotificationPurgeButtons); diff --git a/app/javascript/glitch/components/column/notif_cleaning_widget/notification_purge_buttons.js b/app/javascript/glitch/components/column/notif_cleaning_widget/notification_purge_buttons.js new file mode 100644 index 000000000..e41572256 --- /dev/null +++ b/app/javascript/glitch/components/column/notif_cleaning_widget/notification_purge_buttons.js @@ -0,0 +1,100 @@ +/** + * Buttons widget for controlling the notification clearing mode. + * In idle state, the cleaning mode button is shown. When the mode is active, + * a Confirm and Abort buttons are shown in its place. + */ + + +// Package imports // +import React from 'react'; +import PropTypes from 'prop-types'; +import { defineMessages, injectIntl } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; + +// Mastodon imports // + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +const messages = defineMessages({ + enter : { id: 'notification_purge.start', defaultMessage: 'Enter notification cleaning mode' }, + accept : { id: 'notification_purge.confirm', defaultMessage: 'Dismiss selected notifications' }, + abort : { id: 'notification_purge.abort', defaultMessage: 'Leave cleaning mode' }, +}); + +@injectIntl +export default class NotificationPurgeButtons extends ImmutablePureComponent { + + static propTypes = { + // Nukes all marked notifications + onDeleteMarkedNotifications : PropTypes.func.isRequired, + // Enables or disables the mode + // and also clears the marked status of all notifications + onEnterCleaningMode : PropTypes.func.isRequired, + // Active state, changed via onStateChange() + active: PropTypes.bool.isRequired, + // i18n + intl: PropTypes.object.isRequired, + }; + + onEnterBtnClick = () => { + this.props.onEnterCleaningMode(true); + } + + onAcceptBtnClick = () => { + this.props.onDeleteMarkedNotifications(); + } + + onAbortBtnClick = () => { + this.props.onEnterCleaningMode(false); + } + + render () { + const { intl, active } = this.props; + + const msgEnter = intl.formatMessage(messages.enter); + const msgAccept = intl.formatMessage(messages.accept); + const msgAbort = intl.formatMessage(messages.abort); + + let enterButton, acceptButton, abortButton; + + if (active) { + acceptButton = ( + + ); + abortButton = ( + + ); + } else { + enterButton = ( + + ); + } + + return ( +
+ {acceptButton}{abortButton}{enterButton} +
+ ); + } + +} diff --git a/app/javascript/glitch/components/notification/container.js b/app/javascript/glitch/components/notification/container.js index bed086172..7d2590684 100644 --- a/app/javascript/glitch/components/notification/container.js +++ b/app/javascript/glitch/components/notification/container.js @@ -24,7 +24,6 @@ import { makeGetNotification } from '../../../mastodon/selectors'; // Our imports // import Notification from '.'; -import { deleteNotification } from '../../../mastodon/actions/notifications'; // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * @@ -53,21 +52,4 @@ const makeMapStateToProps = () => { // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -/* - -Dispatch mapping: ------------------ - -The `mapDispatchToProps()` function maps dispatches to our store to the -various props of our component. We only need to provide a dispatch for -deleting notifications. - -*/ - -const mapDispatchToProps = dispatch => ({ - onDeleteNotification (id) { - dispatch(deleteNotification(id)); - }, -}); - -export default connect(makeMapStateToProps, mapDispatchToProps)(Notification); +export default connect(makeMapStateToProps)(Notification); diff --git a/app/javascript/glitch/components/notification/follow.js b/app/javascript/glitch/components/notification/follow.js index 26396478b..0e0065eb1 100644 --- a/app/javascript/glitch/components/notification/follow.js +++ b/app/javascript/glitch/components/notification/follow.js @@ -36,7 +36,7 @@ Imports: import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; -import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import { FormattedMessage } from 'react-intl'; import escapeTextContentForBrowser from 'escape-html'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -45,22 +45,10 @@ import emojify from '../../../mastodon/emoji'; import Permalink from '../../../mastodon/components/permalink'; import AccountContainer from '../../../mastodon/containers/account_container'; -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - -/* - -Inital setup: -------------- - -The `messages` constant is used to define any messages that we need -from inside props. - -*/ +// Our imports // +import NotificationOverlayContainer from '../notification/overlay/container'; -const messages = defineMessages({ - deleteNotification : - { id: 'status.dismiss_notification', defaultMessage: 'Dismiss notification' }, -}); +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* @@ -69,31 +57,16 @@ Implementation: */ -@injectIntl export default class NotificationFollow extends ImmutablePureComponent { static propTypes = { id : PropTypes.number.isRequired, - onDeleteNotification : PropTypes.func.isRequired, account : ImmutablePropTypes.map.isRequired, - intl : PropTypes.object.isRequired, + notification : ImmutablePropTypes.map.isRequired, }; /* -### `handleNotificationDeleteClick()` - -This function just calls our `onDeleteNotification()` prop with the -notification's `id`. - -*/ - - handleNotificationDeleteClick = () => { - this.props.onDeleteNotification(this.props.id); - } - -/* - ### `render()` This actually renders the component. @@ -101,26 +74,7 @@ This actually renders the component. */ render () { - const { account, intl } = this.props; - -/* - -`dismiss` creates the notification dismissal button. Its title is given -by `dismissTitle`. - -*/ - - const dismissTitle = intl.formatMessage(messages.deleteNotification); - const dismiss = ( - - ); + const { account, notification } = this.props; /* @@ -149,6 +103,7 @@ We can now render our component. return (
+
@@ -159,8 +114,6 @@ We can now render our component. defaultMessage='{name} followed you' values={{ name: link }} /> - - {dismiss}
diff --git a/app/javascript/glitch/components/notification/index.js b/app/javascript/glitch/components/notification/index.js index 556d5aea8..b2e55aad5 100644 --- a/app/javascript/glitch/components/notification/index.js +++ b/app/javascript/glitch/components/notification/index.js @@ -2,7 +2,6 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import PropTypes from 'prop-types'; // Mastodon imports // @@ -15,7 +14,6 @@ export default class Notification extends ImmutablePureComponent { static propTypes = { notification: ImmutablePropTypes.map.isRequired, settings: ImmutablePropTypes.map.isRequired, - onDeleteNotification: PropTypes.func.isRequired, }; renderFollow (notification) { @@ -23,7 +21,7 @@ export default class Notification extends ImmutablePureComponent { ); } @@ -32,7 +30,7 @@ export default class Notification extends ImmutablePureComponent { return ( ); @@ -45,7 +43,7 @@ export default class Notification extends ImmutablePureComponent { account={notification.get('account')} prepend='favourite' muted - notificationId={notification.get('id')} + notification={notification} withDismiss /> ); @@ -58,7 +56,7 @@ export default class Notification extends ImmutablePureComponent { account={notification.get('account')} prepend='reblog' muted - notificationId={notification.get('id')} + notification={notification} withDismiss /> ); diff --git a/app/javascript/glitch/components/notification/overlay/container.js b/app/javascript/glitch/components/notification/overlay/container.js new file mode 100644 index 000000000..019b78d0b --- /dev/null +++ b/app/javascript/glitch/components/notification/overlay/container.js @@ -0,0 +1,49 @@ +/* + +`` +========================= + +This container connects ``s to the Redux store. + +*/ + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Imports: +-------- + +*/ + +// Package imports // +import { connect } from 'react-redux'; + +// Our imports // +import NotificationOverlay from './notification_overlay'; +import { markNotificationForDelete } from '../../../../mastodon/actions/notifications'; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Dispatch mapping: +----------------- + +The `mapDispatchToProps()` function maps dispatches to our store to the +various props of our component. We only need to provide a dispatch for +deleting notifications. + +*/ + +const mapDispatchToProps = dispatch => ({ + onMarkForDelete(id, yes) { + dispatch(markNotificationForDelete(id, yes)); + }, +}); + +const mapStateToProps = state => ({ + revealed: state.getIn(['notifications', 'cleaningMode']), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(NotificationOverlay); diff --git a/app/javascript/glitch/components/notification/overlay/notification_overlay.js b/app/javascript/glitch/components/notification/overlay/notification_overlay.js new file mode 100644 index 000000000..73eda718f --- /dev/null +++ b/app/javascript/glitch/components/notification/overlay/notification_overlay.js @@ -0,0 +1,59 @@ +/** + * Notification overlay + */ + + +// Package imports // +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { defineMessages, injectIntl } from 'react-intl'; + +// Mastodon imports // + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +const messages = defineMessages({ + markForDeletion: { id: 'notification.markForDeletion', defaultMessage: 'Mark for deletion' }, +}); + +@injectIntl +export default class NotificationOverlay extends ImmutablePureComponent { + + static propTypes = { + notification : ImmutablePropTypes.map.isRequired, + onMarkForDelete : PropTypes.func.isRequired, + revealed : PropTypes.bool.isRequired, + intl : PropTypes.object.isRequired, + }; + + onToggleMark = () => { + const mark = !this.props.notification.get('markedForDelete'); + const id = this.props.notification.get('id'); + this.props.onMarkForDelete(id, mark); + } + + render () { + const { notification, revealed, intl } = this.props; + + const active = notification.get('markedForDelete'); + const label = intl.formatMessage(messages.markForDeletion); + + return ( +
+ +
+ ); + } + +} diff --git a/app/javascript/glitch/components/status/action_bar.js b/app/javascript/glitch/components/status/action_bar.js index df0904a7c..7c73002c1 100644 --- a/app/javascript/glitch/components/status/action_bar.js +++ b/app/javascript/glitch/components/status/action_bar.js @@ -24,7 +24,6 @@ const messages = defineMessages({ report: { id: 'status.report', defaultMessage: 'Report @{name}' }, muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' }, unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' }, - deleteNotification: { id: 'status.dismiss_notification', defaultMessage: 'Dismiss notification' }, }); @injectIntl @@ -36,7 +35,6 @@ export default class StatusActionBar extends ImmutablePureComponent { static propTypes = { status: ImmutablePropTypes.map.isRequired, - notificationId: PropTypes.number, onReply: PropTypes.func, onFavourite: PropTypes.func, onReblog: PropTypes.func, @@ -46,7 +44,6 @@ export default class StatusActionBar extends ImmutablePureComponent { onBlock: PropTypes.func, onReport: PropTypes.func, onMuteConversation: PropTypes.func, - onDeleteNotification: PropTypes.func, me: PropTypes.number, withDismiss: PropTypes.bool, intl: PropTypes.object.isRequired, @@ -100,10 +97,6 @@ export default class StatusActionBar extends ImmutablePureComponent { this.props.onMuteConversation(this.props.status); } - handleNotificationDeleteClick = () => { - this.props.onDeleteNotification(this.props.notificationId); - } - render () { const { status, me, intl, withDismiss } = this.props; const reblogDisabled = status.get('visibility') === 'private' || status.get('visibility') === 'direct'; @@ -120,7 +113,6 @@ export default class StatusActionBar extends ImmutablePureComponent { if (withDismiss) { menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick }); - menu.push({ text: intl.formatMessage(messages.deleteNotification), action: this.handleNotificationDeleteClick }); menu.push(null); } diff --git a/app/javascript/glitch/components/status/container.js b/app/javascript/glitch/components/status/container.js index c45b2e0ec..1d572e0e7 100644 --- a/app/javascript/glitch/components/status/container.js +++ b/app/javascript/glitch/components/status/container.js @@ -50,7 +50,6 @@ import { } from '../../../mastodon/actions/statuses'; import { initReport } from '../../../mastodon/actions/reports'; import { openModal } from '../../../mastodon/actions/modal'; -import { deleteNotification } from '../../../mastodon/actions/notifications'; // Our imports // import Status from '.'; @@ -245,10 +244,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ dispatch(muteStatus(status.get('id'))); } }, - - onDeleteNotification (id) { - dispatch(deleteNotification(id)); - }, }); export default injectIntl( diff --git a/app/javascript/glitch/components/status/index.js b/app/javascript/glitch/components/status/index.js index 4a91b5aa3..dc06250ec 100644 --- a/app/javascript/glitch/components/status/index.js +++ b/app/javascript/glitch/components/status/index.js @@ -47,6 +47,7 @@ import StatusContent from './content'; import StatusActionBar from './action_bar'; import StatusGallery from './gallery'; import StatusPlayer from './player'; +import NotificationOverlayContainer from '../notification/overlay/container'; /* * * * */ @@ -158,6 +159,7 @@ export default class Status extends ImmutablePureComponent { status : ImmutablePropTypes.map, account : ImmutablePropTypes.map, settings : ImmutablePropTypes.map, + notification : ImmutablePropTypes.map, me : PropTypes.number, onFavourite : PropTypes.func, onReblog : PropTypes.func, @@ -170,7 +172,6 @@ export default class Status extends ImmutablePureComponent { onReport : PropTypes.func, onOpenMedia : PropTypes.func, onOpenVideo : PropTypes.func, - onDeleteNotification : PropTypes.func, reblogModal : PropTypes.bool, deleteModal : PropTypes.bool, autoPlayGif : PropTypes.bool, @@ -178,7 +179,6 @@ export default class Status extends ImmutablePureComponent { collapse : PropTypes.bool, prepend : PropTypes.string, withDismiss : PropTypes.bool, - notificationId : PropTypes.number, intersectionObserverWrapper : PropTypes.object, }; @@ -186,6 +186,7 @@ export default class Status extends ImmutablePureComponent { isExpanded : null, isIntersecting : true, isHidden : false, + markedForDelete : false, } /* @@ -212,10 +213,12 @@ to remember to specify it here. 'autoPlayGif', 'muted', 'collapse', + 'notification', ] updateOnStates = [ 'isExpanded', + 'markedForDelete', ] /* @@ -523,6 +526,10 @@ applicable. } } + markNotifForDelete = () => { + this.setState({ 'markedForDelete' : !this.state.markedForDelete }); + } + /* #### `render()`. @@ -551,6 +558,7 @@ this operation are further explained in the code below. onOpenVideo, onOpenMedia, autoPlayGif, + notification, ...other } = this.props; const { isExpanded, isIntersecting, isHidden } = this.state; @@ -678,6 +686,8 @@ collapsed. isExpanded === false ? ' collapsed' : '' }${ isExpanded === false && background ? ' has-background' : '' + }${ + this.state.markedForDelete ? ' marked-for-delete' : '' }` } style={{ @@ -689,13 +699,17 @@ collapsed. }} ref={handleRef} > + {notification ? ( + + ) : null} {prepend && account ? ( ) : null} { - this.props.onDeleteNotification(this.props.notificationId); - } - /* #### ``. @@ -159,19 +146,7 @@ the `` inside of an
- {dismiss} ); } diff --git a/app/javascript/glitch/locales/en.json b/app/javascript/glitch/locales/en.json index 80fdc3a39..d202d9c33 100644 --- a/app/javascript/glitch/locales/en.json +++ b/app/javascript/glitch/locales/en.json @@ -28,5 +28,5 @@ "settings.wide_view": "Wide view (Desktop mode only)", "status.collapse": "Collapse", "status.uncollapse": "Uncollapse", - "status.dismiss_notification": "Dismiss notification" + "notification.markForDeletion": "Mark for deletion" } diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index b2a0f7ac3..fca26516a 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -6,7 +6,15 @@ import { defineMessages } from 'react-intl'; export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; -export const NOTIFICATION_DELETE_SUCCESS = 'NOTIFICATION_DELETE_SUCCESS'; +// tracking the notif cleaning request +export const NOTIFICATIONS_DELETE_MARKED_REQUEST = 'NOTIFICATIONS_DELETE_MARKED_REQUEST'; +export const NOTIFICATIONS_DELETE_MARKED_SUCCESS = 'NOTIFICATIONS_DELETE_MARKED_SUCCESS'; +export const NOTIFICATIONS_DELETE_MARKED_FAIL = 'NOTIFICATIONS_DELETE_MARKED_FAIL'; +export const NOTIFICATIONS_ENTER_CLEARING_MODE = 'NOTIFICATIONS_ENTER_CLEARING_MODE'; // arg: yes +// Unmark notifications (when the cleaning mode is left) +export const NOTIFICATIONS_UNMARK_ALL_FOR_DELETE = 'NOTIFICATIONS_UNMARK_ALL_FOR_DELETE'; +// Mark one for delete +export const NOTIFICATION_MARK_FOR_DELETE = 'NOTIFICATION_MARK_FOR_DELETE'; export const NOTIFICATIONS_REFRESH_REQUEST = 'NOTIFICATIONS_REFRESH_REQUEST'; export const NOTIFICATIONS_REFRESH_SUCCESS = 'NOTIFICATIONS_REFRESH_SUCCESS'; @@ -190,17 +198,61 @@ export function scrollTopNotifications(top) { }; }; -export function deleteNotification(id) { +export function deleteMarkedNotifications() { return (dispatch, getState) => { - api(getState).delete(`/api/v1/notifications/${id}`).then(() => { - dispatch(deleteNotificationSuccess(id)); + dispatch(deleteMarkedNotificationsRequest()); + + let ids = []; + getState().getIn(['notifications', 'items']).forEach((n) => { + if (n.get('markedForDelete')) { + ids.push(n.get('id')); + } + }); + + if (ids.length === 0) { + dispatch(enterNotificationClearingMode(false)); + return; + } + + api(getState).delete(`/api/v1/notifications/destroy_multiple?ids[]=${ids.join('&ids[]=')}`).then(() => { + dispatch(deleteMarkedNotificationsSuccess()); + dispatch(expandNotifications()); // Load more (to fill the empty space) + }).catch(error => { + console.error(error); + dispatch(deleteMarkedNotificationsFail(error)); }); }; }; -export function deleteNotificationSuccess(id) { +export function enterNotificationClearingMode(yes) { + return { + type: NOTIFICATIONS_ENTER_CLEARING_MODE, + yes: yes, + }; +}; + +export function deleteMarkedNotificationsRequest() { return { - type: NOTIFICATION_DELETE_SUCCESS, + type: NOTIFICATIONS_DELETE_MARKED_REQUEST, + }; +}; + +export function deleteMarkedNotificationsFail() { + return { + type: NOTIFICATIONS_DELETE_MARKED_FAIL, + }; +}; + +export function markNotificationForDelete(id, yes) { + return { + type: NOTIFICATION_MARK_FOR_DELETE, id: id, + yes: yes, + }; +}; + +export function deleteMarkedNotificationsSuccess() { + return { + type: NOTIFICATIONS_DELETE_MARKED_SUCCESS, }; }; diff --git a/app/javascript/mastodon/components/column.js b/app/javascript/mastodon/components/column.js index 3cbb745c5..0dd31e137 100644 --- a/app/javascript/mastodon/components/column.js +++ b/app/javascript/mastodon/components/column.js @@ -34,7 +34,12 @@ export default class Column extends React.PureComponent { const { children } = this.props; return ( -
+
{children}
); diff --git a/app/javascript/mastodon/components/column_header.js b/app/javascript/mastodon/components/column_header.js index e9f041be6..045904206 100644 --- a/app/javascript/mastodon/components/column_header.js +++ b/app/javascript/mastodon/components/column_header.js @@ -1,8 +1,18 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +// Glitch imports +import NotificationPurgeButtonsContainer from '../../glitch/components/column/notif_cleaning_widget/container'; + +const messages = defineMessages({ + titleNotifClearing: { id: 'column.notifications_clearing', defaultMessage: 'Dismiss selected notifications:' }, + titleNotifClearingShort: { id: 'column.notifications_clearing_short', defaultMessage: 'Dismiss selected:' }, +}); + +@injectIntl export default class ColumnHeader extends React.PureComponent { static contextTypes = { @@ -13,13 +23,17 @@ export default class ColumnHeader extends React.PureComponent { title: PropTypes.node.isRequired, icon: PropTypes.string.isRequired, active: PropTypes.bool, + localSettings : ImmutablePropTypes.map, multiColumn: PropTypes.bool, showBackButton: PropTypes.bool, + notifCleaning: PropTypes.bool, // true only for the notification column + notifCleaningActive: PropTypes.bool, children: PropTypes.node, pinned: PropTypes.bool, onPin: PropTypes.func, onMove: PropTypes.func, onClick: PropTypes.func, + intl: PropTypes.object.isRequired, }; state = { @@ -58,9 +72,16 @@ export default class ColumnHeader extends React.PureComponent { } render () { - const { title, icon, active, children, pinned, onPin, multiColumn, showBackButton } = this.props; + const { intl, icon, active, children, pinned, onPin, multiColumn, showBackButton, notifCleaning, localSettings } = this.props; const { collapsed, animating } = this.state; + let title = this.props.title; + if (notifCleaning && this.props.notifCleaningActive) { + title = intl.formatMessage(localSettings.getIn(['stretch']) ? + messages.titleNotifClearing : + messages.titleNotifClearingShort); + } + const wrapperClassName = classNames('column-header__wrapper', { 'active': active, }); @@ -130,6 +151,7 @@ export default class ColumnHeader extends React.PureComponent { {title}
+ {notifCleaning ? () : null} {backButton} {collapseButton}
diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js index 39fb4b26d..6a262a59e 100644 --- a/app/javascript/mastodon/features/notifications/index.js +++ b/app/javascript/mastodon/features/notifications/index.js @@ -4,7 +4,10 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Column from '../../components/column'; import ColumnHeader from '../../components/column_header'; -import { expandNotifications, scrollTopNotifications } from '../../actions/notifications'; +import { + expandNotifications, + scrollTopNotifications, +} from '../../actions/notifications'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import NotificationContainer from '../../../glitch/components/notification/container'; import { ScrollContainer } from 'react-router-scroll'; @@ -26,9 +29,11 @@ const getNotifications = createSelector([ const mapStateToProps = state => ({ notifications: getNotifications(state), + localSettings: state.get('local_settings'), isLoading: state.getIn(['notifications', 'isLoading'], true), isUnread: state.getIn(['notifications', 'unread']) > 0, hasMore: !!state.getIn(['notifications', 'next']), + notifCleaningActive: state.getIn(['notifications', 'cleaningMode']), }); @connect(mapStateToProps) @@ -45,6 +50,8 @@ export default class Notifications extends React.PureComponent { isUnread: PropTypes.bool, multiColumn: PropTypes.bool, hasMore: PropTypes.bool, + localSettings: ImmutablePropTypes.map, + notifCleaningActive: PropTypes.bool, }; static defaultProps = { @@ -164,7 +171,9 @@ export default class Notifications extends React.PureComponent { this.scrollableArea = scrollableArea; return ( - + diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js index da5fcde84..dd81653d6 100644 --- a/app/javascript/mastodon/reducers/notifications.js +++ b/app/javascript/mastodon/reducers/notifications.js @@ -8,7 +8,11 @@ import { NOTIFICATIONS_EXPAND_FAIL, NOTIFICATIONS_CLEAR, NOTIFICATIONS_SCROLL_TOP, - NOTIFICATION_DELETE_SUCCESS, + NOTIFICATIONS_DELETE_MARKED_REQUEST, + NOTIFICATIONS_DELETE_MARKED_SUCCESS, + NOTIFICATION_MARK_FOR_DELETE, + NOTIFICATIONS_DELETE_MARKED_FAIL, + NOTIFICATIONS_ENTER_CLEARING_MODE, } from '../actions/notifications'; import { ACCOUNT_BLOCK_SUCCESS } from '../actions/accounts'; import { TIMELINE_DELETE } from '../actions/timelines'; @@ -21,12 +25,14 @@ const initialState = ImmutableMap({ unread: 0, loaded: false, isLoading: true, + cleaningMode: false, }); const notificationToMap = notification => ImmutableMap({ id: notification.id, type: notification.type, account: notification.account.id, + markedForDelete: false, status: notification.status ? notification.status.id : null, }); @@ -93,17 +99,34 @@ const deleteByStatus = (state, statusId) => { return state.update('items', list => list.filterNot(item => item.get('status') === statusId)); }; -const deleteById = (state, notificationId) => { - return state.update('items', list => list.filterNot(item => item.get('id') === notificationId)); +const markForDelete = (state, notificationId, yes) => { + return state.update('items', list => list.map(item => { + if(item.get('id') === notificationId) { + return item.set('markedForDelete', yes); + } else { + return item; + } + })); +}; + +const unmarkAllForDelete = (state) => { + return state.update('items', list => list.map(item => item.set('markedForDelete', false))); +}; + +const deleteMarkedNotifs = (state) => { + return state.update('items', list => list.filterNot(item => item.get('markedForDelete'))); }; export default function notifications(state = initialState, action) { switch(action.type) { case NOTIFICATIONS_REFRESH_REQUEST: case NOTIFICATIONS_EXPAND_REQUEST: + case NOTIFICATIONS_DELETE_MARKED_REQUEST: + return state.set('isLoading', true); + case NOTIFICATIONS_DELETE_MARKED_FAIL: case NOTIFICATIONS_REFRESH_FAIL: case NOTIFICATIONS_EXPAND_FAIL: - return state.set('isLoading', true); + return state.set('isLoading', false); case NOTIFICATIONS_SCROLL_TOP: return updateTop(state, action.top); case NOTIFICATIONS_UPDATE: @@ -118,8 +141,15 @@ export default function notifications(state = initialState, action) { return state.set('items', ImmutableList()).set('next', null); case TIMELINE_DELETE: return deleteByStatus(state, action.id); - case NOTIFICATION_DELETE_SUCCESS: - return deleteById(state, action.id); + case NOTIFICATION_MARK_FOR_DELETE: + return markForDelete(state, action.id, action.yes); + case NOTIFICATIONS_DELETE_MARKED_SUCCESS: + return deleteMarkedNotifs(state).set('isLoading', false).set('cleaningMode', false); + case NOTIFICATIONS_ENTER_CLEARING_MODE: + const st = state.set('cleaningMode', action.yes); + if (!action.yes) + return unmarkAllForDelete(st); + else return st; default: return state; } diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index 0cd082985..dbdf286a9 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -451,6 +451,63 @@ cursor: pointer; } +.notification__dismiss-overlay { + position: absolute; + left: 0; top: 0; right: 0; bottom: 0; + + $c1: #00000A; + $c2: #222228; + background: linear-gradient(to right, + rgba($c1, 0.1), + rgba($c1, 0.2) 60%, + rgba($c2, 1) 90%, + rgba($c2, 1)); + + z-index: 999; + align-items: center; + justify-content: flex-end; + cursor: pointer; + + display: none; + + &.show { + display: flex; + } + + // make it brighter + &.active { + $c: #222931; + background: linear-gradient(to right, + rgba($c, 0.1), + rgba($c, 0.2) 60%, + rgba($c, 1) 90%, + rgba($c, 1)); + } + + &:focus { + outline: 0 !important; + } +} + +.notification__dismiss-overlay__ckbox { + border: 2px solid #9baec8; + border-radius: 2px; + width: 30px; + height: 30px; + margin-right: 20px; + font-size: 20px; + color: #c3dcfd; + text-shadow: 0 0 5px black; + display: flex; + justify-content: center; + align-items: center; + + :focus & { + outline: rgb(77, 144, 254) auto 10px; + outline: -webkit-focus-ring-color auto 10px; + } +} + // --- Extra clickable area in the status gutter --- .ui.wide { @mixin xtraspaces-full { @@ -627,24 +684,14 @@ position: absolute; } -.status__prepend-dismiss-button { - border: 0; - background: transparent; - position: absolute; - right: -3px; - opacity: 0; - transition: opacity 0.1s ease-in-out; - - i.fa { - color: crimson; - } +.notification-follow { + position: relative; - .notification__message:hover & { - opacity: 1; - } + // same like Status + border-bottom: 1px solid lighten($ui-base-color, 8%); - .notification-follow & { - right: 6px; + .account { + border-bottom: 0 none; } } @@ -2408,6 +2455,17 @@ button.icon-button.active i.fa-retweet { } } +.column-header__notif-cleaning-buttons { + display: flex; + align-items: stretch; + + button { + @extend .column-header__button; + padding-left: 12px; + padding-right: 12px; + } +} + .column-header__collapsible { max-height: 70vh; overflow: hidden; diff --git a/config/routes.rb b/config/routes.rb index fb2051aad..ac505edc6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -182,6 +182,7 @@ Rails.application.routes.draw do collection do post :clear post :dismiss + delete :destroy_multiple end end -- cgit From b61e3daf983d87c6d2de7e54d420c2e8f5a531e6 Mon Sep 17 00:00:00 2001 From: Gô Shoemake Date: Sun, 30 Jul 2017 10:28:21 -0700 Subject: Multiple frontend support (#110) * Initial multiple frontend support * Removed unnecessary require() * Moved styles/images out of common --- app/controllers/home_controller.rb | 1 + app/javascript/packs/application.js | 6 ++++++ app/javascript/packs/common.js | 7 ++----- app/javascript/packs/frontends/mastodon.js | 16 ++++++++++++++++ app/javascript/styles/application.scss | 3 --- app/javascript/styles/common.scss | 5 +++++ app/views/home/index.html.haml | 4 ++-- app/views/layouts/application.html.haml | 3 +++ config/initializers/frontends.rb | 7 +++++++ config/webpack/shared.js | 15 ++------------- 10 files changed, 44 insertions(+), 23 deletions(-) create mode 100644 app/javascript/packs/frontends/mastodon.js create mode 100644 app/javascript/styles/common.scss create mode 100644 config/initializers/frontends.rb (limited to 'config') diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 1585bc810..fbfb5473e 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -6,6 +6,7 @@ class HomeController < ApplicationController def index @body_classes = 'app-body' + @frontend = (params[:frontend] and Rails.configuration.x.available_frontends.include? params[:frontend] + '.js') ? params[:frontend] : 'mastodon' end private diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 116632dea..c06714dc1 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -1,5 +1,11 @@ import loadPolyfills from '../mastodon/load_polyfills'; +// import default stylesheet with variables +require('font-awesome/css/font-awesome.css'); +require('mastodon-application-style'); + +require.context('../images/', true); + loadPolyfills().then(() => { require('../mastodon/main').default(); }).catch(e => { diff --git a/app/javascript/packs/common.js b/app/javascript/packs/common.js index ba7053f1f..de0c68fa5 100644 --- a/app/javascript/packs/common.js +++ b/app/javascript/packs/common.js @@ -1,9 +1,6 @@ import { start } from 'rails-ujs'; -// import default stylesheet with variables -require('font-awesome/css/font-awesome.css'); -require('mastodon-application-style'); - -require.context('../images/', true); +// import common styling +require('../styles/common.scss'); start(); diff --git a/app/javascript/packs/frontends/mastodon.js b/app/javascript/packs/frontends/mastodon.js new file mode 100644 index 000000000..a983de36f --- /dev/null +++ b/app/javascript/packs/frontends/mastodon.js @@ -0,0 +1,16 @@ +// This file replaces `app/javascript/packs/application.js` for use +// with multiple frontends. + +import loadPolyfills from '../../mastodon/load_polyfills'; + +// import default stylesheet with variables +require('font-awesome/css/font-awesome.css'); +require('mastodon-application-style'); + +require.context('../../images/', true); + +loadPolyfills().then(() => { + require('../../mastodon/main').default(); +}).catch(e => { + console.error(e); +}); diff --git a/app/javascript/styles/application.scss b/app/javascript/styles/application.scss index b08b69449..33c7783f3 100644 --- a/app/javascript/styles/application.scss +++ b/app/javascript/styles/application.scss @@ -1,9 +1,6 @@ @import 'mixins'; @import 'variables'; @import 'variables-glitch'; -@import 'fonts/roboto'; -@import 'fonts/roboto-mono'; -@import 'fonts/montserrat'; @import 'reset'; @import 'basics'; diff --git a/app/javascript/styles/common.scss b/app/javascript/styles/common.scss new file mode 100644 index 000000000..c1772e7ae --- /dev/null +++ b/app/javascript/styles/common.scss @@ -0,0 +1,5 @@ +// This makes our fonts available everywhere. + +@import 'fonts/roboto'; +@import 'fonts/roboto-mono'; +@import 'fonts/montserrat'; diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 1ed5c1ae0..ec6e53461 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -2,8 +2,8 @@ %meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key} %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json) - = javascript_pack_tag 'application', integrity: true, crossorigin: 'anonymous' - = stylesheet_pack_tag 'application', media: 'all' + = javascript_pack_tag "frontends/#{@frontend}", integrity: true, crossorigin: 'anonymous' + = stylesheet_pack_tag "frontends/#{@frontend}", integrity: true, media: 'all' .app-holder#mastodon{ data: { props: Oj.dump(default_props) } } %noscript diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 82b20810a..399d70bc0 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -32,6 +32,9 @@ = javascript_pack_tag "locale_#{I18n.locale}", integrity: true, crossorigin: 'anonymous' = csrf_meta_tags + - if controller_name != 'home' + = stylesheet_pack_tag 'application', integrity: true, media: 'all' + = yield :header_tags - body_classes ||= @body_classes diff --git a/config/initializers/frontends.rb b/config/initializers/frontends.rb new file mode 100644 index 000000000..2cb68cc61 --- /dev/null +++ b/config/initializers/frontends.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +Rails.application.configure do + frontends = [] + Rails.root.join('app', 'javascript', 'packs', 'frontends').each_child(false) { |f| frontends.push f.to_s } + config.x.available_frontends = frontends +end diff --git a/config/webpack/shared.js b/config/webpack/shared.js index 98e864a66..425918d66 100644 --- a/config/webpack/shared.js +++ b/config/webpack/shared.js @@ -2,7 +2,7 @@ const { existsSync } = require('fs'); const webpack = require('webpack'); -const { basename, dirname, join, relative, resolve, sep } = require('path'); +const { basename, dirname, join, relative, resolve } = require('path'); const { sync } = require('glob'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const ManifestPlugin = require('webpack-manifest-plugin'); @@ -54,18 +54,7 @@ module.exports = { }), new webpack.optimize.CommonsChunkPlugin({ name: 'common', - minChunks: (module, count) => { - const reactIntlPathRegexp = new RegExp(`node_modules\\${sep}react-intl`); - - if (module.resource && reactIntlPathRegexp.test(module.resource)) { - // skip react-intl because it's useless to put in the common chunk, - // e.g. because "shared" modules between zh-TW and zh-CN will never - // be loaded together - return false; - } - - return count >= 2; - }, + minChunks: Infinity, // It doesn't make sense to use common chunks with multiple frontend support. }), ], -- cgit From 70592cdaba18f97bc4ba492445b1800033c58619 Mon Sep 17 00:00:00 2001 From: Surinna Curtis Date: Sat, 2 Sep 2017 12:24:58 -0500 Subject: Add a /api/v1/mutes/details route that just returns the array of mutes. --- app/controllers/api/v1/mutes_controller.rb | 27 ++++++++++++++++++------ config/routes.rb | 6 +++++- spec/controllers/api/v1/mutes_controller_spec.rb | 8 +++++++ 3 files changed, 33 insertions(+), 8 deletions(-) (limited to 'config') diff --git a/app/controllers/api/v1/mutes_controller.rb b/app/controllers/api/v1/mutes_controller.rb index 0c43cb943..2118b4c40 100644 --- a/app/controllers/api/v1/mutes_controller.rb +++ b/app/controllers/api/v1/mutes_controller.rb @@ -8,10 +8,15 @@ class Api::V1::MutesController < Api::BaseController respond_to :json def index - @accounts = load_accounts + @data = @accounts = load_accounts render json: @accounts, each_serializer: REST::AccountSerializer end + def details + @data = @mutes = paginated_mutes + render json: @mutes + end + private def load_accounts @@ -36,26 +41,34 @@ class Api::V1::MutesController < Api::BaseController def next_path if records_continue? - api_v1_mutes_url pagination_params(max_id: pagination_max_id) + url_for pagination_params(max_id: pagination_max_id) end end def prev_path - unless @accounts.empty? - api_v1_mutes_url pagination_params(since_id: pagination_since_id) + unless@data.empty? + url_for pagination_params(since_id: pagination_since_id) end end def pagination_max_id - @accounts.last.muted_by_ids.last + if params[:action] == "details" + @mutes.last.id + else + @accounts.last.muted_by_ids.last + end end def pagination_since_id - @accounts.first.muted_by_ids.first + if params[:action] == "details" + @mutes.first.id + else + @accounts.first.muted_by_ids.first + end end def records_continue? - @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) + @data.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) end def pagination_params(core_params) diff --git a/config/routes.rb b/config/routes.rb index 2ff7e890a..dc93fc6fe 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -191,7 +191,11 @@ Rails.application.routes.draw do resources :media, only: [:create] resources :apps, only: [:create] resources :blocks, only: [:index] - resources :mutes, only: [:index] + resources :mutes, only: [:index] do + collection do + get 'details' + end + end resources :favourites, only: [:index] resources :reports, only: [:index, :create] diff --git a/spec/controllers/api/v1/mutes_controller_spec.rb b/spec/controllers/api/v1/mutes_controller_spec.rb index 3e6fa887b..9da83236a 100644 --- a/spec/controllers/api/v1/mutes_controller_spec.rb +++ b/spec/controllers/api/v1/mutes_controller_spec.rb @@ -18,4 +18,12 @@ RSpec.describe Api::V1::MutesController, type: :controller do expect(response).to have_http_status(:success) end end + + describe 'GET #details' do + it 'returns http success' do + get :details, params: { limit: 1 } + + expect(response).to have_http_status(:success) + end + end end -- cgit From 86e617a839bd4eb45ace52ab226a4e93bb184ff0 Mon Sep 17 00:00:00 2001 From: kibigo! Date: Mon, 25 Sep 2017 19:24:32 -0700 Subject: Better themeing support!! --- app/controllers/application_controller.rb | 6 ++++ app/controllers/settings/preferences_controller.rb | 1 + .../local_settings/navigation/item/style.scss | 2 +- .../local_settings/navigation/style.scss | 2 +- .../components/local_settings/page/item/style.scss | 2 +- .../components/local_settings/page/style.scss | 2 +- .../glitch/components/local_settings/style.scss | 2 +- app/javascript/packs/application.js | 2 +- app/javascript/packs/frontends/mastodon.js | 16 ---------- app/javascript/styles/custom.scss | 1 - app/javascript/themes/default/theme.yml | 9 ++++++ app/javascript/themes/spin/pack.js | 2 ++ app/javascript/themes/spin/style.scss | 14 ++++++++ app/javascript/themes/spin/theme.yml | 2 ++ app/lib/themes.rb | 23 ++++++++++++++ app/lib/user_settings_decorator.rb | 5 +++ app/models/user.rb | 4 +++ app/views/home/index.html.haml | 4 +-- app/views/settings/preferences/show.html.haml | 2 ++ config/initializers/frontends.rb | 7 ---- config/locales/simple_form.en.yml | 2 ++ config/settings.yml | 1 + config/webpack/configuration.js | 17 +++++++++- config/webpack/loaders/sass.js | 2 +- config/webpack/shared.js | 37 ++++++++++++---------- 25 files changed, 117 insertions(+), 50 deletions(-) delete mode 100644 app/javascript/packs/frontends/mastodon.js delete mode 100644 app/javascript/styles/custom.scss create mode 100644 app/javascript/themes/default/theme.yml create mode 100644 app/javascript/themes/spin/pack.js create mode 100644 app/javascript/themes/spin/style.scss create mode 100644 app/javascript/themes/spin/theme.yml create mode 100644 app/lib/themes.rb delete mode 100644 config/initializers/frontends.rb (limited to 'config') diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0b40fb05b..d5eca6ffb 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -12,6 +12,7 @@ class ApplicationController < ActionController::Base helper_method :current_account helper_method :current_session + helper_method :current_theme helper_method :single_user_mode? rescue_from ActionController::RoutingError, with: :not_found @@ -77,6 +78,11 @@ class ApplicationController < ActionController::Base @current_session ||= SessionActivation.find_by(session_id: cookies.signed['_session_id']) end + def current_theme + return Setting.default_settings['theme'] unless Themes.instance.names.include? current_user&.setting_theme + current_user.setting_theme + end + def cache_collection(raw, klass) return raw unless klass.respond_to?(:with_includes) diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index f107f2b16..207c7b324 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -41,6 +41,7 @@ class Settings::PreferencesController < ApplicationController :setting_auto_play_gif, :setting_system_font_ui, :setting_noindex, + :setting_theme, notification_emails: %i(follow follow_request reblog favourite mention digest), interactions: %i(must_be_follower must_be_following) ) diff --git a/app/javascript/glitch/components/local_settings/navigation/item/style.scss b/app/javascript/glitch/components/local_settings/navigation/item/style.scss index 505c86912..33d7d3744 100644 --- a/app/javascript/glitch/components/local_settings/navigation/item/style.scss +++ b/app/javascript/glitch/components/local_settings/navigation/item/style.scss @@ -1,4 +1,4 @@ -@import 'variables'; +@import 'styles/variables'; .glitch.local-settings__navigation__item { display: block; diff --git a/app/javascript/glitch/components/local_settings/navigation/style.scss b/app/javascript/glitch/components/local_settings/navigation/style.scss index 1cc39e3e9..a610a1212 100644 --- a/app/javascript/glitch/components/local_settings/navigation/style.scss +++ b/app/javascript/glitch/components/local_settings/navigation/style.scss @@ -1,4 +1,4 @@ -@import 'variables'; +@import 'styles/variables'; .glitch.local-settings__navigation { background: $primary-text-color; diff --git a/app/javascript/glitch/components/local_settings/page/item/style.scss b/app/javascript/glitch/components/local_settings/page/item/style.scss index e614030c0..da1941b99 100644 --- a/app/javascript/glitch/components/local_settings/page/item/style.scss +++ b/app/javascript/glitch/components/local_settings/page/item/style.scss @@ -1,4 +1,4 @@ -@import 'variables'; +@import 'styles/variables'; .glitch.local-settings__page__item { select { diff --git a/app/javascript/glitch/components/local_settings/page/style.scss b/app/javascript/glitch/components/local_settings/page/style.scss index 7269056c3..53c95ea40 100644 --- a/app/javascript/glitch/components/local_settings/page/style.scss +++ b/app/javascript/glitch/components/local_settings/page/style.scss @@ -1,4 +1,4 @@ -@import 'variables'; +@import 'styles/variables'; .glitch.local-settings__page { display: block; diff --git a/app/javascript/glitch/components/local_settings/style.scss b/app/javascript/glitch/components/local_settings/style.scss index 6f7fcbaa4..54fec47bd 100644 --- a/app/javascript/glitch/components/local_settings/style.scss +++ b/app/javascript/glitch/components/local_settings/style.scss @@ -1,4 +1,4 @@ -@import 'variables'; +@import 'styles/variables'; .glitch.local-settings { position: relative; diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index c06714dc1..aa94006c6 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -2,7 +2,7 @@ import loadPolyfills from '../mastodon/load_polyfills'; // import default stylesheet with variables require('font-awesome/css/font-awesome.css'); -require('mastodon-application-style'); +import 'styles/application'; require.context('../images/', true); diff --git a/app/javascript/packs/frontends/mastodon.js b/app/javascript/packs/frontends/mastodon.js deleted file mode 100644 index a983de36f..000000000 --- a/app/javascript/packs/frontends/mastodon.js +++ /dev/null @@ -1,16 +0,0 @@ -// This file replaces `app/javascript/packs/application.js` for use -// with multiple frontends. - -import loadPolyfills from '../../mastodon/load_polyfills'; - -// import default stylesheet with variables -require('font-awesome/css/font-awesome.css'); -require('mastodon-application-style'); - -require.context('../../images/', true); - -loadPolyfills().then(() => { - require('../../mastodon/main').default(); -}).catch(e => { - console.error(e); -}); diff --git a/app/javascript/styles/custom.scss b/app/javascript/styles/custom.scss deleted file mode 100644 index 97a981243..000000000 --- a/app/javascript/styles/custom.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'application'; diff --git a/app/javascript/themes/default/theme.yml b/app/javascript/themes/default/theme.yml new file mode 100644 index 000000000..6a7a872b4 --- /dev/null +++ b/app/javascript/themes/default/theme.yml @@ -0,0 +1,9 @@ +# (REQUIRED) Name must be unique across all installed themes. +name: default + +# (REQUIRED) The location of the pack file inside `pack_directory`. +pack: application.js + +# (OPTIONAL) The directory which contains the pack file. +# Defaults to the theme directory (`app/javascript/themes/[theme]`). +pack_directory: app/javascript/packs diff --git a/app/javascript/themes/spin/pack.js b/app/javascript/themes/spin/pack.js new file mode 100644 index 000000000..dab0e93a4 --- /dev/null +++ b/app/javascript/themes/spin/pack.js @@ -0,0 +1,2 @@ +import 'packs/application'; +import 'themes/spin/style'; diff --git a/app/javascript/themes/spin/style.scss b/app/javascript/themes/spin/style.scss new file mode 100644 index 000000000..1a9381fd0 --- /dev/null +++ b/app/javascript/themes/spin/style.scss @@ -0,0 +1,14 @@ +:root:root:root { + .button, .icon-button, .emoji-button, .account__avatar, .account__avatar-overlay { + animation: spin 4s linear infinite; + } +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/app/javascript/themes/spin/theme.yml b/app/javascript/themes/spin/theme.yml new file mode 100644 index 000000000..a684997dc --- /dev/null +++ b/app/javascript/themes/spin/theme.yml @@ -0,0 +1,2 @@ +name: spin +pack: pack.js \ No newline at end of file diff --git a/app/lib/themes.rb b/app/lib/themes.rb new file mode 100644 index 000000000..2dd188297 --- /dev/null +++ b/app/lib/themes.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'singleton' +require 'yaml' + +class Themes + include Singleton + + def initialize + result = Hash.new + Dir.glob(Rails.root.join('app', 'javascript', 'themes', '*', 'theme.yml')) do |path| + data = YAML.load_file(path) + if data['pack'] && data['name'] + result[data['name']] = data + end + end + @conf = result + end + + def names + @conf.keys + end +end diff --git a/app/lib/user_settings_decorator.rb b/app/lib/user_settings_decorator.rb index 62046ed72..3b156b98c 100644 --- a/app/lib/user_settings_decorator.rb +++ b/app/lib/user_settings_decorator.rb @@ -25,6 +25,7 @@ class UserSettingsDecorator user.settings['auto_play_gif'] = auto_play_gif_preference user.settings['system_font_ui'] = system_font_ui_preference user.settings['noindex'] = noindex_preference + user.settings['theme'] = theme_preference end def merged_notification_emails @@ -67,6 +68,10 @@ class UserSettingsDecorator boolean_cast_setting 'setting_noindex' end + def theme_preference + settings['setting_theme'] + end + def boolean_cast_setting(key) settings[key] == '1' end diff --git a/app/models/user.rb b/app/models/user.rb index 5e548c1ef..3bf069a31 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -110,6 +110,10 @@ class User < ApplicationRecord settings.noindex end + def setting_theme + settings.theme + end + def token_for_app(a) return nil if a.nil? || a.owner != self Doorkeeper::AccessToken diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index a13d0702b..3b4219c56 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -2,8 +2,8 @@ %meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key} %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json) - = javascript_pack_tag "frontends/#{@frontend}", integrity: true, crossorigin: 'anonymous' - = stylesheet_pack_tag "frontends/#{@frontend}", integrity: true, media: 'all' + = javascript_pack_tag "themes/#{current_theme}", integrity: true, crossorigin: 'anonymous' + = stylesheet_pack_tag "themes/#{current_theme}", integrity: true, media: 'all' .app-holder#mastodon{ data: { props: Oj.dump(default_props) } } %noscript diff --git a/app/views/settings/preferences/show.html.haml b/app/views/settings/preferences/show.html.haml index f42f92508..5efd538e4 100644 --- a/app/views/settings/preferences/show.html.haml +++ b/app/views/settings/preferences/show.html.haml @@ -5,6 +5,8 @@ = render 'shared/error_messages', object: current_user .fields-group + = f.input :setting_theme, collection: Themes.instance.names, label_method: lambda { |theme| safe_join([I18n.t("themes.#{theme}", default: theme)])}, wrapper: :with_label, include_blank: false + = f.input :locale, collection: I18n.available_locales, wrapper: :with_label, diff --git a/config/initializers/frontends.rb b/config/initializers/frontends.rb deleted file mode 100644 index 2cb68cc61..000000000 --- a/config/initializers/frontends.rb +++ /dev/null @@ -1,7 +0,0 @@ -# Be sure to restart your server when you modify this file. - -Rails.application.configure do - frontends = [] - Rails.root.join('app', 'javascript', 'packs', 'frontends').each_child(false) { |f| frontends.push f.to_s } - config.x.available_frontends = frontends -end diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index fb8524a24..f9d4e2e52 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -13,6 +13,7 @@ en: one: 1 character left other: %{count} characters left setting_noindex: Affects your public profile and status pages + setting_theme: Affects how Mastodon looks when you're logged in from any device. imports: data: CSV file exported from another Mastodon instance sessions: @@ -44,6 +45,7 @@ en: setting_noindex: Opt-out of search engine indexing setting_system_font_ui: Use system's default font setting_unfollow_modal: Show confirmation dialog before unfollowing someone + setting_theme: Site theme severity: Severity type: Import type username: Username diff --git a/config/settings.yml b/config/settings.yml index 39dfb8f55..3cd3307f4 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -24,6 +24,7 @@ defaults: &defaults auto_play_gif: false system_font_ui: false noindex: false + theme: 'default' notification_emails: follow: false reblog: false diff --git a/config/webpack/configuration.js b/config/webpack/configuration.js index 6ef484c3a..606eb97f1 100644 --- a/config/webpack/configuration.js +++ b/config/webpack/configuration.js @@ -1,13 +1,27 @@ // Common configuration for webpacker loaded from config/webpacker.yml -const { join, resolve } = require('path'); +const { dirname, join, resolve } = require('path'); const { env } = require('process'); const { safeLoad } = require('js-yaml'); const { readFileSync } = require('fs'); +const glob = require('glob'); const configPath = resolve('config', 'webpacker.yml'); const loadersDir = join(__dirname, 'loaders'); const settings = safeLoad(readFileSync(configPath), 'utf8')[env.NODE_ENV]; +const themeFiles = glob.sync('app/javascript/themes/*/theme.yml'); +const themes = {}; + +for (let i = 0; i < themeFiles.length; i++) { + const themeFile = themeFiles[i]; + const data = safeLoad(readFileSync(themeFile), 'utf8'); + if (!data.pack_directory) { + data.pack_directory = dirname(themeFile); + } + if (data.name && data.pack) { + themes[data.name] = data; + } +} function removeOuterSlashes(string) { return string.replace(/^\/*/, '').replace(/\/*$/, ''); @@ -29,6 +43,7 @@ const output = { module.exports = { settings, + themes, env, loadersDir, output, diff --git a/config/webpack/loaders/sass.js b/config/webpack/loaders/sass.js index 40e81b43b..96ad7abe8 100644 --- a/config/webpack/loaders/sass.js +++ b/config/webpack/loaders/sass.js @@ -9,7 +9,7 @@ module.exports = { { loader: 'css-loader', options: { minimize: env.NODE_ENV === 'production' } }, { loader: 'postcss-loader', options: { sourceMap: true } }, 'resolve-url-loader', - { loader: 'sass-loader', options: { includePaths: ['app/javascript/styles'] } }, + { loader: 'sass-loader', options: { includePaths: ['app/javascript'] } }, ], }), }; diff --git a/config/webpack/shared.js b/config/webpack/shared.js index be1b49421..ab925b020 100644 --- a/config/webpack/shared.js +++ b/config/webpack/shared.js @@ -1,13 +1,12 @@ // Note: You must restart bin/webpack-dev-server for changes to take effect -const { existsSync } = require('fs'); const webpack = require('webpack'); const { basename, dirname, join, relative, resolve } = require('path'); const { sync } = require('glob'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const ManifestPlugin = require('webpack-manifest-plugin'); const extname = require('path-complete-extname'); -const { env, settings, output, loadersDir } = require('./configuration.js'); +const { env, settings, themes, output, loadersDir } = require('./configuration.js'); const localePackPaths = require('./generateLocalePacks'); const extensionGlob = `**/*{${settings.extensions.join(',')}}*`; @@ -18,17 +17,27 @@ const entryPacks = [...packPaths, ...localePackPaths].filter(path => path !== jo const customApplicationStyle = resolve(join(settings.source_path, 'styles/custom.scss')); const originalApplicationStyle = resolve(join(settings.source_path, 'styles/application.scss')); +const themePaths = Object.keys(themes).reduce( + (themePaths, name) => { + themeData = themes[name]; + themePaths[`themes/${name}`] = resolve(themeData.pack_directory, themeData.pack); + return themePaths; + }, {} +); + module.exports = { - entry: entryPacks.reduce( - (map, entry) => { - const localMap = map; - let namespace = relative(join(entryPath), dirname(entry)); - if (namespace === join('..', '..', '..', 'tmp', 'packs')) { - namespace = ''; // generated by generateLocalePacks.js - } - localMap[join(namespace, basename(entry, extname(entry)))] = resolve(entry); - return localMap; - }, {} + entry: Object.assign( + entryPacks.reduce( + (map, entry) => { + const localMap = map; + let namespace = relative(join(entryPath), dirname(entry)); + if (namespace === join('..', '..', '..', 'tmp', 'packs')) { + namespace = ''; // generated by generateLocalePacks.js + } + localMap[join(namespace, basename(entry, extname(entry)))] = resolve(entry); + return localMap; + }, {} + ), themePaths ), output: { @@ -59,10 +68,6 @@ module.exports = { ], resolve: { - alias: { - 'mastodon-application-style': existsSync(customApplicationStyle) ? - customApplicationStyle : originalApplicationStyle, - }, extensions: settings.extensions, modules: [ resolve(settings.source_path), -- cgit From f7ca205f382a9d528bf37e3ec38ee8bf77dccccf Mon Sep 17 00:00:00 2001 From: kibigo! Date: Mon, 25 Sep 2017 19:35:54 -0700 Subject: Fixed webpack config code --- config/webpack/shared.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'config') diff --git a/config/webpack/shared.js b/config/webpack/shared.js index ab925b020..99f4dec1a 100644 --- a/config/webpack/shared.js +++ b/config/webpack/shared.js @@ -14,12 +14,9 @@ const entryPath = join(settings.source_path, settings.source_entry_path); const packPaths = sync(join(entryPath, extensionGlob)); const entryPacks = [...packPaths, ...localePackPaths].filter(path => path !== join(entryPath, 'custom.js')); -const customApplicationStyle = resolve(join(settings.source_path, 'styles/custom.scss')); -const originalApplicationStyle = resolve(join(settings.source_path, 'styles/application.scss')); - const themePaths = Object.keys(themes).reduce( (themePaths, name) => { - themeData = themes[name]; + const themeData = themes[name]; themePaths[`themes/${name}`] = resolve(themeData.pack_directory, themeData.pack); return themePaths; }, {} -- cgit From 96ba3482b96504d7a8e2a7dc7dbfea41c86be74f Mon Sep 17 00:00:00 2001 From: DJ Sundog Date: Sat, 7 Oct 2017 19:54:10 +0000 Subject: adding support for audio uploads, transcoded to mp4 videos --- app/models/media_attachment.rb | 28 ++++++++++++++++++++++++---- app/serializers/initial_state_serializer.rb | 2 +- config/application.rb | 1 + lib/paperclip/audio_transcoder.rb | 15 +++++++++++++++ 4 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 lib/paperclip/audio_transcoder.rb (limited to 'config') diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index e4a974f96..93ff43d58 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -23,15 +23,31 @@ require 'mime/types' class MediaAttachment < ApplicationRecord self.inheritance_column = nil - enum type: [:image, :gifv, :video, :unknown] + enum type: [:image, :gifv, :video, :audio, :unknown] IMAGE_FILE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif'].freeze VIDEO_FILE_EXTENSIONS = ['.webm', '.mp4', '.m4v'].freeze + AUDIO_FILE_EXTENSIONS = ['.mp3', '.m4a', '.wav', '.ogg'].freeze IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze VIDEO_MIME_TYPES = ['video/webm', 'video/mp4'].freeze + AUDIO_MIME_TYPES = ['audio/mpeg', 'audio/mp4', 'audio/vnd.wav', 'audio/wav', 'audio/x-wav', 'audio/x-wave', 'audio/ogg',].freeze IMAGE_STYLES = { original: '1280x1280>', small: '400x400>' }.freeze + AUDIO_STYLES = { + original: { + format: 'mp4', + convert_options: { + output: { + filter_complex: '"[0:a]compand,showwaves=s=640x360:mode=line,format=yuv420p[v]"', + map: '"[v]" -map 0:a', + threads: 2, + vcodec: 'libx264', + acodec: 'aac', + }, + }, + }, + }.freeze VIDEO_STYLES = { small: { convert_options: { @@ -54,7 +70,7 @@ class MediaAttachment < ApplicationRecord include Remotable - validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES + validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES + AUDIO_MIME_TYPES validates_attachment_size :file, less_than: 8.megabytes validates :account, presence: true @@ -107,6 +123,8 @@ class MediaAttachment < ApplicationRecord } elsif IMAGE_MIME_TYPES.include? f.instance.file_content_type IMAGE_STYLES + elsif AUDIO_MIME_TYPES.include? f.instance.file_content_type + AUDIO_STYLES else VIDEO_STYLES end @@ -117,6 +135,8 @@ class MediaAttachment < ApplicationRecord [:gif_transcoder] elsif VIDEO_MIME_TYPES.include? f.file_content_type [:video_transcoder] + elsif AUDIO_MIME_TYPES.include? f.file_content_type + [:audio_transcoder] else [:thumbnail] end @@ -137,8 +157,8 @@ class MediaAttachment < ApplicationRecord end def set_type_and_extension - self.type = VIDEO_MIME_TYPES.include?(file_content_type) ? :video : :image - extension = appropriate_extension + self.type = VIDEO_MIME_TYPES.include?(file_content_type) ? :video : AUDIO_MIME_TYPES.include?(file_content_type) ? :audio : :image + extension = AUDIO_MIME_TYPES.include?(file_content_type) ? '.mp4' : appropriate_extension basename = Paperclip::Interpolations.basename(file, :original) file.instance_write :file_name, [basename, extension].delete_if(&:blank?).join('.') end diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index e2f15a601..0992771fc 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -52,6 +52,6 @@ class InitialStateSerializer < ActiveModel::Serializer end def media_attachments - { accept_content_types: MediaAttachment::IMAGE_FILE_EXTENSIONS + MediaAttachment::VIDEO_FILE_EXTENSIONS + MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES } + { accept_content_types: MediaAttachment::IMAGE_FILE_EXTENSIONS + MediaAttachment::VIDEO_FILE_EXTENSIONS + MediaAttachment::AUDIO_FILE_EXTENSIONS + MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES + MediaAttachment::AUDIO_MIME_TYPES } end end diff --git a/config/application.rb b/config/application.rb index eb89f0a10..db53b8c84 100644 --- a/config/application.rb +++ b/config/application.rb @@ -9,6 +9,7 @@ Bundler.require(*Rails.groups) require_relative '../app/lib/exceptions' require_relative '../lib/paperclip/gif_transcoder' require_relative '../lib/paperclip/video_transcoder' +require_relative '../lib/paperclip/audio_transcoder' require_relative '../lib/mastodon/version' Dotenv::Railtie.load diff --git a/lib/paperclip/audio_transcoder.rb b/lib/paperclip/audio_transcoder.rb new file mode 100644 index 000000000..484d694d7 --- /dev/null +++ b/lib/paperclip/audio_transcoder.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Paperclip + class AudioTranscoder < Paperclip::Processor + def make + final_file = Paperclip::Transcoder.make(file, options, attachment) + + attachment.instance.file_file_name = 'media.mp4' + attachment.instance.file_content_type = 'video/mp4' + attachment.instance.type = MediaAttachment.types[:video] + + final_file + end + end +end -- cgit From 4fa2f7e82d68c974ecfdb8896f15a5a3aba25828 Mon Sep 17 00:00:00 2001 From: David Yip Date: Sun, 15 Oct 2017 03:17:33 -0500 Subject: Set up /settings/keyword_mutes. #164. This should eventually be accessible via the API and the web frontend, but I find it easier to set up an editing interface using Rails templates and the like. We can always take it out if it turns out we don't need it. --- app/controllers/settings/keyword_mutes_controller.rb | 7 +++++++ app/helpers/settings/keyword_mutes_helper.rb | 2 ++ app/views/settings/keyword_mutes/index.html.haml | 2 ++ config/locales/en.yml | 1 + config/navigation.rb | 1 + config/routes.rb | 1 + .../controllers/settings/keyword_mutes_controller_spec.rb | 5 +++++ spec/helpers/settings/keyword_mutes_helper_spec.rb | 15 +++++++++++++++ 8 files changed, 34 insertions(+) create mode 100644 app/controllers/settings/keyword_mutes_controller.rb create mode 100644 app/helpers/settings/keyword_mutes_helper.rb create mode 100644 app/views/settings/keyword_mutes/index.html.haml create mode 100644 spec/controllers/settings/keyword_mutes_controller_spec.rb create mode 100644 spec/helpers/settings/keyword_mutes_helper_spec.rb (limited to 'config') diff --git a/app/controllers/settings/keyword_mutes_controller.rb b/app/controllers/settings/keyword_mutes_controller.rb new file mode 100644 index 000000000..ffe94e33a --- /dev/null +++ b/app/controllers/settings/keyword_mutes_controller.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Settings::KeywordMutesController < ApplicationController + layout 'admin' + + before_action :authenticate_user! +end diff --git a/app/helpers/settings/keyword_mutes_helper.rb b/app/helpers/settings/keyword_mutes_helper.rb new file mode 100644 index 000000000..7b98cd59e --- /dev/null +++ b/app/helpers/settings/keyword_mutes_helper.rb @@ -0,0 +1,2 @@ +module Settings::KeywordMutesHelper +end diff --git a/app/views/settings/keyword_mutes/index.html.haml b/app/views/settings/keyword_mutes/index.html.haml new file mode 100644 index 000000000..421fbeba2 --- /dev/null +++ b/app/views/settings/keyword_mutes/index.html.haml @@ -0,0 +1,2 @@ +- content_for :page_title do + = t('settings.keyword_mutes') diff --git a/config/locales/en.yml b/config/locales/en.yml index 45929e97d..6b4e602bd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -491,6 +491,7 @@ en: export: Data export followers: Authorized followers import: Import + keyword_mutes: Muted keywords notifications: Notifications preferences: Preferences settings: Settings diff --git a/config/navigation.rb b/config/navigation.rb index 50bfbd480..9fa029b72 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -7,6 +7,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 settings.item :preferences, safe_join([fa_icon('sliders fw'), t('settings.preferences')]), settings_preferences_url + settings.item :keyword_mutes, safe_join([fa_icon('volume-off fw'), t('settings.keyword_mutes')]), settings_keyword_mutes_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.change_password')]), edit_user_registration_url, highlights_on: %r{/auth/edit|/settings/delete} settings.item :two_factor_authentication, safe_join([fa_icon('mobile fw'), t('settings.two_factor_authentication')]), settings_two_factor_authentication_url, highlights_on: %r{/settings/two_factor_authentication} diff --git a/config/routes.rb b/config/routes.rb index 9ed081e50..686914239 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -66,6 +66,7 @@ Rails.application.routes.draw do namespace :settings do resource :profile, only: [:show, :update] + resources :keyword_mutes resource :preferences, only: [:show, :update] resource :notifications, only: [:show, :update] resource :import, only: [:show, :create] diff --git a/spec/controllers/settings/keyword_mutes_controller_spec.rb b/spec/controllers/settings/keyword_mutes_controller_spec.rb new file mode 100644 index 000000000..a8c37a072 --- /dev/null +++ b/spec/controllers/settings/keyword_mutes_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Settings::KeywordMutesController, type: :controller do + +end diff --git a/spec/helpers/settings/keyword_mutes_helper_spec.rb b/spec/helpers/settings/keyword_mutes_helper_spec.rb new file mode 100644 index 000000000..a19d518dd --- /dev/null +++ b/spec/helpers/settings/keyword_mutes_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the Settings::KeywordMutesHelper. For example: +# +# describe Settings::KeywordMutesHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe Settings::KeywordMutesHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end -- cgit From 2e03a10059889cb05d4fab7736447a4315f90bf5 Mon Sep 17 00:00:00 2001 From: David Yip Date: Sun, 15 Oct 2017 04:51:42 -0500 Subject: Spike out index and new views for keyword mutes controller. --- .../settings/keyword_mutes_controller.rb | 23 ++++++++++++++++++++++ .../settings/keyword_mutes/_keyword_mute.html.haml | 7 +++++++ app/views/settings/keyword_mutes/index.html.haml | 13 ++++++++++++ app/views/settings/keyword_mutes/new.html.haml | 19 ++++++++++++++++++ config/locales/en.yml | 5 +++++ 5 files changed, 67 insertions(+) create mode 100644 app/views/settings/keyword_mutes/_keyword_mute.html.haml create mode 100644 app/views/settings/keyword_mutes/new.html.haml (limited to 'config') diff --git a/app/controllers/settings/keyword_mutes_controller.rb b/app/controllers/settings/keyword_mutes_controller.rb index ffe94e33a..4b3e01b9c 100644 --- a/app/controllers/settings/keyword_mutes_controller.rb +++ b/app/controllers/settings/keyword_mutes_controller.rb @@ -4,4 +4,27 @@ class Settings::KeywordMutesController < ApplicationController layout 'admin' before_action :authenticate_user! + before_action :set_account + + def index + @keyword_mutes = paginated_keyword_mutes_for_account + end + + def new + @keyword_mute = keyword_mutes_for_account.build + end + + private + + def set_account + @account = current_user.account + end + + def keyword_mutes_for_account + KeywordMute.where(account: @account) + end + + def paginated_keyword_mutes_for_account + keyword_mutes_for_account.order(:keyword).page params[:page] + end end diff --git a/app/views/settings/keyword_mutes/_keyword_mute.html.haml b/app/views/settings/keyword_mutes/_keyword_mute.html.haml new file mode 100644 index 000000000..a2698ac7b --- /dev/null +++ b/app/views/settings/keyword_mutes/_keyword_mute.html.haml @@ -0,0 +1,7 @@ +%tr + %td + = keyword_mute.keyword + %td + = table_link_to 'edit', t('settings.keyword_mutes.edit'), edit_settings_keyword_mute_path(keyword_mute) + %td + = table_link_to 'times', t('settings.keyword_mutes.delete'), settings_keyword_mute_path(keyword_mute), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } diff --git a/app/views/settings/keyword_mutes/index.html.haml b/app/views/settings/keyword_mutes/index.html.haml index 421fbeba2..6b212895d 100644 --- a/app/views/settings/keyword_mutes/index.html.haml +++ b/app/views/settings/keyword_mutes/index.html.haml @@ -1,2 +1,15 @@ - content_for :page_title do = t('settings.keyword_mutes') + +.table-wrapper + %table.table + %thead + %tr + %th= t('settings.keyword_mutes.keyword') + %th + %th + %tbody + = render @keyword_mutes + += paginate @keyword_mutes += link_to t('settings.keyword_mutes.add_keyword'), new_settings_keyword_mute_path, class: 'button' diff --git a/app/views/settings/keyword_mutes/new.html.haml b/app/views/settings/keyword_mutes/new.html.haml new file mode 100644 index 000000000..5e8268e97 --- /dev/null +++ b/app/views/settings/keyword_mutes/new.html.haml @@ -0,0 +1,19 @@ +- content_for :page_title do + = t('settings.keyword_mutes.add_keyword') + += simple_form_for @keyword_mute, url: settings_keyword_mutes_path do |f| + = render 'shared/error_messages', object: @keyword_mute + + %p.muted-hint + Keywords match word boundaries case-insensitively. For example: + %ul + %li + alice matches alice, Alice, and Alice's + %li + bob matches bob and Bob but not bobcat + + .fields-group + = f.input :keyword + + .actions + = f.button :button, t('admin.keyword_mutes.add_keyword'), type: :submit diff --git a/config/locales/en.yml b/config/locales/en.yml index 6b4e602bd..5b91f8320 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -215,6 +215,11 @@ en: contact_information: email: Business e-mail username: Contact username + keyword_mutes: + edit: Edit + delete: Delete + add_keyword: Add keyword + keyword: Keyword registrations: closed_message: desc_html: Displayed on frontpage when registrations are closed. You can use HTML tags -- cgit From cd04e3df58c09b0faca81ccc820b2cd5e12c2890 Mon Sep 17 00:00:00 2001 From: David Yip Date: Fri, 20 Oct 2017 15:12:45 -0500 Subject: Fill in create, edit, update, and destroy for keyword mutes interface. Also add a destroy-all action, which can be useful if you're flushing an old list entirely to start a new one. --- .../settings/keyword_mutes_controller.rb | 42 ++++++++++++++++++++++ app/views/settings/keyword_mutes/_fields.html.haml | 11 ++++++ .../settings/keyword_mutes/_keyword_mute.html.haml | 3 ++ app/views/settings/keyword_mutes/edit.html.haml | 6 ++++ app/views/settings/keyword_mutes/index.html.haml | 7 ++-- app/views/settings/keyword_mutes/new.html.haml | 17 ++------- config/locales/en.yml | 13 ++++--- config/routes.rb | 8 ++++- 8 files changed, 84 insertions(+), 23 deletions(-) create mode 100644 app/views/settings/keyword_mutes/_fields.html.haml create mode 100644 app/views/settings/keyword_mutes/edit.html.haml (limited to 'config') diff --git a/app/controllers/settings/keyword_mutes_controller.rb b/app/controllers/settings/keyword_mutes_controller.rb index 4b3e01b9c..d9f99af09 100644 --- a/app/controllers/settings/keyword_mutes_controller.rb +++ b/app/controllers/settings/keyword_mutes_controller.rb @@ -5,6 +5,7 @@ class Settings::KeywordMutesController < ApplicationController before_action :authenticate_user! before_action :set_account + before_action :load_keyword_mute, only: [:edit, :update, :destroy] def index @keyword_mutes = paginated_keyword_mutes_for_account @@ -14,6 +15,39 @@ class Settings::KeywordMutesController < ApplicationController @keyword_mute = keyword_mutes_for_account.build end + def create + @keyword_mute = keyword_mutes_for_account.create(keyword_mute_params) + + if @keyword_mute.persisted? + redirect_to settings_keyword_mutes_path, notice: I18n.t('generic.changes_saved_msg') + else + render :new + end + end + + def update + if @keyword_mute.update(keyword_mute_params) + redirect_to settings_keyword_mutes_path, notice: I18n.t('generic.changes_saved_msg') + else + render :new + end + end + + def destroy + if @keyword_mute.destroy + redirect_to settings_keyword_mutes_path, notice: I18n.t('generic.changes_saved_msg') + else + # FIXME + redirect_to settings_keyword_mutes_path, notice: "huh that didn't work right" + end + end + + def destroy_all + keyword_mutes_for_account.delete_all + + redirect_to settings_keyword_mutes_path, notice: I18n.t('generic.changes_saved_msg') + end + private def set_account @@ -24,6 +58,14 @@ class Settings::KeywordMutesController < ApplicationController KeywordMute.where(account: @account) end + def load_keyword_mute + @keyword_mute = keyword_mutes_for_account.find(params[:id]) + end + + def keyword_mute_params + params.require(:keyword_mute).permit(:keyword, :whole_word) + end + def paginated_keyword_mutes_for_account keyword_mutes_for_account.order(:keyword).page params[:page] end diff --git a/app/views/settings/keyword_mutes/_fields.html.haml b/app/views/settings/keyword_mutes/_fields.html.haml new file mode 100644 index 000000000..892676f18 --- /dev/null +++ b/app/views/settings/keyword_mutes/_fields.html.haml @@ -0,0 +1,11 @@ +.fields-group + = f.input :keyword + = f.check_box :whole_word + = f.label :whole_word, t('keyword_mutes.match_whole_word') + +.actions + - if f.object.persisted? + = f.button :button, t('generic.save_changes'), type: :submit + = link_to t('keyword_mutes.remove'), settings_keyword_mute_path(f.object), class: 'negative button', method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } + - else + = f.button :button, t('keyword_mutes.add_keyword'), type: :submit diff --git a/app/views/settings/keyword_mutes/_keyword_mute.html.haml b/app/views/settings/keyword_mutes/_keyword_mute.html.haml index a2698ac7b..7e191d79b 100644 --- a/app/views/settings/keyword_mutes/_keyword_mute.html.haml +++ b/app/views/settings/keyword_mutes/_keyword_mute.html.haml @@ -1,6 +1,9 @@ %tr %td = keyword_mute.keyword + %td + - if keyword_mute.whole_word + %i.fa.fa-check %td = table_link_to 'edit', t('settings.keyword_mutes.edit'), edit_settings_keyword_mute_path(keyword_mute) %td diff --git a/app/views/settings/keyword_mutes/edit.html.haml b/app/views/settings/keyword_mutes/edit.html.haml new file mode 100644 index 000000000..2b52f4018 --- /dev/null +++ b/app/views/settings/keyword_mutes/edit.html.haml @@ -0,0 +1,6 @@ +- content_for :page_title do + = t('keyword_mutes.edit_keyword') + += simple_form_for @keyword_mute, url: settings_keyword_mute_path(@keyword_mute) do |f| + = render 'shared/error_messages', object: @keyword_mute + = render 'fields', f: f diff --git a/app/views/settings/keyword_mutes/index.html.haml b/app/views/settings/keyword_mutes/index.html.haml index 6b212895d..b359eea4a 100644 --- a/app/views/settings/keyword_mutes/index.html.haml +++ b/app/views/settings/keyword_mutes/index.html.haml @@ -5,11 +5,14 @@ %table.table %thead %tr - %th= t('settings.keyword_mutes.keyword') + %th= t('keyword_mutes.keyword') + %th= t('keyword_mutes.match_whole_word') %th %th %tbody = render @keyword_mutes = paginate @keyword_mutes -= link_to t('settings.keyword_mutes.add_keyword'), new_settings_keyword_mute_path, class: 'button' +.simple_form + = link_to t('keyword_mutes.add_keyword'), new_settings_keyword_mute_path, class: 'button' + = link_to t('keyword_mutes.remove_all'), destroy_all_settings_keyword_mutes_path, class: 'button negative', method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } diff --git a/app/views/settings/keyword_mutes/new.html.haml b/app/views/settings/keyword_mutes/new.html.haml index 5e8268e97..197f10cd7 100644 --- a/app/views/settings/keyword_mutes/new.html.haml +++ b/app/views/settings/keyword_mutes/new.html.haml @@ -1,19 +1,6 @@ - content_for :page_title do - = t('settings.keyword_mutes.add_keyword') + = t('keyword_mutes.add_keyword') = simple_form_for @keyword_mute, url: settings_keyword_mutes_path do |f| = render 'shared/error_messages', object: @keyword_mute - - %p.muted-hint - Keywords match word boundaries case-insensitively. For example: - %ul - %li - alice matches alice, Alice, and Alice's - %li - bob matches bob and Bob but not bobcat - - .fields-group - = f.input :keyword - - .actions - = f.button :button, t('admin.keyword_mutes.add_keyword'), type: :submit + = render 'fields', f: f diff --git a/config/locales/en.yml b/config/locales/en.yml index 5b91f8320..22aa29be3 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -215,11 +215,6 @@ en: contact_information: email: Business e-mail username: Contact username - keyword_mutes: - edit: Edit - delete: Delete - add_keyword: Add keyword - keyword: Keyword registrations: closed_message: desc_html: Displayed on frontpage when registrations are closed. You can use HTML tags @@ -378,6 +373,14 @@ en: following: Following list muting: Muting list upload: Upload + keyword_mutes: + add_keyword: Add keyword + delete: Delete + edit: Edit + edit_keyword: Edit keyword + keyword: Keyword + match_whole_word: Match whole word + remove_all: Remove all landing_strip_html: "%{name} is a user on %{link_to_root_path}. You can follow them or interact with them if you have an account anywhere in the fediverse." landing_strip_signup_html: If you don't, you can sign up here. media_attachments: diff --git a/config/routes.rb b/config/routes.rb index 686914239..5d83ef2ab 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -66,7 +66,13 @@ Rails.application.routes.draw do namespace :settings do resource :profile, only: [:show, :update] - resources :keyword_mutes + + resources :keyword_mutes do + collection do + delete :destroy_all + end + end + resource :preferences, only: [:show, :update] resource :notifications, only: [:show, :update] resource :import, only: [:show, :create] -- cgit From 1a60445a5fa8208b54afaedf5e5796fb2ac0a80a Mon Sep 17 00:00:00 2001 From: David Yip Date: Sun, 22 Oct 2017 01:05:56 -0500 Subject: Address unused translation errors. --- app/views/settings/keyword_mutes/_keyword_mute.html.haml | 4 ++-- config/locales/en.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'config') diff --git a/app/views/settings/keyword_mutes/_keyword_mute.html.haml b/app/views/settings/keyword_mutes/_keyword_mute.html.haml index 7e191d79b..c45cc64fb 100644 --- a/app/views/settings/keyword_mutes/_keyword_mute.html.haml +++ b/app/views/settings/keyword_mutes/_keyword_mute.html.haml @@ -5,6 +5,6 @@ - if keyword_mute.whole_word %i.fa.fa-check %td - = table_link_to 'edit', t('settings.keyword_mutes.edit'), edit_settings_keyword_mute_path(keyword_mute) + = table_link_to 'edit', t('keyword_mutes.edit'), edit_settings_keyword_mute_path(keyword_mute) %td - = table_link_to 'times', t('settings.keyword_mutes.delete'), settings_keyword_mute_path(keyword_mute), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } + = table_link_to 'times', t('keyword_mutes.remove'), settings_keyword_mute_path(keyword_mute), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } diff --git a/config/locales/en.yml b/config/locales/en.yml index 22aa29be3..7d46df327 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -375,11 +375,11 @@ en: upload: Upload keyword_mutes: add_keyword: Add keyword - delete: Delete edit: Edit edit_keyword: Edit keyword keyword: Keyword match_whole_word: Match whole word + remove: Remove remove_all: Remove all landing_strip_html: "%{name} is a user on %{link_to_root_path}. You can follow them or interact with them if you have an account anywhere in the fediverse." landing_strip_signup_html: If you don't, you can sign up here. -- cgit From 3db80f75a6d76a7eea576413c5ae9b206d2ab385 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Sun, 15 Oct 2017 21:02:39 -0700 Subject: Added a timeline for Direct statuses * Lists all Direct statuses you've sent and received * Displayed in Getting Started * Streaming server support for direct TL --- .../api/v1/timelines/direct_controller.rb | 60 ++++++++++++ app/javascript/mastodon/actions/compose.js | 2 + app/javascript/mastodon/actions/streaming.js | 1 + app/javascript/mastodon/actions/timelines.js | 2 + .../containers/column_settings_container.js | 17 ++++ .../mastodon/features/direct_timeline/index.js | 107 +++++++++++++++++++++ .../mastodon/features/getting_started/index.js | 15 ++- .../features/ui/components/columns_area.js | 3 +- app/javascript/mastodon/features/ui/index.js | 2 + .../mastodon/features/ui/util/async-components.js | 4 + .../mastodon/locales/defaultMessages.json | 17 ++++ app/javascript/mastodon/locales/en.json | 3 + app/javascript/mastodon/reducers/settings.js | 6 ++ app/models/status.rb | 8 ++ app/services/batched_remove_status_service.rb | 11 +++ app/services/fan_out_on_write_service.rb | 13 ++- app/services/remove_status_service.rb | 8 ++ config/routes.rb | 1 + spec/models/status_spec.rb | 49 ++++++++++ streaming/index.js | 7 ++ 20 files changed, 329 insertions(+), 7 deletions(-) create mode 100644 app/controllers/api/v1/timelines/direct_controller.rb create mode 100644 app/javascript/mastodon/features/direct_timeline/containers/column_settings_container.js create mode 100644 app/javascript/mastodon/features/direct_timeline/index.js (limited to 'config') diff --git a/app/controllers/api/v1/timelines/direct_controller.rb b/app/controllers/api/v1/timelines/direct_controller.rb new file mode 100644 index 000000000..d455227eb --- /dev/null +++ b/app/controllers/api/v1/timelines/direct_controller.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +class Api::V1::Timelines::DirectController < Api::BaseController + before_action -> { doorkeeper_authorize! :read }, only: [:show] + before_action :require_user!, only: [:show] + after_action :insert_pagination_headers, unless: -> { @statuses.empty? } + + respond_to :json + + def show + @statuses = load_statuses + render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + end + + private + + def load_statuses + cached_direct_statuses + end + + def cached_direct_statuses + cache_collection direct_statuses, Status + end + + def direct_statuses + direct_timeline_statuses.paginate_by_max_id( + limit_param(DEFAULT_STATUSES_LIMIT), + params[:max_id], + params[:since_id] + ) + end + + def direct_timeline_statuses + Status.as_direct_timeline(current_account) + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def pagination_params(core_params) + params.permit(:local, :limit).merge(core_params) + end + + def next_path + api_v1_timelines_direct_url pagination_params(max_id: pagination_max_id) + end + + def prev_path + api_v1_timelines_direct_url pagination_params(since_id: pagination_since_id) + end + + def pagination_max_id + @statuses.last.id + end + + def pagination_since_id + @statuses.first.id + end +end diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 8a35049b3..278fbc898 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -128,6 +128,8 @@ export function submitCompose() { if (response.data.in_reply_to_id === null && response.data.visibility === 'public') { insertOrRefresh('community', refreshCommunityTimeline); insertOrRefresh('public', refreshPublicTimeline); + } else if (response.data.visibility === 'direct') { + dispatch(updateTimeline('direct', { ...response.data })); } }).catch(function (error) { dispatch(submitComposeFail(error)); diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js index 7802694a3..a2e25c930 100644 --- a/app/javascript/mastodon/actions/streaming.js +++ b/app/javascript/mastodon/actions/streaming.js @@ -92,3 +92,4 @@ export const connectCommunityStream = () => connectTimelineStream('community', ' export const connectMediaStream = () => connectTimelineStream('community', 'public:local'); export const connectPublicStream = () => connectTimelineStream('public', 'public'); export const connectHashtagStream = (tag) => connectTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`); +export const connectDirectStream = () => connectTimelineStream('direct', 'direct'); diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index 09abe2702..935bbb6f0 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -115,6 +115,7 @@ export function refreshTimeline(timelineId, path, params = {}) { export const refreshHomeTimeline = () => refreshTimeline('home', '/api/v1/timelines/home'); export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public'); export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true }); +export const refreshDirectTimeline = () => refreshTimeline('direct', '/api/v1/timelines/direct'); export const refreshAccountTimeline = accountId => refreshTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`); export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true }); export const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); @@ -155,6 +156,7 @@ export function expandTimeline(timelineId, path, params = {}) { export const expandHomeTimeline = () => expandTimeline('home', '/api/v1/timelines/home'); export const expandPublicTimeline = () => expandTimeline('public', '/api/v1/timelines/public'); export const expandCommunityTimeline = () => expandTimeline('community', '/api/v1/timelines/public', { local: true }); +export const expandDirectTimeline = () => expandTimeline('direct', '/api/v1/timelines/direct'); export const expandAccountTimeline = accountId => expandTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`); export const expandAccountMediaTimeline = accountId => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true }); export const expandHashtagTimeline = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); diff --git a/app/javascript/mastodon/features/direct_timeline/containers/column_settings_container.js b/app/javascript/mastodon/features/direct_timeline/containers/column_settings_container.js new file mode 100644 index 000000000..1833f69e5 --- /dev/null +++ b/app/javascript/mastodon/features/direct_timeline/containers/column_settings_container.js @@ -0,0 +1,17 @@ +import { connect } from 'react-redux'; +import ColumnSettings from '../../community_timeline/components/column_settings'; +import { changeSetting } from '../../../actions/settings'; + +const mapStateToProps = state => ({ + settings: state.getIn(['settings', 'direct']), +}); + +const mapDispatchToProps = dispatch => ({ + + onChange (key, checked) { + dispatch(changeSetting(['direct', ...key], checked)); + }, + +}); + +export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings); diff --git a/app/javascript/mastodon/features/direct_timeline/index.js b/app/javascript/mastodon/features/direct_timeline/index.js new file mode 100644 index 000000000..05e092ee0 --- /dev/null +++ b/app/javascript/mastodon/features/direct_timeline/index.js @@ -0,0 +1,107 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import StatusListContainer from '../ui/containers/status_list_container'; +import Column from '../../components/column'; +import ColumnHeader from '../../components/column_header'; +import { + refreshDirectTimeline, + expandDirectTimeline, +} from '../../actions/timelines'; +import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import ColumnSettingsContainer from './containers/column_settings_container'; +import { connectDirectStream } from '../../actions/streaming'; + +const messages = defineMessages({ + title: { id: 'column.direct', defaultMessage: 'Direct messages' }, +}); + +const mapStateToProps = state => ({ + hasUnread: state.getIn(['timelines', 'direct', 'unread']) > 0, +}); + +@connect(mapStateToProps) +@injectIntl +export default class DirectTimeline extends React.PureComponent { + + static propTypes = { + dispatch: PropTypes.func.isRequired, + columnId: PropTypes.string, + intl: PropTypes.object.isRequired, + hasUnread: PropTypes.bool, + multiColumn: PropTypes.bool, + }; + + handlePin = () => { + const { columnId, dispatch } = this.props; + + if (columnId) { + dispatch(removeColumn(columnId)); + } else { + dispatch(addColumn('DIRECT', {})); + } + } + + handleMove = (dir) => { + const { columnId, dispatch } = this.props; + dispatch(moveColumn(columnId, dir)); + } + + handleHeaderClick = () => { + this.column.scrollTop(); + } + + componentDidMount () { + const { dispatch } = this.props; + + dispatch(refreshDirectTimeline()); + this.disconnect = dispatch(connectDirectStream()); + } + + componentWillUnmount () { + if (this.disconnect) { + this.disconnect(); + this.disconnect = null; + } + } + + setRef = c => { + this.column = c; + } + + handleLoadMore = () => { + this.props.dispatch(expandDirectTimeline()); + } + + render () { + const { intl, hasUnread, columnId, multiColumn } = this.props; + const pinned = !!columnId; + + return ( + + + + + + } + /> + + ); + } + +} diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index 973c8a4ae..94dabd4ad 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -16,6 +16,7 @@ const messages = defineMessages({ navigation_subheading: { id: 'column_subheading.navigation', defaultMessage: 'Navigation' }, settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' }, community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, + direct: { id: 'navigation_bar.direct', defaultMessage: 'Direct messages' }, preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, @@ -65,18 +66,22 @@ export default class GettingStarted extends ImmutablePureComponent { } } + if (!multiColumn || !columns.find(item => item.get('id') === 'DIRECT')) { + navItems.push(); + } + navItems = navItems.concat([ - , - , + , + , ]); if (me.get('locked')) { - navItems.push(); + navItems.push(); } navItems = navItems.concat([ - , - , + , + , ]); return ( diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js index 5610095b9..ee1064229 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.js @@ -11,7 +11,7 @@ import BundleContainer from '../containers/bundle_container'; import ColumnLoading from './column_loading'; import DrawerLoading from './drawer_loading'; import BundleColumnError from './bundle_column_error'; -import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, FavouritedStatuses } from '../../ui/util/async-components'; +import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses } from '../../ui/util/async-components'; import detectPassiveEvents from 'detect-passive-events'; import { scrollRight } from '../../../scroll'; @@ -23,6 +23,7 @@ const componentMap = { 'PUBLIC': PublicTimeline, 'COMMUNITY': CommunityTimeline, 'HASHTAG': HashtagTimeline, + 'DIRECT': DirectTimeline, 'FAVOURITES': FavouritedStatuses, }; diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 70e451373..cf51f0fb6 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -28,6 +28,7 @@ import { Following, Reblogs, Favourites, + DirectTimeline, HashtagTimeline, Notifications, FollowRequests, @@ -350,6 +351,7 @@ export default class UI extends React.Component { + diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index 8f7b91d21..f86c2266c 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -26,6 +26,10 @@ export function HashtagTimeline () { return import(/* webpackChunkName: "features/hashtag_timeline" */'../../hashtag_timeline'); } +export function DirectTimeline() { + return import(/* webpackChunkName: "features/direct_timeline" */'../../direct_timeline'); +} + export function Status () { return import(/* webpackChunkName: "features/status" */'../../status'); } diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index f400b283f..ebb514e69 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -755,6 +755,19 @@ ], "path": "app/javascript/mastodon/features/compose/index.json" }, + { + "descriptors": [ + { + "defaultMessage": "Direct messages", + "id": "column.direct" + }, + { + "defaultMessage": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "id": "empty_column.direct" + } + ], + "path": "app/javascript/mastodon/features/direct_timeline/index.json" + }, { "descriptors": [ { @@ -816,6 +829,10 @@ "defaultMessage": "Local timeline", "id": "navigation_bar.community_timeline" }, + { + "defaultMessage": "Direct messages", + "id": "navigation_bar.direct" + }, { "defaultMessage": "Preferences", "id": "navigation_bar.preferences" diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 1d0bbcee5..efe0e1de9 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -28,6 +28,7 @@ "bundle_modal_error.retry": "Try again", "column.blocks": "Blocked users", "column.community": "Local timeline", + "column.direct": "Direct messages", "column.favourites": "Favourites", "column.follow_requests": "Follow requests", "column.home": "Home", @@ -80,6 +81,7 @@ "emoji_button.symbols": "Symbols", "emoji_button.travel": "Travel & Places", "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.hashtag": "There is nothing in this hashtag yet.", "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.", "empty_column.home.public_timeline": "the public timeline", @@ -106,6 +108,7 @@ "missing_indicator.label": "Not found", "navigation_bar.blocks": "Blocked users", "navigation_bar.community_timeline": "Local timeline", + "navigation_bar.direct": "Direct messages", "navigation_bar.edit_profile": "Edit profile", "navigation_bar.favourites": "Favourites", "navigation_bar.follow_requests": "Follow requests", diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js index a9f3f9529..8b8bf165a 100644 --- a/app/javascript/mastodon/reducers/settings.js +++ b/app/javascript/mastodon/reducers/settings.js @@ -57,6 +57,12 @@ const initialState = ImmutableMap({ body: '', }), }), + + direct: ImmutableMap({ + regex: ImmutableMap({ + body: '', + }), + }), }); const defaultColumns = fromJS([ diff --git a/app/models/status.rb b/app/models/status.rb index 5a7245613..346282e2a 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -154,6 +154,14 @@ class Status < ApplicationRecord where(account: [account] + account.following).where(visibility: [:public, :unlisted, :private]) end + def as_direct_timeline(account) + query = joins("LEFT OUTER JOIN mentions ON statuses.id = mentions.status_id AND mentions.account_id = #{account.id}") + .where("mentions.account_id = #{account.id} OR statuses.account_id = #{account.id}") + .where(visibility: [:direct]) + + apply_timeline_filters(query, account, false) + end + def as_public_timeline(account = nil, local_only = false) query = timeline_scope(local_only).without_replies diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb index 5d83771c9..aa2229f13 100644 --- a/app/services/batched_remove_status_service.rb +++ b/app/services/batched_remove_status_service.rb @@ -40,6 +40,7 @@ class BatchedRemoveStatusService < BaseService # Cannot be batched statuses.each do |status| unpush_from_public_timelines(status) + unpush_from_direct_timelines(status) if status.direct_visibility? batch_salmon_slaps(status) if status.local? end @@ -100,6 +101,16 @@ class BatchedRemoveStatusService < BaseService end end + def unpush_from_direct_timelines(status) + payload = @json_payloads[status.id] + redis.pipelined do + @mentions[status.id].each do |mention| + redis.publish("timeline:direct:#{mention.account.id}", payload) if mention.account.local? + end + redis.publish("timeline:direct:#{status.account.id}", payload) if status.account.local? + end + end + def batch_salmon_slaps(status) return if @mentions[status.id].empty? diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index 47a47a735..2214d73dd 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -10,15 +10,17 @@ class FanOutOnWriteService < BaseService deliver_to_self(status) if status.account.local? + render_anonymous_payload(status) + if status.direct_visibility? deliver_to_mentioned_followers(status) + deliver_to_direct_timelines(status) else deliver_to_followers(status) end return if status.account.silenced? || !status.public_visibility? || status.reblog? - render_anonymous_payload(status) deliver_to_hashtags(status) return if status.reply? && status.in_reply_to_account_id != status.account_id @@ -73,4 +75,13 @@ class FanOutOnWriteService < BaseService Redis.current.publish('timeline:public', @payload) Redis.current.publish('timeline:public:local', @payload) if status.local? end + + def deliver_to_direct_timelines(status) + Rails.logger.debug "Delivering status #{status.id} to direct timelines" + + status.mentions.includes(:account).each do |mention| + Redis.current.publish("timeline:direct:#{mention.account.id}", @payload) if mention.account.local? + end + Redis.current.publish("timeline:direct:#{status.account.id}", @payload) if status.account.local? + end end diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index 96d9208cc..8eef3e57e 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -18,6 +18,7 @@ class RemoveStatusService < BaseService remove_reblogs remove_from_hashtags remove_from_public + remove_from_direct if status.direct_visibility? @status.destroy! @@ -121,6 +122,13 @@ class RemoveStatusService < BaseService Redis.current.publish('timeline:public:local', @payload) if @status.local? end + def remove_from_direct + @mentions.each do |mention| + Redis.current.publish("timeline:direct:#{mention.account.id}", @payload) if mention.account.local? + end + Redis.current.publish("timeline:direct:#{@account.id}", @payload) if @account.local? + end + def redis Redis.current end diff --git a/config/routes.rb b/config/routes.rb index 5a6351f77..8263c477b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -193,6 +193,7 @@ Rails.application.routes.draw do end namespace :timelines do + resource :direct, only: :show, controller: :direct resource :home, only: :show, controller: :home resource :public, only: :show, controller: :public resources :tag, only: :show diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index 9cb71d715..12e857169 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -232,6 +232,55 @@ RSpec.describe Status, type: :model do end end + describe '.as_direct_timeline' do + let(:account) { Fabricate(:account) } + let(:followed) { Fabricate(:account) } + let(:not_followed) { Fabricate(:account) } + + before do + Fabricate(:follow, account: account, target_account: followed) + + @self_public_status = Fabricate(:status, account: account, visibility: :public) + @self_direct_status = Fabricate(:status, account: account, visibility: :direct) + @followed_public_status = Fabricate(:status, account: followed, visibility: :public) + @followed_direct_status = Fabricate(:status, account: followed, visibility: :direct) + @not_followed_direct_status = Fabricate(:status, account: not_followed, visibility: :direct) + + @results = Status.as_direct_timeline(account) + end + + it 'does not include public statuses from self' do + expect(@results).to_not include(@self_public_status) + end + + it 'includes direct statuses from self' do + expect(@results).to include(@self_direct_status) + end + + it 'does not include public statuses from followed' do + expect(@results).to_not include(@followed_public_status) + end + + it 'includes direct statuses mentioning recipient from followed' do + Fabricate(:mention, account: account, status: @followed_direct_status) + expect(@results).to include(@followed_direct_status) + end + + it 'does not include direct statuses not mentioning recipient from followed' do + expect(@results).to_not include(@followed_direct_status) + end + + it 'includes direct statuses mentioning recipient from non-followed' do + Fabricate(:mention, account: account, status: @not_followed_direct_status) + expect(@results).to include(@not_followed_direct_status) + end + + it 'does not include direct statuses not mentioning recipient from non-followed' do + expect(@results).to_not include(@not_followed_direct_status) + end + + end + describe '.as_public_timeline' do it 'only includes statuses with public visibility' do public_status = Fabricate(:status, visibility: :public) diff --git a/streaming/index.js b/streaming/index.js index 83903b89b..8adc5174a 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -402,6 +402,10 @@ const startWorker = (workerId) => { streamFrom('timeline:public:local', req, streamToHttp(req, res), streamHttpEnd(req), true); }); + app.get('/api/v1/streaming/direct', (req, res) => { + streamFrom(`timeline:direct:${req.accountId}`, req, streamToHttp(req, res), streamHttpEnd(req), true); + }); + app.get('/api/v1/streaming/hashtag', (req, res) => { streamFrom(`timeline:hashtag:${req.query.tag.toLowerCase()}`, req, streamToHttp(req, res), streamHttpEnd(req), true); }); @@ -437,6 +441,9 @@ const startWorker = (workerId) => { case 'public:local': streamFrom('timeline:public:local', req, streamToWs(req, ws), streamWsEnd(req, ws), true); break; + case 'direct': + streamFrom(`timeline:direct:${req.accountId}`, req, streamToWs(req, ws), streamWsEnd(req, ws), true); + break; case 'hashtag': streamFrom(`timeline:hashtag:${location.query.tag.toLowerCase()}`, req, streamToWs(req, ws), streamWsEnd(req, ws), true); break; -- cgit From 254b74c71f24304f0a723d38b3ed76f9a70b8c93 Mon Sep 17 00:00:00 2001 From: beatrix Date: Fri, 3 Nov 2017 12:34:50 -0400 Subject: add memorial to production.rb in memory of Natalie Nguyen let her name ring through the ether --- config/environments/production.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'config') diff --git a/config/environments/production.rb b/config/environments/production.rb index e0ee393c1..f7cb4b08a 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -97,6 +97,8 @@ Rails.application.configure do 'X-XSS-Protection' => '1; mode=block', 'Content-Security-Policy' => "frame-ancestors 'none'; object-src 'none'; script-src 'self' https://dev-static.glitch.social 'unsafe-inline'; base-uri 'none';" , 'Referrer-Policy' => 'no-referrer, strict-origin-when-cross-origin', - 'Strict-Transport-Security' => 'max-age=63072000; includeSubDomains; preload' + 'Strict-Transport-Security' => 'max-age=63072000; includeSubDomains; preload', + 'X-Clacks-Overhead' => 'GNU Natalie Nguyen' + } end -- cgit