]> Untitled Git - lemmy.git/commitdiff
A first pass at adding icons, and tippy tooltips.
authorDessalines <tyhou13@gmx.com>
Tue, 3 Mar 2020 07:29:45 +0000 (02:29 -0500)
committerDessalines <tyhou13@gmx.com>
Tue, 3 Mar 2020 07:29:45 +0000 (02:29 -0500)
- Adding icons for post-listing, comment-node, and navbar.
- Adding html titles.
- Updating moment expand to use users locale.

19 files changed:
ui/assets/css/main.css
ui/assets/css/tippy.css [new file with mode: 0644]
ui/package.json
ui/src/components/comment-form.tsx
ui/src/components/comment-node.tsx
ui/src/components/community.tsx
ui/src/components/iframely-card.tsx
ui/src/components/inbox.tsx
ui/src/components/main.tsx
ui/src/components/moment-time.tsx
ui/src/components/navbar.tsx
ui/src/components/post-form.tsx
ui/src/components/post-listing.tsx
ui/src/components/symbols.tsx
ui/src/components/user.tsx
ui/src/index.html
ui/src/utils.ts
ui/translations/en.json
ui/yarn.lock

index b03f27036ad278179907ad9f7cf19cfe7853d951..d206a508deafa25ae3e6abab7406d335a7542ede 100644 (file)
   fill: currentColor;
   vertical-align: middle;
   align-self: center;
+  -webkit-touch-callout: none;
+  -webkit-user-select: none;
+  -khtml-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
 }
 
+.icon-inline {
+  margin-bottom: 2px;
+}
 
 .spin {
   animation: spins 2s linear infinite;
@@ -225,3 +234,20 @@ hr {
   height: 50px;
   width: 50px;
 }
+
+.unselectable {
+  -webkit-touch-callout: none;
+  -webkit-user-select: none;
+  -khtml-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+
+.list-inline-item-action {
+  display: inline-block;
+}
+
+.list-inline-item-action:not(:last-child) {
+  margin-right: 1.2rem;
+}
diff --git a/ui/assets/css/tippy.css b/ui/assets/css/tippy.css
new file mode 100644 (file)
index 0000000..ff0a313
--- /dev/null
@@ -0,0 +1 @@
+.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}.tippy-iOS{cursor:pointer!important;-webkit-tap-highlight-color:transparent}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{border-width:8px 8px 0;border-top-color:#333;bottom:-7px;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;border-width:0 8px 8px;border-bottom-color:#333;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:#333;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:#333;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1}
\ No newline at end of file
index f12f947afeb505e5ebcb2c182bd19788ccc1356a..8658d136329dbc5d512f0593191226d3b49fa876 100644 (file)
@@ -41,6 +41,7 @@
     "reconnecting-websocket": "^4.4.0",
     "rxjs": "^6.4.0",
     "terser": "^4.6.3",
+    "tippy.js": "^6.0.0",
     "toastify-js": "^1.6.2",
     "tributejs": "^4.1.1",
     "twemoji": "^12.1.2",
index eaa054d83fae7c724e76739c3894efe6f9f65819..aa8e651de30a69a49779c81588dd72ba0a7d58df 100644 (file)
@@ -141,16 +141,22 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
               <a
                 href={markdownHelpUrl}
                 target="_blank"
-                class="d-inline-block float-right text-muted small font-weight-bold"
+                class="d-inline-block float-right text-muted font-weight-bold"
+                title={i18n.t('formatting_help')}
               >
-                {i18n.t('formatting_help')}
+                <svg class="icon icon-inline">
+                  <use xlinkHref="#icon-help-circle"></use>
+                </svg>
               </a>
-              <form class="d-inline-block mr-2 float-right text-muted small font-weight-bold">
+              <form class="d-inline-block mr-3 float-right text-muted font-weight-bold">
                 <label
                   htmlFor={`file-upload-${this.id}`}
                   className={`${UserService.Instance.user && 'pointer'}`}
+                  data-tippy-content={i18n.t('upload_image')}
                 >
-                  {i18n.t('upload_image')}
+                  <svg class="icon icon-inline">
+                    <use xlinkHref="#icon-image"></use>
+                  </svg>
                 </label>
                 <input
                   id={`file-upload-${this.id}`}
index cd95a75bedae0d1c528237fa925ffbe66fd6c134..3db87aa5856563c4e6821ac285e062fd72b48d58 100644 (file)
@@ -26,6 +26,7 @@ import {
   isMod,
   pictshareAvatarThumbnail,
   showAvatars,
+  setupTippy,
 } from '../utils';
 import moment from 'moment';
 import { MomentTime } from './moment-time';
@@ -102,6 +103,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
     this.handleCommentDownvote = this.handleCommentDownvote.bind(this);
   }
 
+  componentDidUpdate() {
+    setupTippy();
+  }
+
   componentWillReceiveProps(nextProps: CommentNodeProps) {
     this.state.my_vote = nextProps.node.comment.my_vote;
     this.state.upvotes = nextProps.node.comment.upvotes;
@@ -128,18 +133,22 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
               }`}
               onClick={linkEvent(node, this.handleCommentUpvote)}
+              data-tippy-content={i18n.t('upvote')}
             >
               <svg class="icon upvote">
                 <use xlinkHref="#icon-arrow-up"></use>
               </svg>
             </button>
-            <div class={`font-weight-bold text-muted`}>{this.state.score}</div>
+            <div class={`unselectable font-weight-bold text-muted`}>
+              {this.state.score}
+            </div>
             {WebSocketService.Instance.site.enable_downvotes && (
               <button
                 className={`vote-animate btn btn-link p-0 ${
                   this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
                 }`}
                 onClick={linkEvent(node, this.handleCommentDownvote)}
+                data-tippy-content={i18n.t('downvote')}
               >
                 <svg class="icon downvote">
                   <use xlinkHref="#icon-arrow-down"></use>
@@ -192,11 +201,19 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
               </li>
             )}
             <li className="list-inline-item">
-              <span>
-                (<span className="text-info">+{this.state.upvotes}</span>
-                <span> | </span>
-                <span className="text-danger">-{this.state.downvotes}</span>
-                <span>) </span>
+              <span className="text-info">
+                <svg class="small icon icon-inline mr-1">
+                  <use xlinkHref="#icon-arrow-up"></use>
+                </svg>
+                {this.state.upvotes}
+              </span>
+            </li>
+            <li className="list-inline-item">
+              <span className="text-danger">
+                <svg class="small icon icon-inline mr-1">
+                  <use xlinkHref="#icon-arrow-down"></use>
+                </svg>
+                {this.state.downvotes}
               </span>
             </li>
             {this.props.showCommunity && (
@@ -214,7 +231,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
             </li>
             <li className="list-inline-item">
               <div
-                className="pointer text-monospace"
+                className="unselectable pointer text-monospace"
                 onClick={linkEvent(this, this.handleCommentCollapse)}
               >
                 {this.state.collapsed ? '[+]' : '[-]'}
@@ -239,97 +256,141 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                   dangerouslySetInnerHTML={mdToHtml(this.commentUnlessRemoved)}
                 />
               )}
-              <ul class="list-inline mb-1 text-muted small font-weight-bold">
+              <ul class="list-inline mb-1 text-muted font-weight-bold h6">
                 {this.props.markable && (
-                  <li className="list-inline-item">
+                  <li className="list-inline-item-action">
                     <span
                       class="pointer"
                       onClick={linkEvent(this, this.handleMarkRead)}
+                      data-tippy-content={
+                        node.comment.read
+                          ? i18n.t('mark_as_unread')
+                          : i18n.t('mark_as_read')
+                      }
                     >
-                      {node.comment.read
-                        ? i18n.t('mark_as_unread')
-                        : i18n.t('mark_as_read')}
+                      <svg
+                        class={`icon icon-inline ${node.comment.read &&
+                          'text-success'}`}
+                      >
+                        <use xlinkHref="#icon-check"></use>
+                      </svg>
                     </span>
                   </li>
                 )}
                 {UserService.Instance.user && !this.props.viewOnly && (
                   <>
-                    <li className="list-inline-item">
+                    <li className="list-inline-item-action">
                       <span
                         class="pointer"
                         onClick={linkEvent(this, this.handleReplyClick)}
+                        data-tippy-content={i18n.t('reply')}
                       >
-                        {i18n.t('reply')}
+                        <svg class="icon icon-inline">
+                          <use xlinkHref="#icon-reply1"></use>
+                        </svg>
                       </span>
                     </li>
-                    <li className="list-inline-item mr-2">
+                    <li className="list-inline-item-action">
                       <span
                         class="pointer"
                         onClick={linkEvent(this, this.handleSaveCommentClick)}
+                        data-tippy-content={
+                          node.comment.saved ? i18n.t('unsave') : i18n.t('save')
+                        }
                       >
-                        {node.comment.saved ? i18n.t('unsave') : i18n.t('save')}
+                        <svg
+                          class={`icon icon-inline ${node.comment.saved &&
+                            'text-warning'}`}
+                        >
+                          <use xlinkHref="#icon-star"></use>
+                        </svg>
                       </span>
                     </li>
                     {!this.myComment && (
-                      <li className="list-inline-item">
+                      <li className="list-inline-item-action">
                         <Link
                           class="text-muted"
                           to={`/create_private_message?recipient_id=${node.comment.creator_id}`}
+                          title={i18n.t('message').toLowerCase()}
                         >
-                          {i18n.t('message').toLowerCase()}
+                          <svg class="icon icon-inline">
+                            <use xlinkHref="#icon-mail"></use>
+                          </svg>
                         </Link>
                       </li>
                     )}
-                    <li className="list-inline-item">
+                    <li className="list-inline-item-action">
                       <Link
                         className="text-muted"
                         to={`/post/${node.comment.post_id}/comment/${node.comment.id}`}
+                        title={i18n.t('link')}
                       >
-                        {i18n.t('link')}
+                        <svg class="icon icon-inline">
+                          <use xlinkHref="#icon-external-link"></use>
+                        </svg>
                       </Link>
                     </li>
                     {!this.state.showAdvanced ? (
-                      <li className="list-inline-item">
+                      <li className="list-inline-item-action">
                         <span
-                          className="pointer"
+                          className="unselectable pointer"
                           onClick={linkEvent(this, this.handleShowAdvanced)}
+                          data-tippy-content={i18n.t('more')}
                         >
-                          {i18n.t('more')}
+                          <svg class="icon icon-inline">
+                            <use xlinkHref="#icon-more-vertical"></use>
+                          </svg>
                         </span>
                       </li>
                     ) : (
                       <>
-                        <li className="list-inline-item">•</li>
-                        <li className="list-inline-item">
+                        <li className="list-inline-item-action">
                           <span
                             className="pointer"
                             onClick={linkEvent(this, this.handleViewSource)}
+                            data-tippy-content={i18n.t('view_source')}
                           >
-                            {i18n.t('view_source')}
+                            <svg
+                              class={`icon icon-inline ${this.state
+                                .viewSource && 'text-success'}`}
+                            >
+                              <use xlinkHref="#icon-eye"></use>
+                            </svg>
                           </span>
                         </li>
-                        <li className="list-inline-item">•</li>
                         {this.myComment && (
                           <>
-                            <li className="list-inline-item">
+                            <li className="list-inline-item-action">•</li>
+                            <li className="list-inline-item-action">
                               <span
                                 class="pointer"
                                 onClick={linkEvent(this, this.handleEditClick)}
+                                data-tippy-content={i18n.t('edit')}
                               >
-                                {i18n.t('edit')}
+                                <svg class="icon icon-inline">
+                                  <use xlinkHref="#icon-edit"></use>
+                                </svg>
                               </span>
                             </li>
-                            <li className="list-inline-item">
+                            <li className="list-inline-item-action">
                               <span
                                 class="pointer"
                                 onClick={linkEvent(
                                   this,
                                   this.handleDeleteClick
                                 )}
+                                data-tippy-content={
+                                  !node.comment.deleted
+                                    ? i18n.t('delete')
+                                    : i18n.t('restore')
+                                }
                               >
-                                {!node.comment.deleted
-                                  ? i18n.t('delete')
-                                  : i18n.t('restore')}
+                                <svg
+                                  class={`icon icon-inline ${node.comment
+                                    .deleted && 'text-danger'}`}
+                                >
+                                  <use xlinkHref="#icon-trash"></use>
+                                </svg>
                               </span>
                             </li>
                           </>
@@ -337,7 +398,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                         {/* Admins and mods can remove comments */}
                         {(this.canMod || this.canAdmin) && (
                           <>
-                            <li className="list-inline-item">
+                            <li className="list-inline-item-action">
                               {!node.comment.removed ? (
                                 <span
                                   class="pointer"
@@ -366,7 +427,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                         {this.canMod && (
                           <>
                             {!this.isMod && (
-                              <li className="list-inline-item">
+                              <li className="list-inline-item-action">
                                 {!node.comment.banned_from_community ? (
                                   <span
                                     class="pointer"
@@ -391,7 +452,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                               </li>
                             )}
                             {!node.comment.banned_from_community && (
-                              <li className="list-inline-item">
+                              <li className="list-inline-item-action">
                                 {!this.state.showConfirmAppointAsMod ? (
                                   <span
                                     class="pointer"
@@ -436,7 +497,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                         {/* Community creators and admins can transfer community to another mod */}
                         {(this.amCommunityCreator || this.canAdmin) &&
                           this.isMod && (
-                            <li className="list-inline-item">
+                            <li className="list-inline-item-action">
                               {!this.state.showConfirmTransferCommunity ? (
                                 <span
                                   class="pointer"
@@ -479,7 +540,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                         {this.canAdmin && (
                           <>
                             {!this.isAdmin && (
-                              <li className="list-inline-item">
+                              <li className="list-inline-item-action">
                                 {!node.comment.banned ? (
                                   <span
                                     class="pointer"
@@ -504,7 +565,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                               </li>
                             )}
                             {!node.comment.banned && (
-                              <li className="list-inline-item">
+                              <li className="list-inline-item-action">
                                 {!this.state.showConfirmAppointAsAdmin ? (
                                   <span
                                     class="pointer"
@@ -548,7 +609,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                         )}
                         {/* Site Creator can transfer to another admin */}
                         {this.amSiteCreator && this.isAdmin && (
-                          <li className="list-inline-item">
+                          <li className="list-inline-item-action">
                             {!this.state.showConfirmTransferSite ? (
                               <span
                                 class="pointer"
index 67386469e0dfe204f4ee1e746bf83c53217514d3..1c875d04d53e89408dad899e34a815a2d4282206 100644 (file)
@@ -208,6 +208,7 @@ export class Community extends Component<any, State> {
             SortType[this.state.sort]
           }`}
           target="_blank"
+          title="RSS"
         >
           <svg class="icon text-muted small">
             <use xlinkHref="#icon-rss">#</use>
index 4bae06d1c5d7cc09935b185b7acae6890f913d02..31929eafe244ab1e4a991ffdba67c52037a14929 100644 (file)
@@ -1,6 +1,7 @@
 import { Component, linkEvent } from 'inferno';
 import { FramelyData } from '../interfaces';
 import { mdToHtml } from '../utils';
+import { i18n } from '../i18next';
 
 interface FramelyCardProps {
   iframely: FramelyData;
@@ -54,6 +55,7 @@ export class IFramelyCard extends Component<
                       <span
                         class="ml-2 pointer text-monospace"
                         onClick={linkEvent(this, this.handleIframeExpand)}
+                        data-tippy-content={i18n.t('expand_here')}
                       >
                         {this.state.expanded ? '[-]' : '[+]'}
                       </span>
index 56bf15785b93838f63ebcab7fb5dbab9bc781aef..0d07dca5132c9b5dc650896d3afd602fffc8ea2e 100644 (file)
@@ -116,6 +116,7 @@ export class Inbox extends Component<any, InboxState> {
                 <a
                   href={`/feeds/inbox/${UserService.Instance.auth}.xml`}
                   target="_blank"
+                  title="RSS"
                 >
                   <svg class="icon mx-2 text-muted small">
                     <use xlinkHref="#icon-rss">#</use>
index 81612009619611ef205fe9f2630485bfc6a313a4..014d82d31500a6cca83e04350f2c506c923882d3 100644 (file)
@@ -441,6 +441,7 @@ export class Main extends Component<any, MainState> {
           <a
             href={`/feeds/all.xml?sort=${SortType[this.state.sort]}`}
             target="_blank"
+            title="RSS"
           >
             <svg class="icon mx-1 text-muted small">
               <use xlinkHref="#icon-rss">#</use>
@@ -454,6 +455,7 @@ export class Main extends Component<any, MainState> {
                 SortType[this.state.sort]
               }`}
               target="_blank"
+              title="RSS"
             >
               <svg class="icon mx-1 text-muted small">
                 <use xlinkHref="#icon-rss">#</use>
index fd2a7efadf10db0ba74f779b7d2900811472c2a7..a256f7858cbe854c37798b2e99e535812370f4c9 100644 (file)
@@ -1,6 +1,6 @@
 import { Component } from 'inferno';
 import moment from 'moment';
-import { getMomentLanguage } from '../utils';
+import { getMomentLanguage, setupTippy } from '../utils';
 import { i18n } from '../i18next';
 
 interface MomentTimeProps {
@@ -20,16 +20,37 @@ export class MomentTime extends Component<MomentTimeProps, any> {
     moment.locale(lang);
   }
 
+  componentDidMount() {
+    setupTippy();
+  }
+
   render() {
     if (this.props.data.updated) {
       return (
-        <span title={this.props.data.updated} className="font-italics">
+        <span
+          data-tippy-content={this.format(this.props.data.updated)}
+          className="font-italics pointer unselectable"
+        >
           {i18n.t('modified')} {moment.utc(this.props.data.updated).fromNow()}
         </span>
       );
     } else {
       let str = this.props.data.published || this.props.data.when_;
-      return <span title={str}>{moment.utc(str).fromNow()}</span>;
+      return (
+        <span
+          className="pointer unselectable"
+          data-tippy-content={this.format(str)}
+        >
+          {moment.utc(str).fromNow()}
+        </span>
+      );
     }
   }
+
+  format(input: string): string {
+    return moment
+      .utc(input)
+      .local()
+      .format('LLLL');
+  }
 }
index 75cdd55498e850fabbe10251634abcef028f091e..031c2ecbfb21f095329a72f9e5f222952bd9a138 100644 (file)
@@ -26,6 +26,7 @@ import {
   fetchLimit,
   isCommentType,
   toast,
+  setupTippy,
 } from '../utils';
 import { version } from '../version';
 import { i18n } from '../i18next';
@@ -84,6 +85,10 @@ export class Navbar extends Component<any, NavbarState> {
     WebSocketService.Instance.getSite();
   }
 
+  componentDidMount() {
+    setupTippy();
+  }
+
   render() {
     return this.navbar();
   }
@@ -105,6 +110,7 @@ export class Navbar extends Component<any, NavbarState> {
           type="button"
           aria-label="menu"
           onClick={linkEvent(this, this.expandNavbar)}
+          data-tippy-content={i18n.t('expand_here')}
         >
           <span class="navbar-toggler-icon"></span>
         </button>
@@ -113,12 +119,16 @@ export class Navbar extends Component<any, NavbarState> {
         >
           <ul class="navbar-nav mr-auto">
             <li class="nav-item">
-              <Link class="nav-link" to="/communities">
+              <Link
+                class="nav-link"
+                to="/communities"
+                title={i18n.t('communities')}
+              >
                 {i18n.t('communities')}
               </Link>
             </li>
             <li class="nav-item">
-              <Link class="nav-link" to="/search">
+              <Link class="nav-link" to="/search" title={i18n.t('search')}>
                 {i18n.t('search')}
               </Link>
             </li>
@@ -129,12 +139,17 @@ export class Navbar extends Component<any, NavbarState> {
                   pathname: '/create_post',
                   state: { prevPath: this.currentLocation },
                 }}
+                title={i18n.t('create_post')}
               >
                 {i18n.t('create_post')}
               </Link>
             </li>
             <li class="nav-item">
-              <Link class="nav-link" to="/create_community">
+              <Link
+                class="nav-link"
+                to="/create_community"
+                title={i18n.t('create_community')}
+              >
                 {i18n.t('create_community')}
               </Link>
             </li>
@@ -154,9 +169,9 @@ export class Navbar extends Component<any, NavbarState> {
             {this.state.isLoggedIn ? (
               <>
                 <li className="nav-item mt-1">
-                  <Link class="nav-link" to="/inbox">
+                  <Link class="nav-link" to="/inbox" title={i18n.t('inbox')}>
                     <svg class="icon">
-                      <use xlinkHref="#icon-mail"></use>
+                      <use xlinkHref="#icon-bell"></use>
                     </svg>
                     {this.state.unreadCount > 0 && (
                       <span class="ml-1 badge badge-light">
@@ -169,6 +184,7 @@ export class Navbar extends Component<any, NavbarState> {
                   <Link
                     class="nav-link"
                     to={`/u/${UserService.Instance.user.username}`}
+                    title={i18n.t('settings')}
                   >
                     <span>
                       {UserService.Instance.user.avatar && showAvatars() && (
@@ -187,7 +203,11 @@ export class Navbar extends Component<any, NavbarState> {
                 </li>
               </>
             ) : (
-              <Link class="nav-link" to="/login">
+              <Link
+                class="nav-link"
+                to="/login"
+                title={i18n.t('login_sign_up')}
+              >
                 {i18n.t('login_sign_up')}
               </Link>
             )}
index d502330b4b2540f2c6bdd661a86c0152c5b847d4..ef25a546bd949bbb65db25f592ff096fd17e0a41 100644 (file)
@@ -32,6 +32,7 @@ import {
   toast,
   randomStr,
   setupTribute,
+  setupTippy,
 } from '../utils';
 import autosize from 'autosize';
 import Tribute from 'tributejs/src/Tribute.js';
@@ -142,6 +143,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
       this.setState(this.state);
       autosize.update(textarea);
     });
+    setupTippy();
   }
 
   componentWillUnmount() {
@@ -179,9 +181,12 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
                 <label
                   htmlFor="file-upload"
                   className={`${UserService.Instance.user &&
-                    'pointer'} d-inline-block mr-2 float-right text-muted small font-weight-bold`}
+                    'pointer'} d-inline-block float-right text-muted h6 font-weight-bold`}
+                  data-tippy-content={i18n.t('upload_image')}
                 >
-                  {i18n.t('upload_image')}
+                  <svg class="icon icon-inline">
+                    <use xlinkHref="#icon-image"></use>
+                  </svg>
                 </label>
                 <input
                   id="file-upload"
@@ -279,9 +284,12 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
               <a
                 href={markdownHelpUrl}
                 target="_blank"
-                class="d-inline-block float-right text-muted small font-weight-bold"
+                class="d-inline-block float-right text-muted h6 font-weight-bold"
+                title={i18n.t('formatting_help')}
               >
-                {i18n.t('formatting_help')}
+                <svg class="icon icon-inline">
+                  <use xlinkHref="#icon-help-circle"></use>
+                </svg>
               </a>
             </div>
           </div>
index 732664e88c2f1a0ced7d12ae5671d5d726bd6d06..5f3fef09e0bd5d626026740065f4247b7876ce57 100644 (file)
@@ -30,6 +30,7 @@ import {
   pictshareAvatarThumbnail,
   showAvatars,
   imageThumbnailer,
+  setupTippy,
 } from '../utils';
 import { i18n } from '../i18next';
 
@@ -101,6 +102,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
     }
   }
 
+  componentDidUpdate() {
+    setupTippy();
+  }
+
   componentWillReceiveProps(nextProps: PostListingProps) {
     this.state.my_vote = nextProps.post.my_vote;
     this.state.upvotes = nextProps.post.upvotes;
@@ -185,7 +190,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
       return (
         <span
           class="text-body pointer"
-          title={i18n.t('expand_here')}
+          data-tippy-content={i18n.t('expand_here')}
           onClick={linkEvent(this, this.handleImageExpandClick)}
         >
           {this.imgThumb()}
@@ -246,12 +251,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
               this.state.my_vote == 1 ? 'text-info' : 'text-muted'
             }`}
             onClick={linkEvent(this, this.handlePostLike)}
+            data-tippy-content={i18n.t('upvote')}
           >
             <svg class="icon upvote">
               <use xlinkHref="#icon-arrow-up"></use>
             </svg>
           </button>
-          <div class={`font-weight-bold text-muted px-1`}>
+          <div class={`unselectable font-weight-bold text-muted px-1`}>
             {this.state.score}
           </div>
           {WebSocketService.Instance.site.enable_downvotes && (
@@ -260,6 +266,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
               }`}
               onClick={linkEvent(this, this.handlePostDisLike)}
+              data-tippy-content={i18n.t('downvote')}
             >
               <svg class="icon downvote">
                 <use xlinkHref="#icon-arrow-down"></use>
@@ -333,8 +340,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                   <>
                     {!this.state.imageExpanded ? (
                       <span
-                        class="text-monospace pointer ml-2 text-muted small"
-                        title={i18n.t('expand_here')}
+                        class="text-monospace unselectable pointer ml-2 text-muted small"
+                        data-tippy-content={i18n.t('expand_here')}
                         onClick={linkEvent(this, this.handleImageExpandClick)}
                       >
                         [+]
@@ -342,7 +349,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                     ) : (
                       <span>
                         <span
-                          class="text-monospace pointer ml-2 text-muted small"
+                          class="text-monospace unselectable pointer ml-2 text-muted small"
                           onClick={linkEvent(this, this.handleImageExpandClick)}
                         >
                           [-]
@@ -439,22 +446,37 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                   </span>
                 </li>
                 <li className="list-inline-item">
-                  <span>
-                    (<span className="text-info">+{this.state.upvotes}</span>
-                    <span> | </span>
-                    <span className="text-danger">-{this.state.downvotes}</span>
-                    <span>) </span>
+                  <span className="text-info">
+                    <svg class="small icon icon-inline mr-1">
+                      <use xlinkHref="#icon-arrow-up"></use>
+                    </svg>
+                    {this.state.upvotes}
                   </span>
                 </li>
                 <li className="list-inline-item">
-                  <Link className="text-muted" to={`/post/${post.id}`}>
-                    {i18n.t('number_of_comments', {
+                  <span className="text-danger">
+                    <svg class="small icon icon-inline mr-1">
+                      <use xlinkHref="#icon-arrow-down"></use>
+                    </svg>
+                    {this.state.downvotes}
+                  </span>
+                </li>
+                <li className="list-inline-item">
+                  <Link
+                    className="text-muted"
+                    title={i18n.t('number_of_comments', {
                       count: post.number_of_comments,
                     })}
+                    to={`/post/${post.id}`}
+                  >
+                    <svg class="mr-1 icon icon-inline">
+                      <use xlinkHref="#icon-message-square"></use>
+                    </svg>
+                    {post.number_of_comments}
                   </Link>
                 </li>
               </ul>
-              <ul class="list-inline mb-1 text-muted small">
+              <ul class="list-inline mb-1 text-muted">
                 {this.props.post.duplicates && (
                   <>
                     <li className="list-inline-item mr-2">
@@ -470,65 +492,90 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                   </>
                 )}
               </ul>
-              <ul class="list-inline mb-1 text-muted small font-weight-bold">
+              <ul class="list-inline mb-1 text-muted h6 font-weight-bold">
                 {UserService.Instance.user && (
                   <>
                     {this.props.showBody && (
                       <>
-                        <li className="list-inline-item mr-2">
+                        <li className="list-inline-item-action">
                           <span
                             class="pointer"
                             onClick={linkEvent(this, this.handleSavePostClick)}
+                            data-tippy-content={
+                              post.saved ? i18n.t('unsave') : i18n.t('save')
+                            }
                           >
-                            {post.saved ? i18n.t('unsave') : i18n.t('save')}
+                            <svg
+                              class={`icon icon-inline ${post.saved &&
+                                'text-warning'}`}
+                            >
+                              <use xlinkHref="#icon-star"></use>
+                            </svg>
                           </span>
                         </li>
-                        <li className="list-inline-item mr-2">
+                        <li className="list-inline-item-action">
                           <Link
                             className="text-muted"
                             to={`/create_post${this.crossPostParams}`}
+                            title={i18n.t('cross_post')}
                           >
-                            {i18n.t('cross_post')}
+                            <svg class="icon icon-inline">
+                              <use xlinkHref="#icon-copy"></use>
+                            </svg>
                           </Link>
                         </li>
                       </>
                     )}
                     {this.myPost && this.props.showBody && (
                       <>
-                        <li className="list-inline-item">
+                        <li className="list-inline-item-action">
                           <span
                             class="pointer"
                             onClick={linkEvent(this, this.handleEditClick)}
+                            data-tippy-content={i18n.t('edit')}
                           >
-                            {i18n.t('edit')}
+                            <svg class="icon icon-inline">
+                              <use xlinkHref="#icon-edit"></use>
+                            </svg>
                           </span>
                         </li>
-                        <li className="list-inline-item mr-2">
+                        <li className="list-inline-item-action">
                           <span
                             class="pointer"
                             onClick={linkEvent(this, this.handleDeleteClick)}
+                            data-tippy-content={
+                              !post.deleted
+                                ? i18n.t('delete')
+                                : i18n.t('restore')
+                            }
                           >
-                            {!post.deleted
-                              ? i18n.t('delete')
-                              : i18n.t('restore')}
+                            <svg
+                              class={`icon icon-inline ${post.deleted &&
+                                'text-danger'}`}
+                            >
+                              <use xlinkHref="#icon-trash"></use>
+                            </svg>
                           </span>
                         </li>
                       </>
                     )}
 
                     {!this.state.showAdvanced && this.props.showBody ? (
-                      <li className="list-inline-item">
+                      <li className="list-inline-item-action">
                         <span
                           className="pointer"
                           onClick={linkEvent(this, this.handleShowAdvanced)}
+                          data-tippy-content={i18n.t('more')}
                         >
-                          {i18n.t('more')}
+                          <svg class="icon icon-inline">
+                            <use xlinkHref="#icon-more-vertical"></use>
+                          </svg>
                         </span>
                       </li>
                     ) : (
                       <>
                         {this.props.showBody && post.body && (
-                          <li className="list-inline-item">
+                          <li className="list-inline-item-action">
                             <span
                               className="pointer"
                               onClick={linkEvent(this, this.handleViewSource)}
@@ -539,24 +586,40 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                         )}
                         {this.canModOnSelf && (
                           <>
-                            <li className="list-inline-item">
+                            <li className="list-inline-item-action">
                               <span
                                 class="pointer"
                                 onClick={linkEvent(this, this.handleModLock)}
+                                data-tippy-content={
+                                  post.locked
+                                    ? i18n.t('unlock')
+                                    : i18n.t('lock')
+                                }
                               >
-                                {post.locked
-                                  ? i18n.t('unlock')
-                                  : i18n.t('lock')}
+                                <svg
+                                  class={`icon icon-inline ${post.locked &&
+                                    'text-danger'}`}
+                                >
+                                  <use xlinkHref="#icon-lock"></use>
+                                </svg>
                               </span>
                             </li>
-                            <li className="list-inline-item">
+                            <li className="list-inline-item-action">
                               <span
                                 class="pointer"
                                 onClick={linkEvent(this, this.handleModSticky)}
+                                data-tippy-content={
+                                  post.stickied
+                                    ? i18n.t('unsticky')
+                                    : i18n.t('sticky')
+                                }
                               >
-                                {post.stickied
-                                  ? i18n.t('unsticky')
-                                  : i18n.t('sticky')}
+                                <svg
+                                  class={`icon icon-inline ${post.stickied &&
+                                    'text-success'}`}
+                                >
+                                  <use xlinkHref="#icon-pin"></use>
+                                </svg>
                               </span>
                             </li>
                           </>
index b56732632d6ef998fe557bd9cb25d8040e1b33a1..09efcf68feeb69ae79d44de8759abe9592fc90f5 100644 (file)
@@ -15,40 +15,66 @@ export class Symbols extends Component<any, any> {
         xmlnsXlink="http://www.w3.org/1999/xlink"
       >
         <defs>
+          <symbol id="icon-help-circle" viewBox="0 0 24 24">
+            <path d="M23 12c0-3.037-1.232-5.789-3.222-7.778s-4.741-3.222-7.778-3.222-5.789 1.232-7.778 3.222-3.222 4.741-3.222 7.778 1.232 5.789 3.222 7.778 4.741 3.222 7.778 3.222 5.789-1.232 7.778-3.222 3.222-4.741 3.222-7.778zM21 12c0 2.486-1.006 4.734-2.636 6.364s-3.878 2.636-6.364 2.636-4.734-1.006-6.364-2.636-2.636-3.878-2.636-6.364 1.006-4.734 2.636-6.364 3.878-2.636 6.364-2.636 4.734 1.006 6.364 2.636 2.636 3.878 2.636 6.364zM10.033 9.332c0.183-0.521 0.559-0.918 1.022-1.14s1.007-0.267 1.528-0.083c0.458 0.161 0.819 0.47 1.050 0.859 0.183 0.307 0.284 0.665 0.286 1.037 0 0.155-0.039 0.309-0.117 0.464-0.080 0.16-0.203 0.325-0.368 0.49-0.709 0.709-1.831 1.092-1.831 1.092-0.524 0.175-0.807 0.741-0.632 1.265s0.741 0.807 1.265 0.632c0 0 1.544-0.506 2.613-1.575 0.279-0.279 0.545-0.614 0.743-1.010 0.2-0.4 0.328-0.858 0.328-1.369-0.004-0.731-0.204-1.437-0.567-2.049-0.463-0.778-1.19-1.402-2.105-1.724-1.042-0.366-2.135-0.275-3.057 0.167s-1.678 1.238-2.044 2.28c-0.184 0.521 0.090 1.092 0.611 1.275s1.092-0.091 1.275-0.611zM12 18c0.552 0 1-0.448 1-1s-0.448-1-1-1-1 0.448-1 1 0.448 1 1 1z"></path>
+          </symbol>
+          <symbol id="icon-pin" viewBox="0 0 18 18">
+            <path d="M15 2v-1h-12v1c0 0.552 0.448 1 1 1v8c-0.552 0-1 0.448-1 1v1h5v3c0 0.552 0.448 1 1 1s1-0.448 1-1v-3h5v-1c0-0.552-0.448-1-1-1v-8c0.552 0 1-0.448 1-1zM12 11h-6v-8h6v8z"></path>
+          </symbol>
+          <symbol id="icon-lock" viewBox="0 0 24 24">
+            <path d="M5 12h14c0.276 0 0.525 0.111 0.707 0.293s0.293 0.431 0.293 0.707v7c0 0.276-0.111 0.525-0.293 0.707s-0.431 0.293-0.707 0.293h-14c-0.276 0-0.525-0.111-0.707-0.293s-0.293-0.431-0.293-0.707v-7c0-0.276 0.111-0.525 0.293-0.707s0.431-0.293 0.707-0.293zM18 10v-3c0-1.657-0.673-3.158-1.757-4.243s-2.586-1.757-4.243-1.757-3.158 0.673-4.243 1.757-1.757 2.586-1.757 4.243v3h-1c-0.828 0-1.58 0.337-2.121 0.879s-0.879 1.293-0.879 2.121v7c0 0.828 0.337 1.58 0.879 2.121s1.293 0.879 2.121 0.879h14c0.828 0 1.58-0.337 2.121-0.879s0.879-1.293 0.879-2.121v-7c0-0.828-0.337-1.58-0.879-2.121s-1.293-0.879-2.121-0.879zM8 10v-3c0-1.105 0.447-2.103 1.172-2.828s1.723-1.172 2.828-1.172 2.103 0.447 2.828 1.172 1.172 1.723 1.172 2.828v3z"></path>
+          </symbol>
+          <symbol id="icon-check" viewBox="0 0 24 24">
+            <path d="M19.293 5.293l-10.293 10.293-4.293-4.293c-0.391-0.391-1.024-0.391-1.414 0s-0.391 1.024 0 1.414l5 5c0.391 0.391 1.024 0.391 1.414 0l11-11c0.391-0.391 0.391-1.024 0-1.414s-1.024-0.391-1.414 0z"></path>
+          </symbol>
+          <symbol id="icon-copy" viewBox="0 0 24 24">
+            <path d="M11 8c-0.828 0-1.58 0.337-2.121 0.879s-0.879 1.293-0.879 2.121v9c0 0.828 0.337 1.58 0.879 2.121s1.293 0.879 2.121 0.879h9c0.828 0 1.58-0.337 2.121-0.879s0.879-1.293 0.879-2.121v-9c0-0.828-0.337-1.58-0.879-2.121s-1.293-0.879-2.121-0.879zM11 10h9c0.276 0 0.525 0.111 0.707 0.293s0.293 0.431 0.293 0.707v9c0 0.276-0.111 0.525-0.293 0.707s-0.431 0.293-0.707 0.293h-9c-0.276 0-0.525-0.111-0.707-0.293s-0.293-0.431-0.293-0.707v-9c0-0.276 0.111-0.525 0.293-0.707s0.431-0.293 0.707-0.293zM5 14h-1c-0.276 0-0.525-0.111-0.707-0.293s-0.293-0.431-0.293-0.707v-9c0-0.276 0.111-0.525 0.293-0.707s0.431-0.293 0.707-0.293h9c0.276 0 0.525 0.111 0.707 0.293s0.293 0.431 0.293 0.707v1c0 0.552 0.448 1 1 1s1-0.448 1-1v-1c0-0.828-0.337-1.58-0.879-2.121s-1.293-0.879-2.121-0.879h-9c-0.828 0-1.58 0.337-2.121 0.879s-0.879 1.293-0.879 2.121v9c0 0.828 0.337 1.58 0.879 2.121s1.293 0.879 2.121 0.879h1c0.552 0 1-0.448 1-1s-0.448-1-1-1z"></path>
+          </symbol>
+          <symbol id="icon-more-vertical" viewBox="0 0 24 24">
+            <path d="M14 12c0-0.552-0.225-1.053-0.586-1.414s-0.862-0.586-1.414-0.586-1.053 0.225-1.414 0.586-0.586 0.862-0.586 1.414 0.225 1.053 0.586 1.414 0.862 0.586 1.414 0.586 1.053-0.225 1.414-0.586 0.586-0.862 0.586-1.414zM14 5c0-0.552-0.225-1.053-0.586-1.414s-0.862-0.586-1.414-0.586-1.053 0.225-1.414 0.586-0.586 0.862-0.586 1.414 0.225 1.053 0.586 1.414 0.862 0.586 1.414 0.586 1.053-0.225 1.414-0.586 0.586-0.862 0.586-1.414zM14 19c0-0.552-0.225-1.053-0.586-1.414s-0.862-0.586-1.414-0.586-1.053 0.225-1.414 0.586-0.586 0.862-0.586 1.414 0.225 1.053 0.586 1.414 0.862 0.586 1.414 0.586 1.053-0.225 1.414-0.586 0.586-0.862 0.586-1.414z"></path>
+          </symbol>
+          <symbol id="icon-bell" viewBox="0 0 24 24">
+            <path d="M17 8c0 4.011 0.947 6.52 1.851 8h-13.702c0.904-1.48 1.851-3.989 1.851-8 0-1.381 0.559-2.63 1.464-3.536s2.155-1.464 3.536-1.464 2.63 0.559 3.536 1.464 1.464 2.155 1.464 3.536zM19 8c0-1.933-0.785-3.684-2.050-4.95s-3.017-2.050-4.95-2.050-3.684 0.785-4.95 2.050-2.050 3.017-2.050 4.95c0 6.127-2.393 8.047-2.563 8.174-0.453 0.308-0.573 0.924-0.269 1.381 0.192 0.287 0.506 0.443 0.832 0.445h18c0.552 0 1-0.448 1-1 0-0.339-0.168-0.638-0.429-0.821-0.176-0.13-2.571-2.050-2.571-8.179zM12.865 20.498c-0.139 0.239-0.359 0.399-0.608 0.465s-0.52 0.037-0.759-0.101c-0.162-0.094-0.283-0.222-0.359-0.357-0.274-0.48-0.884-0.647-1.364-0.373s-0.647 0.884-0.373 1.364c0.25 0.439 0.623 0.823 1.093 1.096 0.716 0.416 1.535 0.501 2.276 0.304s1.409-0.678 1.824-1.394c0.277-0.478 0.114-1.090-0.363-1.367s-1.090-0.114-1.367 0.363z"></path>
+          </symbol>
+          <symbol id="icon-eye" viewBox="0 0 24 24">
+            <path d="M0.106 11.553c-0.136 0.274-0.146 0.603 0 0.894 0 0 0.396 0.789 1.12 1.843 0.451 0.656 1.038 1.432 1.757 2.218 0.894 0.979 2.004 1.987 3.319 2.8 1.595 0.986 3.506 1.692 5.698 1.692s4.103-0.706 5.698-1.692c1.315-0.813 2.425-1.821 3.319-2.8 0.718-0.786 1.306-1.562 1.757-2.218 0.724-1.054 1.12-1.843 1.12-1.843 0.136-0.274 0.146-0.603 0-0.894 0 0-0.396-0.789-1.12-1.843-0.451-0.656-1.038-1.432-1.757-2.218-0.894-0.979-2.004-1.987-3.319-2.8-1.595-0.986-3.506-1.692-5.698-1.692s-4.103 0.706-5.698 1.692c-1.315 0.813-2.425 1.821-3.319 2.8-0.719 0.786-1.306 1.561-1.757 2.218-0.724 1.054-1.12 1.843-1.12 1.843zM2.14 12c0.163-0.281 0.407-0.681 0.734-1.158 0.41-0.596 0.94-1.296 1.585-2.001 0.805-0.881 1.775-1.756 2.894-2.448 1.35-0.834 2.901-1.393 4.647-1.393s3.297 0.559 4.646 1.393c1.119 0.692 2.089 1.567 2.894 2.448 0.644 0.705 1.175 1.405 1.585 2.001 0.328 0.477 0.572 0.876 0.734 1.158-0.163 0.281-0.407 0.681-0.734 1.158-0.41 0.596-0.94 1.296-1.585 2.001-0.805 0.881-1.775 1.756-2.894 2.448-1.349 0.834-2.9 1.393-4.646 1.393s-3.297-0.559-4.646-1.393c-1.119-0.692-2.089-1.567-2.894-2.448-0.644-0.705-1.175-1.405-1.585-2.001-0.328-0.477-0.572-0.877-0.735-1.158zM16 12c0-1.104-0.449-2.106-1.172-2.828s-1.724-1.172-2.828-1.172-2.106 0.449-2.828 1.172-1.172 1.724-1.172 2.828 0.449 2.106 1.172 2.828 1.724 1.172 2.828 1.172 2.106-0.449 2.828-1.172 1.172-1.724 1.172-2.828zM14 12c0 0.553-0.223 1.051-0.586 1.414s-0.861 0.586-1.414 0.586-1.051-0.223-1.414-0.586-0.586-0.861-0.586-1.414 0.223-1.051 0.586-1.414 0.861-0.586 1.414-0.586 1.051 0.223 1.414 0.586 0.586 0.861 0.586 1.414z"></path>
+          </symbol>
+          <symbol id="icon-edit" viewBox="0 0 24 24">
+            <path d="M11 3h-7c-0.828 0-1.58 0.337-2.121 0.879s-0.879 1.293-0.879 2.121v14c0 0.828 0.337 1.58 0.879 2.121s1.293 0.879 2.121 0.879h14c0.828 0 1.58-0.337 2.121-0.879s0.879-1.293 0.879-2.121v-7c0-0.552-0.448-1-1-1s-1 0.448-1 1v7c0 0.276-0.111 0.525-0.293 0.707s-0.431 0.293-0.707 0.293h-14c-0.276 0-0.525-0.111-0.707-0.293s-0.293-0.431-0.293-0.707v-14c0-0.276 0.111-0.525 0.293-0.707s0.431-0.293 0.707-0.293h7c0.552 0 1-0.448 1-1s-0.448-1-1-1zM17.793 1.793l-9.5 9.5c-0.122 0.121-0.217 0.28-0.263 0.465l-1 4c-0.039 0.15-0.042 0.318 0 0.485 0.134 0.536 0.677 0.862 1.213 0.728l4-1c0.167-0.041 0.33-0.129 0.465-0.263l9.5-9.5c0.609-0.609 0.914-1.41 0.914-2.207s-0.305-1.598-0.914-2.207-1.411-0.915-2.208-0.915-1.598 0.305-2.207 0.914zM19.207 3.207c0.219-0.219 0.504-0.328 0.793-0.328s0.574 0.109 0.793 0.328 0.328 0.504 0.328 0.793-0.109 0.574-0.328 0.793l-9.304 9.304-2.114 0.529 0.529-2.114z"></path>
+          </symbol>
+          <symbol id="icon-trash" viewBox="0 0 24 24">
+            <path d="M18 7v13c0 0.276-0.111 0.525-0.293 0.707s-0.431 0.293-0.707 0.293h-10c-0.276 0-0.525-0.111-0.707-0.293s-0.293-0.431-0.293-0.707v-13zM17 5v-1c0-0.828-0.337-1.58-0.879-2.121s-1.293-0.879-2.121-0.879h-4c-0.828 0-1.58 0.337-2.121 0.879s-0.879 1.293-0.879 2.121v1h-4c-0.552 0-1 0.448-1 1s0.448 1 1 1h1v13c0 0.828 0.337 1.58 0.879 2.121s1.293 0.879 2.121 0.879h10c0.828 0 1.58-0.337 2.121-0.879s0.879-1.293 0.879-2.121v-13h1c0.552 0 1-0.448 1-1s-0.448-1-1-1zM9 5v-1c0-0.276 0.111-0.525 0.293-0.707s0.431-0.293 0.707-0.293h4c0.276 0 0.525 0.111 0.707 0.293s0.293 0.431 0.293 0.707v1z"></path>
+          </symbol>
+          <symbol id="icon-reply1" viewBox="0 0 20 20">
+            <path d="M15 17v-2.99c0-0.003 0-0.006 0-0.010 0-2.209-1.791-4-4-4 0 0-0 0-0 0h-3v5l-6-6 6-6v5h3c3.314 0 6 2.686 6 6v0 3h-2z"></path>
+          </symbol>
+          <symbol id="icon-star" viewBox="0 0 24 24">
+            <path d="M12.897 1.557c-0.092-0.189-0.248-0.352-0.454-0.454-0.495-0.244-1.095-0.041-1.339 0.454l-2.858 5.789-6.391 0.935c-0.208 0.029-0.411 0.127-0.571 0.291-0.386 0.396-0.377 1.029 0.018 1.414l4.623 4.503-1.091 6.362c-0.036 0.207-0.006 0.431 0.101 0.634 0.257 0.489 0.862 0.677 1.351 0.42l5.714-3.005 5.715 3.005c0.186 0.099 0.408 0.139 0.634 0.101 0.544-0.093 0.91-0.61 0.817-1.155l-1.091-6.362 4.623-4.503c0.151-0.146 0.259-0.344 0.292-0.572 0.080-0.546-0.298-1.054-0.845-1.134l-6.39-0.934zM12 4.259l2.193 4.444c0.151 0.305 0.436 0.499 0.752 0.547l4.906 0.717-3.549 3.457c-0.244 0.238-0.341 0.569-0.288 0.885l0.837 4.883-4.386-2.307c-0.301-0.158-0.647-0.148-0.931 0l-4.386 2.307 0.837-4.883c0.058-0.336-0.059-0.661-0.288-0.885l-3.549-3.457 4.907-0.718c0.336-0.049 0.609-0.26 0.752-0.546z"></path>
+          </symbol>
           <symbol id="icon-message-square" viewBox="0 0 24 24">
-            <title>message-square</title>
             <path d="M22 15v-10c0-0.828-0.337-1.58-0.879-2.121s-1.293-0.879-2.121-0.879h-14c-0.828 0-1.58 0.337-2.121 0.879s-0.879 1.293-0.879 2.121v16c0 0.256 0.098 0.512 0.293 0.707 0.391 0.391 1.024 0.391 1.414 0l3.707-3.707h11.586c0.828 0 1.58-0.337 2.121-0.879s0.879-1.293 0.879-2.121zM20 15c0 0.276-0.111 0.525-0.293 0.707s-0.431 0.293-0.707 0.293h-12c-0.276 0-0.526 0.112-0.707 0.293l-2.293 2.293v-13.586c0-0.276 0.111-0.525 0.293-0.707s0.431-0.293 0.707-0.293h14c0.276 0 0.525 0.111 0.707 0.293s0.293 0.431 0.293 0.707z"></path>
           </symbol>
-          <symbol id="icon-image" viewBox="0 0 32 32">
-            <title>image</title>
-            <path d="M29.996 4c0.001 0.001 0.003 0.002 0.004 0.004v23.993c-0.001 0.001-0.002 0.003-0.004 0.004h-27.993c-0.001-0.001-0.003-0.002-0.004-0.004v-23.993c0.001-0.001 0.002-0.003 0.004-0.004h27.993zM30 2h-28c-1.1 0-2 0.9-2 2v24c0 1.1 0.9 2 2 2h28c1.1 0 2-0.9 2-2v-24c0-1.1-0.9-2-2-2v0z"></path>
-            <path d="M26 9c0 1.657-1.343 3-3 3s-3-1.343-3-3 1.343-3 3-3 3 1.343 3 3z"></path>
-            <path d="M28 26h-24v-4l7-12 8 10h2l7-6z"></path>
+          <symbol id="icon-image" viewBox="0 0 24 24">
+            <path d="M5 2c-0.828 0-1.58 0.337-2.121 0.879s-0.879 1.293-0.879 2.121v14c0 0.828 0.337 1.58 0.879 2.121s1.293 0.879 2.121 0.879h14c0.828 0 1.58-0.337 2.121-0.879s0.879-1.293 0.879-2.121v-14c0-0.828-0.337-1.58-0.879-2.121s-1.293-0.879-2.121-0.879zM11 8.5c0-0.69-0.281-1.316-0.732-1.768s-1.078-0.732-1.768-0.732-1.316 0.281-1.768 0.732-0.732 1.078-0.732 1.768 0.281 1.316 0.732 1.768 1.078 0.732 1.768 0.732 1.316-0.281 1.768-0.732 0.732-1.078 0.732-1.768zM9 8.5c0 0.138-0.055 0.262-0.146 0.354s-0.216 0.146-0.354 0.146-0.262-0.055-0.354-0.146-0.146-0.216-0.146-0.354 0.055-0.262 0.146-0.354 0.216-0.146 0.354-0.146 0.262 0.055 0.354 0.146 0.146 0.216 0.146 0.354zM7.414 20l8.586-8.586 4 4v3.586c0 0.276-0.111 0.525-0.293 0.707s-0.431 0.293-0.707 0.293zM20 12.586l-3.293-3.293c-0.391-0.391-1.024-0.391-1.414 0l-10.644 10.644c-0.135-0.050-0.255-0.129-0.356-0.23-0.182-0.182-0.293-0.431-0.293-0.707v-14c0-0.276 0.111-0.525 0.293-0.707s0.431-0.293 0.707-0.293h14c0.276 0 0.525 0.111 0.707 0.293s0.293 0.431 0.293 0.707z"></path>
           </symbol>
           <symbol id="icon-external-link" viewBox="0 0 24 24">
-            <title>external-link</title>
             <path d="M17 13v6c0 0.276-0.111 0.525-0.293 0.707s-0.431 0.293-0.707 0.293h-11c-0.276 0-0.525-0.111-0.707-0.293s-0.293-0.431-0.293-0.707v-11c0-0.276 0.111-0.525 0.293-0.707s0.431-0.293 0.707-0.293h6c0.552 0 1-0.448 1-1s-0.448-1-1-1h-6c-0.828 0-1.58 0.337-2.121 0.879s-0.879 1.293-0.879 2.121v11c0 0.828 0.337 1.58 0.879 2.121s1.293 0.879 2.121 0.879h11c0.828 0 1.58-0.337 2.121-0.879s0.879-1.293 0.879-2.121v-6c0-0.552-0.448-1-1-1s-1 0.448-1 1zM10.707 14.707l9.293-9.293v3.586c0 0.552 0.448 1 1 1s1-0.448 1-1v-6c0-0.136-0.027-0.265-0.076-0.383s-0.121-0.228-0.216-0.323c-0.001-0.001-0.001-0.001-0.002-0.002-0.092-0.092-0.202-0.166-0.323-0.216-0.118-0.049-0.247-0.076-0.383-0.076h-6c-0.552 0-1 0.448-1 1s0.448 1 1 1h3.586l-9.293 9.293c-0.391 0.391-0.391 1.024 0 1.414s1.024 0.391 1.414 0z"></path>
           </symbol>
           <symbol id="icon-coffee" viewBox="0 0 24 24">
-            <title>coffee1</title>
             <path d="M17 19h-12c-0.553 0-1-0.447-1-1s0.447-1 1-1h12c0.553 0 1 0.447 1 1s-0.447 1-1 1z"></path>
             <path d="M17.5 5h-12.5v9c0 1.1 0.9 2 2 2h8c1.1 0 2-0.9 2-2v-2h0.5c1.93 0 3.5-1.57 3.5-3.5s-1.57-3.5-3.5-3.5zM15 14h-8v-7h8v7zM17.5 10h-1.5v-3h1.5c0.827 0 1.5 0.673 1.5 1.5s-0.673 1.5-1.5 1.5z"></path>
           </symbol>
           <symbol id="icon-rss" viewBox="0 0 32 32">
-            <title>rss</title>
             <path d="M4.259 23.467c-2.35 0-4.259 1.917-4.259 4.252 0 2.349 1.909 4.244 4.259 4.244 2.358 0 4.265-1.895 4.265-4.244-0-2.336-1.907-4.252-4.265-4.252zM0.005 10.873v6.133c3.993 0 7.749 1.562 10.577 4.391 2.825 2.822 4.384 6.595 4.384 10.603h6.16c-0-11.651-9.478-21.127-21.121-21.127zM0.012 0v6.136c14.243 0 25.836 11.604 25.836 25.864h6.152c0-17.64-14.352-32-31.988-32z"></path>
           </symbol>
           <symbol id="icon-arrow-down" viewBox="0 0 26 28">
-            <title>arrow-down</title>
             <path d="M25.172 13c0 0.531-0.219 1.047-0.578 1.406l-10.172 10.187c-0.375 0.359-0.891 0.578-1.422 0.578s-1.047-0.219-1.406-0.578l-10.172-10.187c-0.375-0.359-0.594-0.875-0.594-1.406s0.219-1.047 0.594-1.422l1.156-1.172c0.375-0.359 0.891-0.578 1.422-0.578s1.047 0.219 1.406 0.578l4.594 4.594v-11c0-1.094 0.906-2 2-2h2c1.094 0 2 0.906 2 2v11l4.594-4.594c0.359-0.359 0.875-0.578 1.406-0.578s1.047 0.219 1.422 0.578l1.172 1.172c0.359 0.375 0.578 0.891 0.578 1.422z"></path>
           </symbol>
           <symbol id="icon-arrow-up" viewBox="0 0 26 28">
-            <title>arrow-up</title>
             <path d="M25.172 15.172c0 0.531-0.219 1.031-0.578 1.406l-1.172 1.172c-0.375 0.375-0.891 0.594-1.422 0.594s-1.047-0.219-1.406-0.594l-4.594-4.578v11c0 1.125-0.938 1.828-2 1.828h-2c-1.062 0-2-0.703-2-1.828v-11l-4.594 4.578c-0.359 0.375-0.875 0.594-1.406 0.594s-1.047-0.219-1.406-0.594l-1.172-1.172c-0.375-0.375-0.594-0.875-0.594-1.406s0.219-1.047 0.594-1.422l10.172-10.172c0.359-0.375 0.875-0.578 1.406-0.578s1.047 0.203 1.422 0.578l10.172 10.172c0.359 0.375 0.578 0.891 0.578 1.422z"></path>
           </symbol>
-          <symbol id="icon-mail" viewBox="0 0 32 32">
-            <title>mail</title>
-            <path d="M28 5h-24c-2.209 0-4 1.792-4 4v13c0 2.209 1.791 4 4 4h24c2.209 0 4-1.791 4-4v-13c0-2.208-1.791-4-4-4zM2 10.25l6.999 5.25-6.999 5.25v-10.5zM30 22c0 1.104-0.898 2-2 2h-24c-1.103 0-2-0.896-2-2l7.832-5.875 4.368 3.277c0.533 0.398 1.166 0.6 1.8 0.6 0.633 0 1.266-0.201 1.799-0.6l4.369-3.277 7.832 5.875zM30 20.75l-7-5.25 7-5.25v10.5zM17.199 18.602c-0.349 0.262-0.763 0.4-1.199 0.4s-0.851-0.139-1.2-0.4l-12.8-9.602c0-1.103 0.897-2 2-2h24c1.102 0 2 0.897 2 2l-12.801 9.602z"></path>
+          <symbol id="icon-mail" viewBox="0 0 24 24">
+            <path d="M3 7.921l8.427 5.899c0.34 0.235 0.795 0.246 1.147 0l8.426-5.899v10.079c0 0.272-0.11 0.521-0.295 0.705s-0.433 0.295-0.705 0.295h-16c-0.272 0-0.521-0.11-0.705-0.295s-0.295-0.433-0.295-0.705zM1 5.983c0 0.010 0 0.020 0 0.030v11.987c0 0.828 0.34 1.579 0.88 2.12s1.292 0.88 2.12 0.88h16c0.828 0 1.579-0.34 2.12-0.88s0.88-1.292 0.88-2.12v-11.988c0-0.010 0-0.020 0-0.030-0.005-0.821-0.343-1.565-0.88-2.102-0.541-0.54-1.292-0.88-2.12-0.88h-16c-0.828 0-1.579 0.34-2.12 0.88-0.537 0.537-0.875 1.281-0.88 2.103zM20.894 5.554l-8.894 6.225-8.894-6.225c0.048-0.096 0.112-0.183 0.188-0.259 0.185-0.185 0.434-0.295 0.706-0.295h16c0.272 0 0.521 0.11 0.705 0.295 0.076 0.076 0.14 0.164 0.188 0.259z"></path>
           </symbol>
           <symbol
             id="icon-mouse"
@@ -95,15 +121,12 @@ export class Symbols extends Component<any, any> {
             </g>
           </symbol>
           <symbol id="icon-search" viewBox="0 0 32 32">
-            <title>search</title>
             <path d="M31.008 27.231l-7.58-6.447c-0.784-0.705-1.622-1.029-2.299-0.998 1.789-2.096 2.87-4.815 2.87-7.787 0-6.627-5.373-12-12-12s-12 5.373-12 12 5.373 12 12 12c2.972 0 5.691-1.081 7.787-2.87-0.031 0.677 0.293 1.515 0.998 2.299l6.447 7.58c1.104 1.226 2.907 1.33 4.007 0.23s0.997-2.903-0.23-4.007zM12 20c-4.418 0-8-3.582-8-8s3.582-8 8-8 8 3.582 8 8-3.582 8-8 8z"></path>
           </symbol>
           <symbol id="icon-github" viewBox="0 0 32 32">
-            <title>github</title>
             <path d="M16 0.395c-8.836 0-16 7.163-16 16 0 7.069 4.585 13.067 10.942 15.182 0.8 0.148 1.094-0.347 1.094-0.77 0-0.381-0.015-1.642-0.022-2.979-4.452 0.968-5.391-1.888-5.391-1.888-0.728-1.849-1.776-2.341-1.776-2.341-1.452-0.993 0.11-0.973 0.11-0.973 1.606 0.113 2.452 1.649 2.452 1.649 1.427 2.446 3.743 1.739 4.656 1.33 0.143-1.034 0.558-1.74 1.016-2.14-3.554-0.404-7.29-1.777-7.29-7.907 0-1.747 0.625-3.174 1.649-4.295-0.166-0.403-0.714-2.030 0.155-4.234 0 0 1.344-0.43 4.401 1.64 1.276-0.355 2.645-0.532 4.005-0.539 1.359 0.006 2.729 0.184 4.008 0.539 3.054-2.070 4.395-1.64 4.395-1.64 0.871 2.204 0.323 3.831 0.157 4.234 1.026 1.12 1.647 2.548 1.647 4.295 0 6.145-3.743 7.498-7.306 7.895 0.574 0.497 1.085 1.47 1.085 2.963 0 2.141-0.019 3.864-0.019 4.391 0 0.426 0.288 0.925 1.099 0.768 6.354-2.118 10.933-8.113 10.933-15.18 0-8.837-7.164-16-16-16z"></path>
           </symbol>
           <symbol id="icon-spinner" viewBox="0 0 32 32">
-            <title>spinner</title>
             <path d="M16 32c-4.274 0-8.292-1.664-11.314-4.686s-4.686-7.040-4.686-11.314c0-3.026 0.849-5.973 2.456-8.522 1.563-2.478 3.771-4.48 6.386-5.791l1.344 2.682c-2.126 1.065-3.922 2.693-5.192 4.708-1.305 2.069-1.994 4.462-1.994 6.922 0 7.168 5.832 13 13 13s13-5.832 13-13c0-2.459-0.69-4.853-1.994-6.922-1.271-2.015-3.066-3.643-5.192-4.708l1.344-2.682c2.615 1.31 4.824 3.313 6.386 5.791 1.607 2.549 2.456 5.495 2.456 8.522 0 4.274-1.664 8.292-4.686 11.314s-7.040 4.686-11.314 4.686z"></path>
           </symbol>
         </defs>
index e2df15e1405194886b423d71e489057e684fd4a3..e97d7b08d8e2910e3c04110c8ae1fd1a1b410c31 100644 (file)
@@ -267,6 +267,7 @@ export class User extends Component<any, UserState> {
             SortType[this.state.sort]
           }`}
           target="_blank"
+          title="RSS"
         >
           <svg class="icon mx-2 text-muted small">
             <use xlinkHref="#icon-rss">#</use>
index 24bed724726fec167345f49c8867fd6260dbabe4..f39773d02ef27db83762a040bdd49cad53b01364 100644 (file)
@@ -14,6 +14,7 @@
     <link rel="stylesheet" type="text/css" href="/static/assets/css/tribute.css" />
     <link rel="stylesheet" type="text/css" href="/static/assets/css/toastify.css" />
     <link rel="stylesheet" type="text/css" href="/static/assets/css/selectr.min.css" />
+    <link rel="stylesheet" type="text/css" href="/static/assets/css/tippy.css" />
     <link rel="stylesheet" type="text/css" href="/static/assets/css/themes/darkly.min.css" id="darkly" />
     <link rel="stylesheet" type="text/css" href="/static/assets/css/main.css" />
 
index 6610572069982af8367d198f96cc24e9054987d9..7ef3e303e65f72fa7b2712eeb925c26a66e947b2 100644 (file)
@@ -41,6 +41,7 @@ import markdown_it_container from 'markdown-it-container';
 import twemoji from 'twemoji';
 import emojiShortName from 'emoji-short-name';
 import Toastify from 'toastify-js';
+import tippy from 'tippy.js';
 
 export const repoUrl = 'https://github.com/dessalines/lemmy';
 export const markdownHelpUrl = '/docs/about_guide.html';
@@ -477,6 +478,17 @@ export function setupTribute(): Tribute {
   });
 }
 
+let tippyInstance = tippy('[data-tippy-content]');
+
+export function setupTippy() {
+  tippyInstance.forEach(e => e.destroy());
+  tippyInstance = tippy('[data-tippy-content]', {
+    delay: [500, 0],
+    // Display on "long press"
+    touch: ['hold', 500],
+  });
+}
+
 function userSearch(text: string, cb: any) {
   if (text) {
     let form: SearchForm = {
index 8b719c712875f7a195b744afd26430f7701e98b1..1f959396aeba9139b8c61c7a587bc70156d6e924 100644 (file)
     "browser_default": "Browser Default",
     "downvotes_disabled": "Downvotes disabled",
     "enable_downvotes": "Enable Downvotes",
+    "upvote": "Upvote",
+    "downvote": "Downvote",
     "open_registration": "Open Registration",
     "registration_closed": "Registration closed",
     "enable_nsfw": "Enable NSFW",
index 601f3067ac3ff363e8c0a88aa2d9ebeb631dcfb0..6c1f222b24198d3d619400d1d7b4f55d193690e8 100644 (file)
     lodash "^4.17.13"
     to-fast-properties "^2.0.0"
 
+"@popperjs/core@^2.0.6":
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.0.6.tgz#5a39ac118811ca844155b0ad5190b8c24f35e533"
+  integrity sha512-zj7Gw8QC4jmR92eKUvtrZUEpl2ypRbq+qlE4pwf9n2hnUO9BOAcWUs4/Ht+gNIbFt98xtqhLvccdCfD469MzpQ==
+
 "@samverschueren/stream-to-observable@^0.3.0":
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
@@ -4432,6 +4437,13 @@ tiny-warning@^1.0.0:
   resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
   integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
 
+tippy.js@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.0.0.tgz#6bc4f477ea82ef344db51ae34f106a8b25f2d02c"
+  integrity sha512-2NVc5A8cnO9N/Fk+tTU6KEm6m8ZXT1u3pOY756zZ5BE38rWDl/hVyoWhTfM79HW1nEJSpn/VujqAMMZGHsE9qQ==
+  dependencies:
+    "@popperjs/core" "^2.0.6"
+
 tmp@^0.0.33:
   version "0.0.33"
   resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"