Browse Source

Implemented attachment preview in ticket zoom and optional an dedicated sidebar to show all attachments if whole ticket. Thanks to @mrflix.

Martin Edenhofer 7 years ago
parent
commit
b0597ad04d

+ 4 - 3
app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee

@@ -82,6 +82,7 @@ class ArticleViewItem extends App.ObserverController
     'click .textBubble a':         'stopPropagation'
     'click .js-toggleFold':        'toggleFold'
     'click .richtext-content img': 'imageView'
+    'click .attachments img':      'imageView'
 
   constructor: ->
     super
@@ -178,9 +179,9 @@ class ArticleViewItem extends App.ObserverController
       )
       return
     @html App.view('ticket_zoom/article_view')(
-      ticket:     @ticket
-      article:    article
-      isCustomer: @permissionCheck('ticket.customer')
+      ticket:                @ticket
+      article:               article
+      isCustomer:            @permissionCheck('ticket.customer')
     )
 
     new App.WidgetAvatar(

+ 34 - 0
app/assets/javascripts/app/controllers/ticket_zoom/sidebar_article_attachments.coffee

@@ -0,0 +1,34 @@
+class SidebarArticleAttachments extends App.Controller
+  sidebarItem: =>
+    return if !@Config.get('ui_ticket_zoom_sidebar_article_attachments')
+    @item = {
+      name: 'attachment'
+      badgeIcon: 'paperclip'
+      sidebarHead: 'Attachments'
+      sidebarCallback: @showObjects
+      sidebarActions: []
+    }
+    @item
+
+  showObjects: (el) =>
+    @el = el
+
+    if !@ticket || _.isEmpty(@ticket.article_ids)
+      @el.html("<div>#{App.i18n.translateInline('none')}</div>")
+      return
+    html = ''
+    for ticket_article_id, index in @ticket.article_ids
+      article = App.TicketArticle.find(ticket_article_id)
+      if article && article.attachments && !_.isEmpty(article.attachments)
+        html = App.view('ticket_zoom/sidebar_article_attachment')(article: article) + html
+    @el.html(html)
+    @el.delegate('.js-attachments img', 'click', (e) =>
+      @imageView(e)
+    )
+
+  imageView: (e) ->
+    e.preventDefault()
+    e.stopPropagation()
+    new App.TicketZoomArticleImageView(image: $(e.target).get(0).outerHTML)
+
+App.Config.set('900-ArticleAttachments', SidebarArticleAttachments, 'TicketZoomSidebar')

+ 37 - 0
app/assets/javascripts/app/index.coffee

@@ -136,6 +136,43 @@ class App extends Spine.Controller
         return marked(string)
       App.i18n.translateContent(string)
 
+    ContentTypeIcon: (contentType) ->
+      icons =
+        # image
+        'image/jpeg': 'file-image'
+        'image/jpg': 'file-image'
+        'image/png': 'file-image'
+        'image/svg': 'file-image'
+        # documents
+        'application/pdf': 'file-pdf'
+        'application/msword': 'file-word' # .doc, .dot
+        'application/vnd.ms-word': 'file-word'
+        'application/vnd.oasis.opendocument.text': 'file-word'
+        'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'file-word' # .docx
+        'application/vnd.openxmlformats-officedocument.wordprocessingml.template': 'file-word' # .dotx
+        'application/vnd.ms-excel': 'file-excel' # .xls
+        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'file-excel' # .xlsx
+        'application/vnd.oasis.opendocument.spreadsheet': 'file-excel'
+        'application/vnd.ms-powerpoint': 'file-powerpoint' # .ppt
+        'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'file-powerpoint' # .pptx
+        'application/vnd.oasis.opendocument.presentation': 'file-powerpoint'
+        'text/plain': 'file-text'
+        'text/html': 'file-code'
+        'application/json': 'file-code'
+        'message/rfc822': 'file-email'
+        # code
+        'application/json': 'file-code'
+        # text
+        'text/plain': 'file-text'
+        'text/rtf': 'file-text'
+        # archives
+        'application/gzip': 'file-archive'
+        'application/zip': 'file-archive'
+      return icons[contentType]
+
+    canDownload: (contentType) ->
+      contentType != 'text/html'
+
   @viewPrint: (object, attributeName, attributes, table) ->
     if !attributes
       attributes = {}

+ 21 - 4
app/assets/javascripts/app/views/ticket_zoom/article_view.jst.eco

@@ -56,10 +56,27 @@
             <%- @Icon('paperclip') %>
             <div class="attachments-title"><%- @article.attachments.length %> <%- @T('Attached Files') %></div>
             <% for attachment in @article.attachments: %>
-              <div class="attachment">
-                <a class="attachment-name u-highlight" href="<%= App.Config.get('api_path') %>/ticket_attachment/<%= @article.ticket_id %>/<%= @article.id %>/<%= attachment.id %>?disposition=attachment" target="_blank" data-type="attachment"><%= attachment.filename %></a>
-                <div class="attachment-size"><%- @humanFileSize(attachment.size) %></div>
-              </div>
+              <% if !@C('ui_ticket_zoom_attachments_preview'): %>
+                <div class="attachment">
+                  <a class="attachment-name u-highlight" href="<%= App.Config.get('api_path') %>/ticket_attachment/<%= @article.ticket_id %>/<%= @article.id %>/<%= attachment.id %>?disposition=attachment" target="_blank" data-type="attachment"><%= attachment.filename %></a>
+                </div>
+              <% else: %>
+                <a class="attachment attachment--preview" title="<%- attachment.preferences['Content-Type'] %>" target="_blank" href="<%= App.Config.get('api_path') %>/ticket_attachment/<%= @article.ticket_id %>/<%= @article.id %>/<%= attachment.id %>?disposition=attachment" data-type="attachment"<% if @canDownload(attachment.preferences['Content-Type']): %> download<% end %>>
+                  <div class="attachment-icon">
+                  <% if attachment.preferences && attachment.preferences['Content-Type'] && @ContentTypeIcon(attachment.preferences['Content-Type']): %>
+                    <% if attachment.preferences['Content-Type'].match(/image\/(png|jpg|jpeg)/i): %>
+                    <img src="<%= App.Config.get('api_path') %>/ticket_attachment/<%= @article.ticket_id %>/<%= @article.id %>/<%= attachment.id %>">
+                    <% else: %>
+                    <%- @Icon( @ContentTypeIcon(attachment.preferences['Content-Type']) ) %>
+                    <% end %>
+                  <% else: %>
+                    <%- @Icon('file-unknown') %>
+                  <% end %>
+                  </div>
+                  <span class="attachment-name u-highlight"><%= attachment.filename %></span>
+                  <div class="attachment-size"><%- @humanFileSize(attachment.size) %></div>
+                </a>
+              <% end %>
             <% end %>
           </div>
         <% end %>

+ 20 - 0
app/assets/javascripts/app/views/ticket_zoom/sidebar_article_attachment.jst.eco

@@ -0,0 +1,20 @@
+<div class="attachments-block js-attachments">
+  <div class="attachments-block-headline"><%- @humanTime(@article.created_at) %></div>
+  <% for attachment in @article.attachments: %>
+    <a class="attachment attachment--preview" title="<%- attachment.preferences['Content-Type'] %>" target="_blank" href="<%= App.Config.get('api_path') %>/ticket_attachment/<%= @article.ticket_id %>/<%= @article.id %>/<%= attachment.id %>?disposition=attachment" data-type="attachment"<% if @canDownload(attachment.preferences['Content-Type']): %> download<% end %>>
+      <div class="attachment-icon">
+      <% if attachment.preferences && attachment.preferences['Content-Type'] && @ContentTypeIcon(attachment.preferences['Content-Type']): %>
+        <% if attachment.preferences['Content-Type'].match(/image\/(png|jpg|jpeg)/i): %>
+        <img src="<%= App.Config.get('api_path') %>/ticket_attachment/<%= @article.ticket_id %>/<%= @article.id %>/<%= attachment.id %>">
+        <% else: %>
+        <%- @Icon( @ContentTypeIcon(attachment.preferences['Content-Type']) ) %>
+        <% end %>
+      <% else: %>
+        <%- @Icon('file-unknown') %>
+      <% end %>
+      </div>
+      <span class="attachment-name u-highlight"><%= attachment.filename %></span>
+      <div class="attachment-size"><%- @humanFileSize(attachment.size) %></div>
+    </a>
+  <% end %>
+</div>

+ 8 - 0
app/assets/stylesheets/svg-dimensions.css

@@ -22,6 +22,14 @@
 .icon-eyedropper { width: 17px; height: 17px; }
 .icon-facebook-button { width: 29px; height: 24px; }
 .icon-facebook { width: 17px; height: 17px; }
+.icon-file-archive { width: 24px; height: 31px; }
+.icon-file-code { width: 24px; height: 31px; }
+.icon-file-email { width: 24px; height: 31px; }
+.icon-file-excel { width: 24px; height: 31px; }
+.icon-file-pdf { width: 24px; height: 31px; }
+.icon-file-powerpoint { width: 24px; height: 31px; }
+.icon-file-unknown { width: 24px; height: 31px; }
+.icon-file-word { width: 24px; height: 31px; }
 .icon-form { width: 17px; height: 17px; }
 .icon-forward { width: 16px; height: 17px; }
 .icon-full-logo { width: 175px; height: 50px; }

+ 53 - 15
app/assets/stylesheets/zammad.scss

@@ -4957,17 +4957,34 @@ footer {
     .attachments.attachments--list .attachments-title {
       font-size: 13px;
       color: hsl(60,1%,34%);
-      font-weight: bold;
+      font-weight: 500;
+      text-transform: uppercase;
       padding: 0 7px;
-      margin: 0 0 4px;
     }
 
     .attachments .icon-paperclip {
       position: absolute;
       left: 33px;
       top: 27px;
+      fill: hsl(240,1%,84%);
     }
 
+  .attachments-block {
+    margin-bottom: 12px;
+    
+    &:last-child {
+      margin-bottom: 0;
+    }
+
+    &-headline {
+      font-size: 13px;
+      color: hsl(60,1%,34%);
+      font-weight: 500;
+      text-transform: uppercase;
+      margin: 0 7px;
+    }
+  }
+
   .ticket-article-item .task-subline {
     margin-top: 12px;
   }
@@ -5281,11 +5298,7 @@ footer {
       border: none;
       outline: none;
       resize: none;
-    }
-
-    .articleNewEdit-body {
       height: auto;
-      min-height: 38px;
     }
 
     .article-new .bubble-arrow:after {
@@ -5312,28 +5325,53 @@ footer {
     }
 
     .attachment {
+      display: block;
       font-size: 13px;
       padding: 1px 10px 1px 7px;
-      @include rtl(padding, 1px 7px 1px 10px);
-      cursor: default;
       position: relative;
-      display: flex;
+      min-height: 42px;
+      color: inherit;
+      align-items: center;
+      border-bottom: 1px solid hsl(0,0%,96%);
+      
+      &:last-child {
+        border-bottom: none;
+      }
     }
 
-    .attachment:hover {
-      background: hsl(200,20%,97%);
-    }
+      .attachment--preview {
+        padding: 9px 4px 9px 43px;
+      }
+
+      .attachment-icon {
+        position: absolute;
+        left: 0;
+        top: 9px;
+        width: 38px;
+        text-align: center;
+        
+        svg {
+          vertical-align: bottom;
+        }
+        
+        img {
+          width: 30px;
+          height: 30px;
+          object-fit: cover;
+        }
+      }
 
       .attachment-name {
-        @include bidi-style(margin-right, 5px, margin-left, 0);
         min-width: 0;
+        display: block;
         @extend .u-highlight;
+        word-break: break-all;
       }
 
       .attachment-size {
         white-space: nowrap;
-        float: right;
-        @include bidi-style(margin-right, 10px, margin-left, 0);
+        font-size: 11px;
+        color: hsl(200,8%,77%);
       }
 
       .attachment-delete {

BIN
contrib/icon-sprite.sketch


+ 61 - 0
db/migrate/20180220000001_setting_attachment_preview.rb

@@ -0,0 +1,61 @@
+class SettingAttachmentPreview < ActiveRecord::Migration[5.1]
+  def up
+
+    # return if it's a new setup
+    return if !Setting.find_by(name: 'system_init_done')
+    Setting.create_if_not_exists(
+      title: 'Sidebar Attachments',
+      name: 'ui_ticket_zoom_attachments_preview',
+      area: 'UI::TicketZoom::Preview',
+      description: 'Enables preview of attachments.',
+      options: {
+        form: [
+          {
+            display: '',
+            null: true,
+            name: 'ui_ticket_zoom_attachments_preview',
+            tag: 'boolean',
+            translate: true,
+            options: {
+              true  => 'yes',
+              false => 'no',
+            },
+          },
+        ],
+      },
+      state: false,
+      preferences: {
+        prio: 400,
+        permission: ['admin.ui'],
+      },
+      frontend: true
+    )
+    Setting.create_if_not_exists(
+      title: 'Sidebar Attachments',
+      name: 'ui_ticket_zoom_sidebar_article_attachments',
+      area: 'UI::TicketZoom::Preview',
+      description: 'Enables a sidebar to show an overview of all attachments.',
+      options: {
+        form: [
+          {
+            display: '',
+            null: true,
+            name: 'ui_ticket_zoom_sidebar_article_attachments',
+            tag: 'boolean',
+            translate: true,
+            options: {
+              true  => 'yes',
+              false => 'no',
+            },
+          },
+        ],
+      },
+      state: false,
+      preferences: {
+        prio: 500,
+        permission: ['admin.ui'],
+      },
+      frontend: true
+    )
+  end
+end

+ 55 - 0
db/seeds/settings.rb

@@ -698,6 +698,61 @@ Setting.create_if_not_exists(
   },
   frontend: true
 )
+Setting.create_if_not_exists(
+  title: 'Sidebar Attachments',
+  name: 'ui_ticket_zoom_attachments_preview',
+  area: 'UI::TicketZoom::Preview',
+  description: 'Enables preview of attachments.',
+  options: {
+    form: [
+      {
+        display: '',
+        null: true,
+        name: 'ui_ticket_zoom_attachments_preview',
+        tag: 'boolean',
+        translate: true,
+        options: {
+          true  => 'yes',
+          false => 'no',
+        },
+      },
+    ],
+  },
+  state: false,
+  preferences: {
+    prio: 400,
+    permission: ['admin.ui'],
+  },
+  frontend: true
+)
+Setting.create_if_not_exists(
+  title: 'Sidebar Attachments',
+  name: 'ui_ticket_zoom_sidebar_article_attachments',
+  area: 'UI::TicketZoom::Preview',
+  description: 'Enables a sidebar to show an overview of all attachments.',
+  options: {
+    form: [
+      {
+        display: '',
+        null: true,
+        name: 'ui_ticket_zoom_sidebar_article_attachments',
+        tag: 'boolean',
+        translate: true,
+        options: {
+          true  => 'yes',
+          false => 'no',
+        },
+      },
+    ],
+  },
+  state: false,
+  preferences: {
+    prio: 500,
+    permission: ['admin.ui'],
+  },
+  frontend: true
+)
+
 Setting.create_if_not_exists(
   title: 'Set notes for ticket create types.',
   name: 'ui_ticket_create_notes',

Some files were not shown because too many files changed in this diff