refinerycms-images 2.1.5 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/refinery/admin/images_controller.rb +62 -49
  3. data/app/helpers/refinery/admin/images_helper.rb +6 -6
  4. data/app/models/refinery/image.rb +16 -61
  5. data/app/models/refinery/thumbnail_dimensions.rb +97 -0
  6. data/app/views/refinery/admin/images/_actions.html.erb +6 -7
  7. data/app/views/refinery/admin/images/_form.html.erb +30 -12
  8. data/app/views/refinery/admin/images/_grid_view.html.erb +9 -16
  9. data/app/views/refinery/admin/images/_list_view.html.erb +4 -4
  10. data/app/views/refinery/admin/images/_list_view_image.html.erb +24 -15
  11. data/app/views/refinery/admin/images/index.html.erb +3 -3
  12. data/app/views/refinery/admin/images/insert.html.erb +1 -1
  13. data/config/locales/ca.yml +44 -0
  14. data/config/locales/cs.yml +5 -0
  15. data/config/locales/de.yml +5 -0
  16. data/config/locales/en.yml +4 -0
  17. data/config/locales/fr.yml +4 -0
  18. data/config/locales/it.yml +4 -14
  19. data/config/locales/nl.yml +1 -1
  20. data/config/routes.rb +1 -1
  21. data/db/migrate/20140814073957_add_title_and_alt_to_refinery_images.rb +8 -0
  22. data/db/migrate/20150430171341_translate_refinery_images.rb +22 -0
  23. data/lib/generators/refinery/images/templates/config/initializers/refinery/images.rb.erb +2 -5
  24. data/lib/refinery/images.rb +4 -1
  25. data/lib/refinery/images/configuration.rb +4 -6
  26. data/lib/refinery/images/dragonfly.rb +38 -27
  27. data/lib/refinery/images/engine.rb +2 -3
  28. data/license.md +1 -1
  29. data/refinerycms-images.gemspec +6 -3
  30. data/spec/factories/image.rb +6 -2
  31. data/spec/features/refinery/admin/images_spec.rb +49 -136
  32. data/spec/lib/generators/refinery/images/images_generator_spec.rb +1 -1
  33. data/spec/lib/refinery/images/dragonfly_spec.rb +34 -0
  34. data/spec/models/refinery/image_spec.rb +81 -43
  35. data/spec/models/refinery/thumbnail_dimensions_spec.rb +18 -0
  36. data/spec/support/shared contexts/admin_images_tab.rb +17 -0
  37. data/spec/support/shared contexts/many_images.rb +5 -0
  38. data/spec/support/shared contexts/no_images.rb +3 -0
  39. data/spec/support/shared contexts/one_image.rb +3 -0
  40. data/spec/support/shared contexts/visual_editor_add_image.rb +31 -0
  41. data/spec/support/shared examples/image_deleter.rb +31 -0
  42. data/spec/support/shared examples/image_editor.rb +3 -0
  43. data/spec/support/shared examples/image_indexer.rb +118 -0
  44. data/spec/support/shared examples/image_inserter.rb +53 -0
  45. data/spec/support/shared examples/image_previewer.rb +41 -0
  46. data/spec/support/shared examples/image_translator.rb +31 -0
  47. data/spec/support/shared examples/image_uploader.rb +37 -0
  48. data/spec/support/spec_helper.rb +19 -0
  49. data/test.html +126 -0
  50. metadata +64 -9
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ module Refinery
4
+ describe ThumbnailDimensions, type: :model do
5
+ subject(:dimensions) { described_class.new(geometry, 500, 375) }
6
+
7
+ describe '#scale' do
8
+ context 'when no width is supplied' do
9
+ let(:geometry) { 'x225>' }
10
+
11
+ it 'scales by height factor' do
12
+ expect(dimensions.width).to eq 300
13
+ expect(dimensions.height).to eq 225
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ shared_context 'admin images tab' do
2
+
3
+ let(:open_upload_dialog) { find('a', text: ::I18n.t('create_new_image', scope: 'refinery.admin.images.actions')).trigger(:click) }
4
+ let(:select_upload) {}
5
+ let(:initialize_context) {}
6
+ let(:index_in_frame) {false}
7
+ let(:dialog_frame_id) {'dialog_iframe'}
8
+ let(:initial_path) { refinery.admin_images_path(view: %w(grid list).sample) }
9
+
10
+ let(:index_item_selector) {'#records li'}
11
+ let(:gridview_img_selector) {' > img'}
12
+ let(:gridview_title_selector) {'[tooltip]'}
13
+ let(:gridview_alt_selector) {'[alt]'}
14
+ let(:listview_title_selector) {' > span.title'}
15
+ let(:listview_filename_selector) {' > span.preview'}
16
+
17
+ end
@@ -0,0 +1,5 @@
1
+ shared_context 'many images' do
2
+ let!(:image) { FactoryGirl.create(:image) }
3
+ let!(:alt_image) { FactoryGirl.create(:alternate_image) }
4
+ let!(:another_image) { FactoryGirl.create(:another_image) }
5
+ end
@@ -0,0 +1,3 @@
1
+ shared_context "no existing images" do
2
+ let(:image) { FactoryGirl.create(:image) }
3
+ end
@@ -0,0 +1,3 @@
1
+ shared_context "one image" do
2
+ let!(:image) { FactoryGirl.create(:image) }
3
+ end
@@ -0,0 +1,31 @@
1
+ shared_context 'Visual Editor - add image' do
2
+
3
+ let(:page_for_image) {
4
+ page = Refinery::Page.create :title => "Add Image to me"
5
+ # we need page parts so that there's a visual editor
6
+ Refinery::Pages.default_parts.each_with_index do |default_page_part, index|
7
+ page.parts.create(:title => default_page_part, :slug => default_page_part, :body => nil, :position => index)
8
+ end
9
+ page
10
+ }
11
+
12
+ let(:initialize_context) {click_on('Add Image') }
13
+
14
+ let(:open_upload_dialog) {}
15
+ let(:select_upload) {choose(::I18n.t('new_image', :scope => 'refinery.admin.images.insert')) }
16
+
17
+ let(:initial_path) { refinery.edit_admin_page_path(page_for_image) }
18
+
19
+ # let(:target_image) { find(:xpath, "//img[@title='Beach']")}
20
+ # let(:url){target_image["data-#{size}"]}
21
+
22
+ let(:index_in_frame) {true}
23
+ let(:dialog_frame_id) {'dialog_frame'}
24
+ let(:index_item_selector) {'#existing_image_area_content ul li img'}
25
+ let(:title_attribute_selector) {'[title]'}
26
+ let(:alt_attribute_selector) {'[alt]'}
27
+
28
+ let(:image_title_selector) {|title| '//img[@alt="' << title << '"]'}
29
+ let(:image_size_selector) {|size| '//a[@href="#' << size << '"]'}
30
+
31
+ end
@@ -0,0 +1,31 @@
1
+ shared_examples_for 'deletes an image' do
2
+ before do
3
+ raise "please set let(:initial_path)" if initial_path.blank?
4
+ ensure_on(initial_path)
5
+ end
6
+
7
+ let(:image_count) {[Refinery::Image.count, Refinery::Images.pages_per_admin_index].min}
8
+ let(:deleting_an_image) {
9
+ -> {
10
+ first("#records li").click_link(::I18n.t('delete', scope: 'refinery.admin.images'))
11
+ }
12
+ }
13
+
14
+ it 'has a delete image link for each image' do
15
+ expect(page).to have_selector('#records.images li a.confirm-delete', count: image_count)
16
+ end
17
+
18
+ it "removes an image" do
19
+ expect(deleting_an_image).to change(Refinery::Image, :count).by(-1)
20
+ end
21
+
22
+ it 'says the image has been removed' do
23
+ image_title = page.has_selector?('#image_grid') ? find("#image_grid li:first").first("img")[:title] :
24
+ find("#image_list li:first").first("span.title").text
25
+
26
+ expect(image_title).to be_present
27
+
28
+ first(".images_list li:first").click_link(::I18n.t('delete', scope: 'refinery.admin.images'))
29
+ expect(page).to have_content(::I18n.t('destroyed', scope: 'refinery.crudify', what: "'#{image_title}'"))
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ shared_examples 'edits an image' do
2
+ pending
3
+ end
@@ -0,0 +1,118 @@
1
+ shared_examples_for 'indexes images' do
2
+
3
+ let(:image_count) {[Refinery::Image.count, Refinery::Images.pages_per_admin_index].min}
4
+
5
+ before do
6
+ raise "please set let(:initial_path)" if initial_path.blank?
7
+ ensure_on(initial_path)
8
+ initialize_context
9
+ end
10
+
11
+ it 'shows all the images', js: true do
12
+ if index_in_frame
13
+ page.within_frame(dialog_frame_id) do
14
+ expect(page).to have_selector(index_item_selector, count: image_count)
15
+ end
16
+ else
17
+ expect(page).to have_selector(index_item_selector, count: image_count)
18
+ end
19
+ end
20
+
21
+ end # image index
22
+
23
+ shared_examples_for 'shows list and grid views' do
24
+
25
+ let(:image_count) {[Refinery::Image.count, Refinery::Images.pages_per_admin_index].min}
26
+
27
+ before do
28
+ raise "please set let(:initial_path)" if initial_path.blank?
29
+ ensure_on(initial_path)
30
+ end
31
+
32
+ context "when in grid view" do
33
+
34
+ before { ensure_on(current_path + "?view=grid") }
35
+
36
+ it 'shows the images with thumbnails', js: true do
37
+ expect(page).to have_selector(index_item_selector << gridview_img_selector, count: image_count)
38
+ end
39
+
40
+ it 'makes the title attribute of each image available', js: true do
41
+ expect(page).to have_selector(index_item_selector << gridview_img_selector << gridview_title_selector, count: image_count)
42
+ end
43
+
44
+ it 'makes the alt attribute of each image available', js: true do
45
+ expect(page).to have_selector(index_item_selector << gridview_img_selector << gridview_alt_selector, count: image_count)
46
+ end
47
+
48
+ it 'has an option to switch to list view' do
49
+ expect(page).to have_content(::I18n.t('switch_to', view_name: 'list', scope: 'refinery.admin.images.index.view'))
50
+ end
51
+
52
+ end # grid view
53
+
54
+ context "when in list view" do
55
+
56
+ before do
57
+ ensure_on(current_path + "?view=list")
58
+ end
59
+
60
+ it 'makes the title attribute of each image available', js: true do
61
+ expect(page).to have_selector(index_item_selector << listview_title_selector, count: image_count)
62
+ end
63
+
64
+ it 'makes the filename of each image available' do
65
+ expect(page).to have_selector(index_item_selector << listview_filename_selector, count: image_count)
66
+ end
67
+
68
+ it 'has an option to switch to grid view' do
69
+ ensure_on(current_path + '?view=list')
70
+
71
+ expect(page).to have_content(::I18n.t('switch_to', view_name: 'grid', scope: 'refinery.admin.images.index.view'))
72
+ end
73
+
74
+ end # list view
75
+ end
76
+
77
+ shared_examples_for 'paginates the list of images' do
78
+
79
+ let(:image_count) {[Refinery::Image.count, Refinery::Images.pages_per_admin_index].min}
80
+
81
+ before do
82
+ raise "please set let(:initial_path)" if initial_path.blank?
83
+ ensure_on(initial_path)
84
+ end
85
+
86
+ describe 'pagination', unless: Refinery::Image.count <= 2 do
87
+ before {
88
+ Refinery::Images.pages_per_admin_index = 2
89
+ Refinery::Images.pages_per_dialog_that_have_size_options = 2 }
90
+
91
+ it 'divides the index into pages' do
92
+ expect(page).to have_selector("div.pagination em.current")
93
+ end
94
+
95
+ context 'when on the first page' do
96
+ it 'shows a link for the next page' do
97
+ ensure_on(current_path + '?from_page=1&page=1')
98
+ expect(page).to have_selector("a.next_page[rel='next']")
99
+ end
100
+
101
+ it 'has disabled the previous page link' do
102
+ expect(page).to have_selector('.previous_page.disabled')
103
+ end
104
+ end
105
+
106
+ context 'when on the last page' do
107
+ it 'shows a link for the previous page' do
108
+ ensure_on(current_path + '?from_page=1&page=2')
109
+ expect(page).to have_selector("a.previous_page[rel='prev start']")
110
+ end
111
+
112
+ it 'has disabled the next link' do
113
+ expect(page).to have_selector('.next_page.disabled')
114
+ end
115
+
116
+ end
117
+ end # pagination
118
+ end # image index
@@ -0,0 +1,53 @@
1
+
2
+ shared_examples 'inserts images' do
3
+ before do
4
+ raise "please set let(:initial_path)" if initial_path.blank?
5
+ ensure_on(initial_path)
6
+ end
7
+
8
+ def select_and_insert_image(title, size='medium')
9
+ page.within_frame(dialog_frame_id) do
10
+ within '#existing_image_area_content' do
11
+ find(:xpath, "//img[@alt=\"#{title}\"]").click
12
+ find(:xpath, "//a[@href=\"##{size}\"]").click
13
+ find(:xpath, '//input[@id="submit_button"]').click
14
+ end
15
+ end
16
+ end
17
+
18
+ describe 'resize the image' do
19
+ pending
20
+ # it 'inserts an image of the selected size', js: true do
21
+ # click_on('Add Image')
22
+ # select_and_insert_image('Beach','large')
23
+ # sleep 10
24
+ # puts page.html
25
+ # page.within_frame(find(:xpath, '//iframe[contains(@id,"WYMeditor")]')) do
26
+ # expect(page).to have_selector('img[title="Beach"][data-rel*="450x450"]')
27
+ # end
28
+ # end
29
+ end
30
+
31
+ context 'when all images are available' do
32
+ pending
33
+ # it 'inserts the image into the page', js: true do
34
+ # click_on('Add Image')
35
+ # select_and_insert_image('Beach')
36
+ # expect(page).to have_image(url)
37
+ # end
38
+ end
39
+
40
+ context "when images are filtered by a search" do
41
+ pending
42
+
43
+ # before do
44
+ # fill_in "search", :with => "Beach"
45
+ # click_button "Search"
46
+ # end
47
+ # it 'inserts the image into the page', js: true do
48
+ # click_on('Add Image')
49
+ # select_and_insert_image('Beach')
50
+ # expect(page).to have_image(url)
51
+ # end
52
+ end
53
+ end
@@ -0,0 +1,41 @@
1
+ def preview_image
2
+ preview_window = window_opened_by do
3
+ image_url
4
+ find(:linkhref, image_url).click
5
+ end
6
+
7
+ page.within_window preview_window do
8
+ expect(page).to have_image(image_url)
9
+ end
10
+ preview_window.close
11
+ end
12
+
13
+ shared_examples 'shows an image preview' do
14
+ before do
15
+ raise "please set let(:initial_path)" if initial_path.blank?
16
+ ensure_on(initial_path)
17
+ end
18
+
19
+ let(:image_url) {first(:xpath, "//a[@class='preview_icon']")[:href]}
20
+
21
+ context "when in list view" do
22
+ before do
23
+ ensure_on(current_path + "?view=list")
24
+ end
25
+
26
+ it 'displays the image in a new window', js: true do
27
+ preview_image()
28
+ end
29
+ end
30
+
31
+
32
+ context "when in grid view" do
33
+ before do
34
+ ensure_on(current_path + "?view=grid")
35
+ end
36
+
37
+ it 'displays the image in a new window', js: true do
38
+ preview_image
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,31 @@
1
+ shared_examples 'translates an image' do
2
+ before do
3
+ allow(Refinery::I18n).to receive(:frontend_locales).and_return([:en, :fr])
4
+ ensure_on(initial_path)
5
+ end
6
+
7
+ context "when in list view" do
8
+ before do
9
+ ensure_on(current_path + "?view=list")
10
+ end
11
+
12
+ it "can have a second locale added to it" do
13
+ expect(page).to have_content("Beach")
14
+ expect(page).to have_selector("a[href='/refinery/images/#{image.id}/edit']")
15
+
16
+ click_link "Edit this image"
17
+
18
+ within "#switch_locale_picker" do
19
+ click_link "FR"
20
+ end
21
+
22
+ fill_in "Title", :with => "Titre de la première image"
23
+ fill_in "Alt", :with => "Texte alternatif de la première image"
24
+
25
+ click_button "Save"
26
+
27
+ expect(page).to have_content("'Titre de la première image' was successfully updated.")
28
+ expect(Refinery::Image.translation_class.count).to eq(1)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,37 @@
1
+ shared_examples 'uploads images' do
2
+ before do
3
+ raise "please set let(:initial_path)" if initial_path.blank?
4
+ ensure_on(initial_path)
5
+ initialize_context
6
+ end
7
+
8
+ let(:uploading_an_image) {
9
+ ->{
10
+ open_upload_dialog
11
+ page.within_frame(dialog_frame_id) do
12
+ select_upload
13
+ attach_file 'image_image', image_path
14
+ fill_in 'image_image_title', with: 'Image With Dashes'
15
+ fill_in 'image_image_alt', with: "Alt description for image"
16
+ click_button ::I18n.t('save', scope: 'refinery.admin.form_actions')
17
+ end
18
+ }
19
+ }
20
+
21
+ context 'when the image type is acceptable' do
22
+ let(:image_path) {Refinery.roots('refinery/images').join("spec/fixtures/image-with-dashes.jpg")}
23
+ it 'the image is uploaded', :js => true do
24
+ expect(uploading_an_image).to change(Refinery::Image, :count).by(1)
25
+ end
26
+ end
27
+
28
+ context 'when the image type is not acceptable' do
29
+ let(:image_path) {Refinery.roots('refinery/images').join("spec/fixtures/cape-town-tide-table.pdf")}
30
+ it 'the image is rejected', :js => true do
31
+ expect(uploading_an_image).to_not change(Refinery::Image, :count)
32
+ page.within_frame(dialog_frame_id) do
33
+ expect(page).to have_content(::I18n.t('incorrect_format', :scope => 'activerecord.errors.models.refinery/image'))
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,19 @@
1
+ module Capybara
2
+ class Session
3
+ def has_image?(src)
4
+ has_xpath?("//img[contains(@src,'#{src}')]")
5
+ end
6
+ end
7
+
8
+ add_selector(:linkhref) do
9
+ xpath {|href| ".//a[@href='#{href}']"}
10
+ end
11
+ end
12
+
13
+ def ensure_on(path)
14
+ visit(path) unless current_path == path
15
+ end
16
+
17
+ RSpec.configure do |c|
18
+ c.alias_it_should_behave_like_to :it_has_behaviour, 'has behaviour:'
19
+ end
@@ -0,0 +1,126 @@
1
+ <!DOCTYPE html>
2
+ <!-- paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ -->
3
+ <!--[if lt IE 7 ]> <html lang="en" class="no-js ie6"> <![endif]-->
4
+ <!--[if IE 7 ]> <html lang="en" class="no-js ie7"> <![endif]-->
5
+ <!--[if IE 8 ]> <html lang="en" class="no-js ie8"> <![endif]-->
6
+ <!--[if IE 9 ]> <html lang="en" class="no-js ie9"> <![endif]-->
7
+ <!--[if (gt IE 9)|!(IE)]><!-->
8
+ <html lang="en" class="no-js">
9
+ <!--<![endif]-->
10
+
11
+ <head>
12
+ <meta charset='utf-8' />
13
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
14
+ <meta refinerycms='3.0.0' />
15
+ <title>Company Name - Refinery</title>
16
+
17
+ <link rel="stylesheet" media="screen" href="/assets/refinery/refinery.css" />
18
+ <link rel="stylesheet" media="screen" href="/assets/wymeditor.css" />
19
+ <link rel="stylesheet" media="screen" href="/assets/wymeditor/skins/refinery/skin.css" />
20
+
21
+ <script type="text/javascript" >
22
+ var refinery = window.refinery || {};
23
+ refinery.current_admin_locale = 'en';
24
+ </script>
25
+ <script src="/assets/admin.js"></script><script src="/assets/refinery/refinery.js"></script><script src="/assets/refinery/application.js"></script>
26
+ <script src="/assets/refi
27
+ nery/wymeditor.js"></script>
28
+ <script src="/assets/wymeditor/lang/en.js"></script>
29
+ <script src="/assets/wymeditor/skins/refinery/skin.js"></script>
30
+
31
+ </head>
32
+
33
+ <body class="index en">
34
+ <div id='site_bar'>
35
+ <div id='site_bar_content' class='clearfix'>
36
+
37
+ <div id='editor_switch'>
38
+ <a data-no-turbolink="true" href="/">Switch to your website</a>
39
+ </div>
40
+
41
+ <a id="site_bar_refinery_cms_logo" target="_blank" href="http://refinerycms.com"> <span>Refinery CMS™</span> </a>
42
+ <div id='site_bar_branding'>
43
+ <span id='site_bar_company_name'> Company Name </span>
44
+
45
+ <a id="logout" href="/refinery/logout">Log out</a>
46
+ </div>
47
+ </div>
48
+ </div>
49
+
50
+ <div id='tooltip_container'></div>
51
+ <div id="admin_container" class="clearfix">
52
+ <header id="header">
53
+ <nav id="menu">
54
+ <a id="plugin_refinery_users" href="/refinery/users">Users</a>
55
+ <a class="active" id="plugin_refinery_images" href="/refinery/images">Images</a>
56
+ <a id="plugin_refinery_files" href="/refinery/resources">Files</a>
57
+ <a id="plugin_refinery_pages" href="/refinery/pages">Pages</a>
58
+
59
+ <a id="menu_reorder" title="Reorder menu" class=" reorder_icon" href=""></a>
60
+ <a id="menu_reorder_done" title="i18n: Reorder Done" class=" reorder_done_icon hidden" href=""></a>
61
+
62
+ </nav>
63
+
64
+ </header>
65
+ <div id="page_container">
66
+ <div id="page">
67
+ <div id="content" class="clearfix">
68
+ <div id="flash_container">
69
+ <noscript>
70
+ <div class='flash flash_error'>
71
+ For full functionality of this page it is necessary to enable JavaScript.
72
+ Here are the
73
+ <a target="_blank" href="http://www.enable-javascript.com">instructions for how to enable JavaScript in your web browser</a>
74
+ </div>
75
+ </noscript>
76
+
77
+ </div>
78
+ <section id="records" class="images">
79
+
80
+ <div class="pagination_container">
81
+
82
+ <div class="clearfix pagination_frame frame_center">
83
+ <h3>March 04, 2015</h3>
84
+ <ul class='images_list'>
85
+ <li id="sortable_1" class="clearfix record on">
86
+ <span class="title"> Beach </span>
87
+ <span class='alt'> <a title="Title: Beach Alt text: Beach" class=" info_icon" href="#"></a> </span>
88
+
89
+ <span class="preview">&nbsp;</span>
90
+ <span class="actions"> <a title="View this image <br/><em>Opens in a new window</em>" class=" preview_icon" target="_blank" href="/system/images/W1siZiIsIjIwMTUvMDMvMDQvNmhleWpqcDJvZF9iZWFjaC5q
91
+ cGVnIl1d/beach.jpeg?sha=9fd966feb5fd57e4"></a> <a title="Edit this image" class=" edit_icon" href="/refinery/images/1/edit"></a> <a data-confirm="Are you sure you want to remove &#39;Beach&#39;?" title="Remove this image forever" class=" delete_icon cancel confirm-delete" rel="nofollow" data-method
92
+ ="delete" href="/refinery/images/1"></a> </span>
93
+ </li>
94
+
95
+ </ul>
96
+ </div>
97
+
98
+ </div>
99
+
100
+ </section>
101
+ <section id="actions">
102
+ <ul>
103
+ <li>
104
+ <form method='GET' action='/refinery/images' class='search_form'>
105
+ <input id="search" type="search" name="search" title="Put here a string of at least 3 characters containing what you are looking for." />
106
+
107
+ <input type="submit" value="Search" />
108
+ </form>
109
+
110
+ </li>
111
+ <li>
112
+ <a title="Add new image" class=" add_icon" href="/refinery/images/new?dialog=true">Add new image</a>
113
+ </li>
114
+ <li>
115
+ <a title="Switch to grid view" class=" switch_view_grid_icon" href="/refinery/images?view=grid">Switch to grid view</a>
116
+ </li>
117
+ </ul>
118
+
119
+ </section>
120
+
121
+ </div>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ </body>
126
+ </html>