about summary refs log tree commit diff
diff options
context:
space:
mode:
authorDavid Yip <yipdw@member.fsf.org>2018-04-04 13:56:58 -0500
committerDavid Yip <yipdw@member.fsf.org>2018-04-04 13:56:58 -0500
commit3ab6a8b8bed37d048909b178693af8411325eb38 (patch)
treedb371121cbfb240d1338e803dff136f3023c2b87
parentbda1782cd864ed3aabb5a4d87359a1cb7595f4a6 (diff)
parent1c1042556d21e4c2eb22b7c5cbc11aa88087ca60 (diff)
Merge remote-tracking branch 'origin/master' into gs-master
  Conflicts:
 	spec/views/about/show.html.haml_spec.rb
-rw-r--r--app/javascript/mastodon/features/ui/components/zoomable_image.js165
-rw-r--r--app/javascript/styles/mastodon/about.scss48
-rw-r--r--app/javascript/styles/mastodon/components.scss3
-rw-r--r--app/views/about/show.html.haml22
-rw-r--r--config/locales/en.yml1
-rw-r--r--config/locales/pl.yml26
-rw-r--r--package.json1
-rw-r--r--spec/views/about/show.html.haml_spec.rb6
-rw-r--r--yarn.lock4
9 files changed, 173 insertions, 103 deletions
diff --git a/app/javascript/mastodon/features/ui/components/zoomable_image.js b/app/javascript/mastodon/features/ui/components/zoomable_image.js
index 0cae0862d..0a0a4d41a 100644
--- a/app/javascript/mastodon/features/ui/components/zoomable_image.js
+++ b/app/javascript/mastodon/features/ui/components/zoomable_image.js
@@ -1,10 +1,16 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import Hammer from 'hammerjs';
 
 const MIN_SCALE = 1;
 const MAX_SCALE = 4;
-const DOUBLE_TAP_SCALE = 2;
+
+const getMidpoint = (p1, p2) => ({
+  x: (p1.clientX + p2.clientX) / 2,
+  y: (p1.clientY + p2.clientY) / 2,
+});
+
+const getDistance = (p1, p2) =>
+  Math.sqrt(Math.pow(p1.clientX - p2.clientX, 2) + Math.pow(p1.clientY - p2.clientY, 2));
 
 const clamp = (min, max, value) => Math.min(max, Math.max(min, value));
 
@@ -31,95 +37,81 @@ export default class ZoomableImage extends React.PureComponent {
   removers = [];
   container = null;
   image = null;
-  lastScale = null;
-  zoomCenter = null;
+  lastTouchEndTime = 0;
+  lastDistance = 0;
 
   componentDidMount () {
-    // register pinch event handlers to the container
-    let hammer = new Hammer.Manager(this.container, {
-      // required to make container scrollable by touch
-      touchAction: 'pan-x pan-y',
-    });
-    hammer.add(new Hammer.Pinch());
-    hammer.on('pinchstart', this.handlePinchStart);
-    hammer.on('pinchmove', this.handlePinchMove);
-    this.removers.push(() => hammer.off('pinchstart pinchmove'));
-
-    // register tap event handlers
-    hammer = new Hammer.Manager(this.image);
-    // NOTE the order of adding is also the order of gesture recognition
-    hammer.add(new Hammer.Tap({ event: 'doubletap', taps: 2 }));
-    hammer.add(new Hammer.Tap());
-    // prevent the 'tap' event handler be fired on double tap
-    hammer.get('tap').requireFailure('doubletap');
-    // NOTE 'tap' and 'doubletap' events are fired by touch and *mouse*
-    hammer.on('tap', this.handleTap);
-    hammer.on('doubletap', this.handleDoubleTap);
-    this.removers.push(() => hammer.off('tap doubletap'));
+    let handler = this.handleTouchStart;
+    this.container.addEventListener('touchstart', handler);
+    this.removers.push(() => this.container.removeEventListener('touchstart', handler));
+    handler = this.handleTouchMove;
+    // on Chrome 56+, touch event listeners will default to passive
+    // https://www.chromestatus.com/features/5093566007214080
+    this.container.addEventListener('touchmove', handler, { passive: false });
+    this.removers.push(() => this.container.removeEventListener('touchend', handler));
   }
 
   componentWillUnmount () {
     this.removeEventListeners();
   }
 
-  componentDidUpdate (prevProps, prevState) {
-    if (!this.zoomCenter) return;
-
-    const { x: cx, y: cy } = this.zoomCenter;
-    const { scale: prevScale } = prevState;
-    const { scale: nextScale } = this.state;
-    const { scrollLeft, scrollTop } = this.container;
-
-    // math memo:
-    // x = (scrollLeft + cx) / scrollWidth
-    // x' = (nextScrollLeft + cx) / nextScrollWidth
-    // scrollWidth = clientWidth * prevScale
-    // scrollWidth' = clientWidth * nextScale
-    // Solve x = x' for nextScrollLeft
-    const nextScrollLeft = (scrollLeft + cx) * nextScale / prevScale - cx;
-    const nextScrollTop = (scrollTop + cy) * nextScale / prevScale - cy;
-
-    this.container.scrollLeft = nextScrollLeft;
-    this.container.scrollTop = nextScrollTop;
-  }
-
   removeEventListeners () {
     this.removers.forEach(listeners => listeners());
     this.removers = [];
   }
 
-  handleClick = e => {
-    // prevent the click event propagated to parent
-    e.stopPropagation();
+  handleTouchStart = e => {
+    if (e.touches.length !== 2) return;
 
-    // the tap event handler is executed at the same time by touch and mouse,
-    // so we don't need to execute the onClick handler here
+    this.lastDistance = getDistance(...e.touches);
   }
 
-  handlePinchStart = () => {
-    this.lastScale = this.state.scale;
-  }
+  handleTouchMove = e => {
+    const { scrollTop, scrollHeight, clientHeight } = this.container;
+    if (e.touches.length === 1 && scrollTop !== scrollHeight - clientHeight) {
+      // prevent propagating event to MediaModal
+      e.stopPropagation();
+      return;
+    }
+    if (e.touches.length !== 2) return;
 
-  handlePinchMove = e => {
-    const scale = clamp(MIN_SCALE, MAX_SCALE, this.lastScale * e.scale);
-    this.zoom(scale, e.center);
-  }
+    e.preventDefault();
+    e.stopPropagation();
 
-  handleTap = () => {
-    const handler = this.props.onClick;
-    if (handler) handler();
+    const distance = getDistance(...e.touches);
+    const midpoint = getMidpoint(...e.touches);
+    const scale = clamp(MIN_SCALE, MAX_SCALE, this.state.scale * distance / this.lastDistance);
+
+    this.zoom(scale, midpoint);
+
+    this.lastMidpoint = midpoint;
+    this.lastDistance = distance;
   }
 
-  handleDoubleTap = e => {
-    if (this.state.scale === MIN_SCALE)
-      this.zoom(DOUBLE_TAP_SCALE, e.center);
-    else
-      this.zoom(MIN_SCALE, e.center);
+  zoom(nextScale, midpoint) {
+    const { scale } = this.state;
+    const { scrollLeft, scrollTop } = this.container;
+
+    // math memo:
+    // x = (scrollLeft + midpoint.x) / scrollWidth
+    // x' = (nextScrollLeft + midpoint.x) / nextScrollWidth
+    // scrollWidth = clientWidth * scale
+    // scrollWidth' = clientWidth * nextScale
+    // Solve x = x' for nextScrollLeft
+    const nextScrollLeft = (scrollLeft + midpoint.x) * nextScale / scale - midpoint.x;
+    const nextScrollTop = (scrollTop + midpoint.y) * nextScale / scale - midpoint.y;
+
+    this.setState({ scale: nextScale }, () => {
+      this.container.scrollLeft = nextScrollLeft;
+      this.container.scrollTop = nextScrollTop;
+    });
   }
 
-  zoom (scale, center) {
-    this.zoomCenter = center;
-    this.setState({ scale });
+  handleClick = e => {
+    // don't propagate event to MediaModal
+    e.stopPropagation();
+    const handler = this.props.onClick;
+    if (handler) handler();
   }
 
   setContainerRef = c => {
@@ -134,18 +126,6 @@ export default class ZoomableImage extends React.PureComponent {
     const { alt, src } = this.props;
     const { scale } = this.state;
     const overflow = scale === 1 ? 'hidden' : 'scroll';
-    const marginStyle = {
-      position: 'absolute',
-      top: 0,
-      bottom: 0,
-      left: 0,
-      right: 0,
-      display: 'flex',
-      alignItems: 'center',
-      justifyContent: 'center',
-      transform: `scale(${scale})`,
-      transformOrigin: '0 0',
-    };
 
     return (
       <div
@@ -153,18 +133,17 @@ export default class ZoomableImage extends React.PureComponent {
         ref={this.setContainerRef}
         style={{ overflow }}
       >
-        <div
-          className='zoomable-image__margin'
-          style={marginStyle}
-        >
-          <img
-            ref={this.setImageRef}
-            role='presentation'
-            alt={alt}
-            src={src}
-            onClick={this.handleClick}
-          />
-        </div>
+        <img
+          role='presentation'
+          ref={this.setImageRef}
+          alt={alt}
+          src={src}
+          style={{
+            transform: `scale(${scale})`,
+            transformOrigin: '0 0',
+          }}
+          onClick={this.handleClick}
+        />
       </div>
     );
   }
diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss
index c484f074b..03211036c 100644
--- a/app/javascript/styles/mastodon/about.scss
+++ b/app/javascript/styles/mastodon/about.scss
@@ -681,6 +681,54 @@ $small-breakpoint: 960px;
       margin-bottom: 0;
     }
 
+    .account {
+      border-bottom: 0;
+      padding: 0;
+
+      &__display-name {
+        align-items: center;
+        display: flex;
+        margin-right: 5px;
+      }
+
+      div.account__display-name {
+        &:hover {
+          .display-name strong {
+            text-decoration: none;
+          }
+        }
+
+        .account__avatar {
+          cursor: default;
+        }
+      }
+
+      &__avatar-wrapper {
+        margin-left: 0;
+        flex: 0 0 auto;
+      }
+
+      &__avatar {
+        width: 44px;
+        height: 44px;
+        background-size: 44px 44px;
+      }
+
+      .display-name {
+        font-size: 15px;
+
+        &__account {
+          font-size: 14px;
+        }
+      }
+    }
+
+    @media screen and (max-width: $small-breakpoint) {
+      .contact {
+        margin-top: 30px;
+      }
+    }
+
     @media screen and (max-width: $column-breakpoint) {
       padding: 25px 20px;
     }
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 31089301c..c82a760c4 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -1483,6 +1483,9 @@
   position: relative;
   width: 100%;
   height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
 
   img {
     max-width: $media-modal-media-max-width;
diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml
index 5594abecb..674771cbc 100644
--- a/app/views/about/show.html.haml
+++ b/app/views/about/show.html.haml
@@ -107,6 +107,28 @@
               %div
                 %h3= t 'about.what_is_mastodon'
                 %p= t 'about.about_mastodon_html'
+              %div.contact
+                %h3= t 'about.administered_by'
+
+                .account
+                  .account__wrapper
+                    - if @instance_presenter.contact_account
+                      = link_to TagManager.instance.url_for(@instance_presenter.contact_account), class: 'account__display-name' do
+                        .account__avatar-wrapper
+                          .account__avatar{ style: "background-image: url(#{@instance_presenter.contact_account.avatar.url})" }
+                        %span.display-name
+                          %bdi
+                            %strong.display-name__html.emojify= display_name(@instance_presenter.contact_account)
+                          %span.display-name__account @#{@instance_presenter.contact_account.acct}
+                    - else
+                      .account__display-name
+                        .account__avatar-wrapper
+                          .account__avatar{ style: "background-image: url(#{full_asset_url('avatars/original/missing.png', skip_pipeline: true)})" }
+                        %span.display-name
+                          %strong= t 'about.contact_missing'
+                          %span.display-name__account= t 'about.contact_unavailable'
+
+                    = link_to t('about.learn_more'), about_more_path, class: 'button button-alternative'
 
             = render 'features'
 
diff --git a/config/locales/en.yml b/config/locales/en.yml
index a1ed71afe..9e24fd8a1 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -4,6 +4,7 @@ en:
     about_hashtag_html: These are public toots tagged with <strong>#%{hashtag}</strong>. You can interact with them if you have an account anywhere in the fediverse.
     about_mastodon_html: Mastodon is a social network based on open web protocols and free, open-source software. It is decentralized like e-mail.
     about_this: About
+    administered_by: 'Administered by:'
     closed_registrations: Registrations are currently closed on this instance. However! You can find a different instance to make an account on and get access to the very same network from there.
     contact: Contact
     contact_missing: Not set
diff --git a/config/locales/pl.yml b/config/locales/pl.yml
index 90892c767..2bd50f28c 100644
--- a/config/locales/pl.yml
+++ b/config/locales/pl.yml
@@ -4,6 +4,7 @@ pl:
     about_hashtag_html: Znajdują się tu publiczne wpisy oznaczone hashtagiem <strong>#%{hashtag}</strong>. Możesz dołączyć do dyskusji, jeżeli posiadasz konto gdziekolwiek w Fediwersum.
     about_mastodon_html: Mastodon jest wolną i otwartą siecią społecznościową, zdecentralizowaną alternatywą dla zamkniętych, komercyjnych platform.
     about_this: O tej instancji
+    administered_by: 'Administrowana przez:'
     closed_registrations: Rejestracja na tej instancji jest obecnie zamknięta. Możesz jednak zarejestrować się na innej instancji, uzyskując dostęp do tej samej sieci.
     contact: Kontakt
     contact_missing: Nie ustawiono
@@ -60,6 +61,7 @@ pl:
       destroyed_msg: Pomyślnie usunięto notatkę moderacyjną!
     accounts:
       are_you_sure: Jesteś tego pewien?
+      avatar: Awatar
       by_domain: Domena
       confirm: Potwierdź
       confirmed: Potwierdzono
@@ -108,6 +110,7 @@ pl:
       public: Publiczne
       push_subscription_expires: Subskrypcja PuSH wygasa
       redownload: Odśwież awatar
+      remove_avatar: Usun awatar
       reset: Resetuj
       reset_password: Resetuj hasło
       resubscribe: Ponów subskrypcję
@@ -135,6 +138,7 @@ pl:
       web: Sieć
     action_logs:
       actions:
+        assigned_to_self_report: "%{name} przypisał sobie zgłoszenie %{target}"
         confirm_user: "%{name} potwierdził adres e-mail użytkownika %{target}"
         create_custom_emoji: "%{name} dodał nowe emoji %{target}"
         create_domain_block: "%{name} zablokował domenę %{target}"
@@ -150,10 +154,13 @@ pl:
         enable_user: "%{name} przywrócił możliwość logowania użytkownikowi %{target}"
         memorialize_account: "%{name} nadał kontu %{target} status in memoriam"
         promote_user: "%{name} podniósł uprawnienia użytkownikowi %{target}"
+        remove_avatar_user: "%{name} usunął awatar użytkownikowi %{target}"
+        reopen_report: "%{name} otworzył ponownie zgłoszenie %{target}"
         reset_password_user: "%{name} przywrócił hasło użytkownikowi %{target}"
-        resolve_report: "%{name} odrzucił zgłoszenie %{target}"
+        resolve_report: "%{name} rozwiązał zgłoszenie %{target}"
         silence_account: "%{name} wyciszył konto %{target}"
         suspend_account: "%{name} zawiesił konto %{target}"
+        unassigned_report: "%{name} cofnął przypisanie zgłoszenia %{target}"
         unsilence_account: "%{name} cofnął wyciszenie konta %{target}"
         unsuspend_account: "%{name} cofnął zawieszenie konta %{target}"
         update_custom_emoji: "%{name} zaktualizował emoji %{target}"
@@ -240,15 +247,26 @@ pl:
         expired: Wygasłe
         title: Filtruj
       title: Zaproszenia
+    report_notes:
+      created_msg: Pomyslnie utworzono notatkę moderacyjną.
+      destroyed_msg: Pomyślnie usunięto notatkę moderacyjną.
     reports:
       action_taken_by: Działanie podjęte przez
       are_you_sure: Czy na pewno?
+      assign_to_self: Przypisz do siebie
+      assigned: Przypisany moderator
       comment:
-        label: Komentarz
+        label: Komentarz do zgłoszenia
         none: Brak
       delete: Usuń
       id: ID
       mark_as_resolved: Oznacz jako rozwiązane
+      mark_as_unresolved: Oznacz jako nierozwiązane
+      notes:
+        create: Utwórz notatkę
+        create_and_resolve: Rozwiąż i pozostaw notatkę
+        delete: Usuń
+        label: Notatki
       nsfw:
         'false': Nie oznaczaj jako NSFW
         'true': Oznaczaj jako NSFW
@@ -257,12 +275,16 @@ pl:
       reported_account: Zgłoszone konto
       reported_by: Zgłaszający
       resolved: Rozwiązane
+      resolved_msg: Pomyślnie rozwiązano zgłoszenie.
       silence_account: Wycisz konto
       status: Stan
+      statuses: Zgłoszone wpisy
       suspend_account: Zawieś konto
       target: Cel
       title: Zgłoszenia
+      unassign: Cofnij przypisanie
       unresolved: Nierozwiązane
+      updated_at: Zaktualizowano
       view: Wyświetl
     settings:
       activity_api_enabled:
diff --git a/package.json b/package.json
index 8a50afdb6..491a487d2 100644
--- a/package.json
+++ b/package.json
@@ -55,7 +55,6 @@
     "file-loader": "^0.11.2",
     "font-awesome": "^4.7.0",
     "glob": "^7.1.1",
-    "hammerjs": "^2.0.8",
     "http-link-header": "^0.8.0",
     "immutable": "^3.8.2",
     "imports-loader": "^0.8.0",
diff --git a/spec/views/about/show.html.haml_spec.rb b/spec/views/about/show.html.haml_spec.rb
index be1320ed3..0f4d79569 100644
--- a/spec/views/about/show.html.haml_spec.rb
+++ b/spec/views/about/show.html.haml_spec.rb
@@ -21,9 +21,9 @@ describe 'about/show.html.haml', without_verify_partial_doubles: true do
                                 hero: nil,
                                 user_count: 0,
                                 status_count: 0,
-                                closed_registrations_message: 'yes',
-                                commit_hash: commit_hash)
-
+                                commit_hash: commit_hash,
+                                contact_account: nil,
+                                closed_registrations_message: 'yes')
     assign(:instance_presenter, instance_presenter)
     render
 
diff --git a/yarn.lock b/yarn.lock
index e21df9cb4..03915eafe 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3100,10 +3100,6 @@ gzip-size@^3.0.0:
   dependencies:
     duplexer "^0.1.1"
 
-hammerjs@^2.0.8:
-  version "2.0.8"
-  resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1"
-
 handle-thing@^1.2.5:
   version "1.2.5"
   resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4"