Implemented preview feature as default feature.
This commit is contained in:
parent
11b0d4d286
commit
5dedd6ecfc
16 changed files with 342 additions and 14 deletions
|
@ -10,7 +10,7 @@ DB_CONFIG="test:\n adapter: postgresql\n database: zammad_test\n host: 127.0.
|
|||
|
||||
# install build dependencies
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends autoconf automake autotools-dev bison build-essential curl git-core libffi-dev libgdbm-dev libgmp-dev libmariadbclient-dev-compat libncurses5-dev libreadline-dev libsqlite3-dev libssl-dev libtool libxml2-dev libxslt1-dev libyaml-0-2 libyaml-dev patch pkg-config postfix sqlite3 zlib1g-dev
|
||||
sudo apt-get install -y --no-install-recommends autoconf automake autotools-dev bison build-essential curl git-core libffi-dev libgdbm-dev libgmp-dev libmariadbclient-dev-compat libncurses5-dev libreadline-dev libsqlite3-dev libssl-dev libtool libxml2-dev libxslt1-dev libyaml-0-2 libyaml-dev patch pkg-config postfix sqlite3 zlib1g-dev libimlib2 libimlib2-dev
|
||||
|
||||
if [ "${CIRCLE_JOB}" == "install-mysql" ]; then
|
||||
DB_ADAPTER="mysql2"
|
||||
|
|
45
.pkgr.yml
45
.pkgr.yml
|
@ -10,36 +10,69 @@ targets:
|
|||
- nginx
|
||||
- postgresql-server
|
||||
- which
|
||||
debian-8:
|
||||
dependencies:
|
||||
- curl
|
||||
- elasticsearch
|
||||
- nginx|apache2
|
||||
- postgresql|mysql-server|mariadb-server|sqlite
|
||||
- epel-release
|
||||
- imlib2
|
||||
- imlib2-devel
|
||||
build_dependencies:
|
||||
- http://download.fedoraproject.org/pub/epel/7/x86_64/Packages/i/imlib2-1.4.5-9.el7.x86_64.rpm
|
||||
- http://download.fedoraproject.org/pub/epel/7/x86_64/Packages/i/imlib2-devel-1.4.5-9.el7.x86_64.rpm
|
||||
debian-9:
|
||||
dependencies:
|
||||
- curl
|
||||
- elasticsearch
|
||||
- nginx|apache2
|
||||
- postgresql|mariadb-server|sqlite
|
||||
- libimlib2
|
||||
- libimlib2-dev
|
||||
build_dependencies:
|
||||
- libimlib2
|
||||
- libimlib2-dev
|
||||
ubuntu-16.04:
|
||||
dependencies:
|
||||
- curl
|
||||
- elasticsearch
|
||||
- nginx|apache2
|
||||
- postgresql|mysql-server|mariadb-server|sqlite
|
||||
- libimlib2
|
||||
- libimlib2-dev
|
||||
build_dependencies:
|
||||
- libimlib2
|
||||
- libimlib2-dev
|
||||
ubuntu-18.04:
|
||||
dependencies:
|
||||
- curl
|
||||
- elasticsearch
|
||||
- nginx|apache2
|
||||
- postgresql|mysql-server|mariadb-server|sqlite
|
||||
- libimlib2
|
||||
build_dependencies:
|
||||
- libimlib2-dev
|
||||
sles-12:
|
||||
dependencies:
|
||||
- curl
|
||||
- elasticsearch
|
||||
- nginx
|
||||
- postgresql-server
|
||||
- imlib2
|
||||
- imlib2-devel
|
||||
build_dependencies:
|
||||
- imlib2
|
||||
- imlib2-devel
|
||||
sles-12:
|
||||
dependencies:
|
||||
- curl
|
||||
- elasticsearch
|
||||
- nginx
|
||||
- postgresql-server
|
||||
- imlib2
|
||||
- libImlib2-1
|
||||
- imlib2
|
||||
- imlib2-devel
|
||||
build_dependencies:
|
||||
- https://ftp.gwdg.de/pub/opensuse/discontinued/distribution/12.3/repo/oss/suse/x86_64/imlib2-1.4.5-12.1.1.x86_64.rpm
|
||||
- https://ftp.gwdg.de/pub/opensuse/discontinued/distribution/12.3/repo/oss/suse/x86_64/imlib2-devel-1.4.5-12.1.1.x86_64.rpm
|
||||
- https://ftp.gwdg.de/pub/opensuse/discontinued/distribution/12.3/repo/oss/suse/x86_64/imlib2-filters-1.4.5-12.1.1.x86_64.rpm
|
||||
- https://ftp.gwdg.de/pub/opensuse/discontinued/distribution/12.3/repo/oss/suse/x86_64/libImlib2-1-1.4.5-12.1.1.x86_64.rpm
|
||||
before:
|
||||
- contrib/packager.io/before.sh
|
||||
after:
|
||||
|
|
3
Gemfile
3
Gemfile
|
@ -116,6 +116,9 @@ gem 'autodiscover', git: 'https://github.com/zammad-deps/autodiscover'
|
|||
gem 'rubyntlm', git: 'https://github.com/wimm/rubyntlm'
|
||||
gem 'viewpoint'
|
||||
|
||||
# image processing
|
||||
gem 'rszr'
|
||||
|
||||
# Gems used only for develop/test and not required
|
||||
# in production environments by default.
|
||||
group :development, :test do
|
||||
|
|
|
@ -408,6 +408,7 @@ GEM
|
|||
rspec-mocks (~> 3.8.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-support (3.8.0)
|
||||
rszr (0.3.2)
|
||||
rubocop (0.64.0)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
parallel (~> 1.10)
|
||||
|
@ -584,6 +585,7 @@ DEPENDENCIES
|
|||
rb-fsevent
|
||||
rchardet (>= 1.8.0)
|
||||
rspec-rails
|
||||
rszr
|
||||
rubocop
|
||||
rubyntlm!
|
||||
sassc-rails
|
||||
|
|
|
@ -11,9 +11,33 @@ class App.TicketZoomArticleImageView extends App.ControllerModal
|
|||
'click .js-cancel': 'cancel'
|
||||
'click .js-close': 'cancel'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
@unbindAll()
|
||||
$(document).bind('keydown.image_preview', 'right', (e) =>
|
||||
nextElement = @parentElement.closest('.attachment').next('.attachment.attachment--preview')
|
||||
return if nextElement.length is 0
|
||||
@close()
|
||||
nextElement.find('img').click()
|
||||
)
|
||||
$(document).bind('keydown.image_preview', 'left', (e) =>
|
||||
prevElement = @parentElement.closest('.attachment').prev('.attachment.attachment--preview')
|
||||
return if prevElement.length is 0
|
||||
@close()
|
||||
prevElement.find('img').click()
|
||||
)
|
||||
|
||||
content: ->
|
||||
@image = @image.replace(/view=preview/, 'view=inline')
|
||||
"<div class=\"centered imagePreview\">#{@image}</div>"
|
||||
|
||||
onSubmit: =>
|
||||
@image = @image.replace(/(\?|)view=(preview|inline)/, '')
|
||||
url = "#{$(@image).attr('src')}?disposition=attachment"
|
||||
window.open(url, '_blank')
|
||||
|
||||
onClose: =>
|
||||
@unbindAll()
|
||||
|
||||
unbindAll: ->
|
||||
$(document).unbind('keydown.image_preview')
|
||||
|
|
|
@ -421,4 +421,4 @@ class ArticleViewItem extends App.ObserverController
|
|||
imageView: (e) ->
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
new App.TicketZoomArticleImageView(image: $(e.target).get(0).outerHTML)
|
||||
new App.TicketZoomArticleImageView(image: $(e.target).get(0).outerHTML, parentElement: $(e.currentTarget))
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
<div class="attachment-icon">
|
||||
<% if attachment.preferences && attachment.preferences['Content-Type'] && @ContentTypeIcon(attachment.preferences['Content-Type']): %>
|
||||
<% if @canPreview(attachment.preferences['Content-Type']): %>
|
||||
<img src="<%= App.Config.get('api_path') %>/ticket_attachment/<%= @article.ticket_id %>/<%= @article.id %>/<%= attachment.id %>">
|
||||
<img src="<%= App.Config.get('api_path') %>/ticket_attachment/<%= @article.ticket_id %>/<%= @article.id %>/<%= attachment.id %>?view=preview">
|
||||
<% else: %>
|
||||
<%- @Icon( @ContentTypeIcon(attachment.preferences['Content-Type']) ) %>
|
||||
<% end %>
|
||||
|
|
|
@ -255,8 +255,21 @@ class TicketArticlesController < ApplicationController
|
|||
|
||||
disposition = sanitized_disposition
|
||||
|
||||
content = nil
|
||||
if params[:view].present? && file.preferences[:resizable] == true
|
||||
if file.preferences[:content_inline] == true && params[:view] == 'inline'
|
||||
content = file.content_inline
|
||||
elsif file.preferences[:content_preview] == true && params[:view] == 'preview'
|
||||
content = file.content_preview
|
||||
end
|
||||
end
|
||||
|
||||
if content.blank?
|
||||
content = file.content
|
||||
end
|
||||
|
||||
send_data(
|
||||
file.content,
|
||||
content,
|
||||
filename: file.filename,
|
||||
type: file.preferences['Content-Type'] || file.preferences['Mime-Type'] || 'application/octet-stream',
|
||||
disposition: disposition
|
||||
|
|
|
@ -34,7 +34,7 @@ returns
|
|||
=end
|
||||
|
||||
def self.add(data)
|
||||
data = data.stringify_keys
|
||||
data.deep_stringify_keys!
|
||||
|
||||
# lookup store_object.id
|
||||
store_object = Store::Object.create_if_not_exists(name: data['object'])
|
||||
|
@ -50,9 +50,34 @@ returns
|
|||
data.delete('data')
|
||||
data.delete('object')
|
||||
|
||||
data['preferences'] ||= {}
|
||||
['Mime-Type', 'Content-Type', 'mime_type', 'content_type'].each do |key|
|
||||
next if data['preferences'][key].blank?
|
||||
next if !data['preferences'][key].match(%r{image/(jpeg|jpg|png)}i)
|
||||
|
||||
data['preferences']['resizable'] = true
|
||||
break
|
||||
end
|
||||
|
||||
# store meta data
|
||||
store = Store.create!(data)
|
||||
|
||||
begin
|
||||
if store.preferences[:resizable] == true
|
||||
if store.content_preview(silence: true)
|
||||
store.preferences[:content_preview] = true
|
||||
end
|
||||
if store.content_inline(silence: true)
|
||||
store.preferences[:content_inline] = true
|
||||
end
|
||||
store.save!
|
||||
end
|
||||
rescue => e
|
||||
logger.error e
|
||||
store.preferences[:resizable] = false
|
||||
store.save!
|
||||
end
|
||||
|
||||
store
|
||||
end
|
||||
|
||||
|
@ -165,6 +190,52 @@ returns
|
|||
|
||||
=begin
|
||||
|
||||
get content of file in preview size
|
||||
|
||||
store = Store.find(store_id)
|
||||
content_as_string = store.content_preview
|
||||
|
||||
returns
|
||||
|
||||
content_as_string
|
||||
|
||||
=end
|
||||
|
||||
def content_preview(options = {})
|
||||
file = Store::File.find_by(id: store_file_id)
|
||||
if !file
|
||||
raise "No such file #{store_file_id}!"
|
||||
end
|
||||
raise 'Unable to generate preview' if options[:silence] != true && preferences[:content_preview] != true
|
||||
|
||||
image_resize(file.content, 200)
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
get content of file in inline size
|
||||
|
||||
store = Store.find(store_id)
|
||||
content_as_string = store.content_inline
|
||||
|
||||
returns
|
||||
|
||||
content_as_string
|
||||
|
||||
=end
|
||||
|
||||
def content_inline(options = {})
|
||||
file = Store::File.find_by(id: store_file_id)
|
||||
if !file
|
||||
raise "No such file #{store_file_id}!"
|
||||
end
|
||||
raise 'Unable to generate inline' if options[:silence] != true && preferences[:content_inline] != true
|
||||
|
||||
image_resize(file.content, 1800)
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
get content of file
|
||||
|
||||
store = Store.find(store_id)
|
||||
|
@ -204,4 +275,32 @@ returns
|
|||
|
||||
file.provider
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def image_resize(content, width)
|
||||
local_sha = Digest::SHA256.hexdigest(content)
|
||||
|
||||
cache_key = "image-resize-#{local_sha}_#{width}"
|
||||
all = nil
|
||||
image = Cache.get(cache_key)
|
||||
return image if image
|
||||
|
||||
temp_file = ::Tempfile.new
|
||||
temp_file.binmode
|
||||
temp_file.write(content)
|
||||
temp_file.close
|
||||
image = Rszr::Image.load(temp_file.path)
|
||||
return if image.width < width
|
||||
|
||||
image.resize!(width, :auto)
|
||||
temp_file_resize = ::Tempfile.new.path
|
||||
image.save(temp_file_resize)
|
||||
image_resized = ::File.binread(temp_file_resize)
|
||||
|
||||
Cache.write(cache_key, image_resized, { expires_in: 6.months })
|
||||
|
||||
image_resized
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -82,7 +82,7 @@ returns
|
|||
article['attachments'].each do |file|
|
||||
next if !file[:preferences] || !file[:preferences]['Content-ID'] || (file[:preferences]['Content-ID'] != cid && file[:preferences]['Content-ID'] != "<#{cid}>" )
|
||||
|
||||
replace = "#{tag_start}/api/v1/ticket_attachment/#{article['ticket_id']}/#{article['id']}/#{file[:id]}\"#{tag_end}>"
|
||||
replace = "#{tag_start}/api/v1/ticket_attachment/#{article['ticket_id']}/#{article['id']}/#{file[:id]}?view=inline\"#{tag_end}>"
|
||||
inline_attachments[file[:id]] = true
|
||||
break
|
||||
end
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
class SettingChangeTicketZoomAttachmentPreview < ActiveRecord::Migration[5.1]
|
||||
def up
|
||||
# return if it's a new setup
|
||||
return if !Setting.find_by(name: 'system_init_done')
|
||||
|
||||
Setting.create_or_update(
|
||||
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: true,
|
||||
preferences: {
|
||||
prio: 400,
|
||||
permission: ['admin.ui'],
|
||||
},
|
||||
frontend: true
|
||||
)
|
||||
end
|
||||
end
|
|
@ -793,7 +793,7 @@ Setting.create_if_not_exists(
|
|||
},
|
||||
],
|
||||
},
|
||||
state: false,
|
||||
state: true,
|
||||
preferences: {
|
||||
prio: 400,
|
||||
permission: ['admin.ui'],
|
||||
|
|
|
@ -501,7 +501,7 @@ do(window) ->
|
|||
stopPropagation: (event) ->
|
||||
event.stopPropagation()
|
||||
|
||||
onPaste: (e) =>
|
||||
onDrop: (e) =>
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
|
||||
|
|
BIN
test/data/image/1000x1000.png
Normal file
BIN
test/data/image/1000x1000.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
BIN
test/data/image/1x1.png
Normal file
BIN
test/data/image/1x1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 106 B |
|
@ -151,4 +151,124 @@ class StoreTest < ActiveSupport::TestCase
|
|||
assert_not(attachments[0])
|
||||
end
|
||||
end
|
||||
|
||||
test 'test resizable' do
|
||||
|
||||
# not possible
|
||||
store = Store.add(
|
||||
object: 'SomeObject1',
|
||||
o_id: rand(1_234_567_890),
|
||||
data: File.binread(Rails.root.join('test', 'data', 'upload', 'upload1.txt')),
|
||||
filename: 'test1.pdf',
|
||||
preferences: {
|
||||
content_type: 'text/plain',
|
||||
content_id: 234,
|
||||
},
|
||||
created_by_id: 1,
|
||||
)
|
||||
assert_not(store.preferences.key?(:resizable))
|
||||
assert_not(store.preferences.key?(:content_inline))
|
||||
assert_not(store.preferences.key?(:content_preview))
|
||||
assert_raises(RuntimeError) do
|
||||
store.content_inline
|
||||
end
|
||||
assert_raises(RuntimeError) do
|
||||
store.content_preview
|
||||
end
|
||||
|
||||
# not possible
|
||||
store = Store.add(
|
||||
object: 'SomeObject2',
|
||||
o_id: rand(1_234_567_890),
|
||||
data: File.binread(Rails.root.join('test', 'data', 'upload', 'upload1.txt')),
|
||||
filename: 'test1.pdf',
|
||||
preferences: {
|
||||
content_type: 'image/jpg',
|
||||
content_id: 234,
|
||||
},
|
||||
created_by_id: 1,
|
||||
)
|
||||
assert_equal(store.preferences[:resizable], false)
|
||||
assert_not(store.preferences.key?(:content_inline))
|
||||
assert_not(store.preferences.key?(:content_preview))
|
||||
assert_raises(RuntimeError) do
|
||||
store.content_inline
|
||||
end
|
||||
assert_raises(RuntimeError) do
|
||||
store.content_preview
|
||||
end
|
||||
|
||||
# possible (preview and inline)
|
||||
store = Store.add(
|
||||
object: 'SomeObject3',
|
||||
o_id: rand(1_234_567_890),
|
||||
data: File.binread(Rails.root.join('test', 'data', 'upload', 'upload2.jpg')),
|
||||
filename: 'test1.pdf',
|
||||
preferences: {
|
||||
content_type: 'image/jpg',
|
||||
content_id: 234,
|
||||
},
|
||||
created_by_id: 1,
|
||||
)
|
||||
assert_equal(store.preferences[:resizable], true)
|
||||
assert_equal(store.preferences[:content_inline], true)
|
||||
assert_equal(store.preferences[:content_preview], true)
|
||||
|
||||
temp_file = ::Tempfile.new.path
|
||||
File.binwrite(temp_file, store.content_inline)
|
||||
image = Rszr::Image.load(temp_file)
|
||||
assert_equal(image.width, 1800)
|
||||
|
||||
temp_file = ::Tempfile.new.path
|
||||
File.binwrite(temp_file, store.content_preview)
|
||||
image = Rszr::Image.load(temp_file)
|
||||
assert_equal(image.width, 200)
|
||||
|
||||
# possible (preview only)
|
||||
store = Store.add(
|
||||
object: 'SomeObject4',
|
||||
o_id: rand(1_234_567_890),
|
||||
data: File.binread(Rails.root.join('test', 'data', 'image', '1000x1000.png')),
|
||||
filename: 'test1.png',
|
||||
preferences: {
|
||||
content_type: 'image/png',
|
||||
content_id: 234,
|
||||
},
|
||||
created_by_id: 1,
|
||||
)
|
||||
assert_equal(store.preferences[:resizable], true)
|
||||
assert_nil(store.preferences[:content_inline])
|
||||
assert_equal(store.preferences[:content_preview], true)
|
||||
assert_raises(RuntimeError) do
|
||||
store.content_inline
|
||||
end
|
||||
|
||||
temp_file = ::Tempfile.new.path
|
||||
File.binwrite(temp_file, store.content_preview)
|
||||
image = Rszr::Image.load(temp_file)
|
||||
assert_equal(image.width, 200)
|
||||
|
||||
# possible (now preview or inline needed)
|
||||
store = Store.add(
|
||||
object: 'SomeObject5',
|
||||
o_id: rand(1_234_567_890),
|
||||
data: File.binread(Rails.root.join('test', 'data', 'image', '1x1.png')),
|
||||
filename: 'test1.png',
|
||||
preferences: {
|
||||
content_type: 'image/png',
|
||||
content_id: 234,
|
||||
},
|
||||
created_by_id: 1,
|
||||
)
|
||||
assert_equal(store.preferences[:resizable], true)
|
||||
assert_nil(store.preferences[:content_inline])
|
||||
assert_nil(store.preferences[:content_preview])
|
||||
assert_raises(RuntimeError) do
|
||||
store.content_inline
|
||||
end
|
||||
assert_raises(RuntimeError) do
|
||||
store.content_preview
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue