From 457bc0b15d00b5650a9a04940bcce42a9978e7e7 Mon Sep 17 00:00:00 2001 From: Jeremy Stanley Date: Fri, 5 Oct 2012 10:29:14 -0600 Subject: [PATCH] a new [image] button in tinyMCE's toolbar fixes CNVS_5151 test plan: by using the new image button in the tinymce toolbar, you should be able to: * insert an image from Canvas content (course or group files; whatever the context for the editor is) * test in wikis, discussions, quizzes, eportfolios... anywhere you can find a rich content editor * if you're in a course or group context, you should be able to add course/group files. otherwise (in account context, for instance) you will only see "my files". * also, pls to test that subfolders work * single-click an image to select it (and set size/alt text etc. before pressing Update) * double-click an image to select it and insert with the default alt-text and size * note that the size is constrained to the image's aspect ratio * insert an image from the user's own files * insert an image by URL * insert an image from Flickr via search * images inserted from flickr should link to the source flickr page (this is part of flickr TOS, and is not a new behavior, but should be tested explicitly) * make sure if you change to a different flickr image, the link is updated * also test that if you change a flickr image to a canvas image or url image that the flickr link goes away * NOTE: also test the old flickr search dialog on the wiki sidebar (the blue magnifying glass thing) for possible regressions. (the tinymce plugin that powers this thing was modified.) * create or edit alt text for any image type (note, it does not add uploading new files, that will come in another commit) Change-Id: I2d5f8ca9f2301168f442955fda791631ee886636 Reviewed-on: https://gerrit.instructure.com/14391 Reviewed-by: Bracken Mosbacker Product-Review: Bracken Mosbacker Tested-by: Jenkins QA-Review: Adam Phillipps --- app/coffeescripts/models/Folder.coffee | 65 ++++++++ app/coffeescripts/tinymce.coffee | 2 + .../views/FileBrowserView.coffee | 46 ++++++ .../views/FindFlickrImageView.coffee | 45 ++++++ app/coffeescripts/views/FolderTreeView.coffee | 72 +++++++++ .../tinymce/InsertUpdateImageView.coffee | 141 ++++++++++++++++++ app/stylesheets/_variables.sass | 1 + app/stylesheets/bootstrap/_reset.scss | 1 - app/stylesheets/bootstrap_parts/_main.scss | 3 + .../bootstrap_parts/_overrides.scss | 2 +- app/stylesheets/jst/FileBrowserView.scss | 42 ++++++ app/stylesheets/jst/FindFlickrImageView.scss | 30 ++++ .../jst/tinymce/InsertUpdateImageView.scss | 15 ++ app/stylesheets/tree.scss | 50 +++++++ app/views/jst/FileBrowserView.handlebars | 1 + .../jst/FindFlickrImageResult.handlebars | 10 ++ app/views/jst/FindFlickrImageView.handlebars | 10 ++ app/views/jst/FolderTreeItem.handlebars | 6 + .../tinymce/InsertUpdateImageView.handlebars | 52 +++++++ app/views/layouts/_foot.html.erb | 2 +- lib/canvas/require_js.rb | 8 +- public/javascripts/full_files.js | 2 +- public/javascripts/jquery.ajaxJSON.js | 4 +- public/javascripts/tinymce.editor_box.js | 4 +- .../tiny_mce/plugins/editor_plugin.js | 0 .../instructure_embed/editor_plugin.js | 5 +- .../instructure_image/editor_plugin.js | 53 +++++++ .../img/button.gif | Bin spec/factories/attachment_factory.rb | 5 + spec/fixtures/test_image.jpg | Bin 0 -> 10707 bytes spec/selenium/common.rb | 11 ++ spec/selenium/eportfolios_spec.rb | 6 +- spec/selenium/helpers/wiki_and_tiny_common.rb | 44 +++++- .../teacher_wiki_and_tiny_images_spec.rb | 57 +++++-- 34 files changed, 760 insertions(+), 35 deletions(-) create mode 100644 app/coffeescripts/models/Folder.coffee create mode 100644 app/coffeescripts/views/FileBrowserView.coffee create mode 100644 app/coffeescripts/views/FindFlickrImageView.coffee create mode 100644 app/coffeescripts/views/FolderTreeView.coffee create mode 100644 app/coffeescripts/views/tinymce/InsertUpdateImageView.coffee create mode 100644 app/stylesheets/jst/FileBrowserView.scss create mode 100644 app/stylesheets/jst/FindFlickrImageView.scss create mode 100644 app/stylesheets/jst/tinymce/InsertUpdateImageView.scss create mode 100644 app/stylesheets/tree.scss create mode 100644 app/views/jst/FileBrowserView.handlebars create mode 100644 app/views/jst/FindFlickrImageResult.handlebars create mode 100644 app/views/jst/FindFlickrImageView.handlebars create mode 100644 app/views/jst/FolderTreeItem.handlebars create mode 100644 app/views/jst/tinymce/InsertUpdateImageView.handlebars delete mode 100644 public/javascripts/tinymce/jscripts/tiny_mce/plugins/editor_plugin.js create mode 100644 public/javascripts/tinymce/jscripts/tiny_mce/plugins/instructure_image/editor_plugin.js rename public/javascripts/tinymce/jscripts/tiny_mce/plugins/{instructure_embed => instructure_image}/img/button.gif (100%) create mode 100644 spec/fixtures/test_image.jpg diff --git a/app/coffeescripts/models/Folder.coffee b/app/coffeescripts/models/Folder.coffee new file mode 100644 index 00000000000..c6d570a3174 --- /dev/null +++ b/app/coffeescripts/models/Folder.coffee @@ -0,0 +1,65 @@ +define [ + 'Backbone' + 'underscore' +], (Backbone, _) -> + + class Folder extends Backbone.Model + + defaults: + 'name' : '' + + initialize: -> + @setUpFilesAndFoldersIfNeeded() + super + + parse: (response) -> + @setUpFilesAndFoldersIfNeeded() + + @folders.url = response.folders_url + @files.url = response.files_url + super + + setUpFilesAndFoldersIfNeeded: -> + unless @folders + @folders = new Backbone.Collection + @folders.model = Folder + unless @files + @files = new Backbone.Collection + + expand: (force=false) -> + @isExpanded = true + @trigger 'expanded' + unless @expandDfd || force + @isExpanding = true + @trigger 'beginexpanding' + @expandDfd = $.Deferred().done => + @isExpanding = false + @trigger 'endexpanding' + + selfHasntBeenFetched = @folders.url is @folders.constructor::url or @files.url is @files.constructor::url + fetchDfd = @fetch() if selfHasntBeenFetched || force + $.when(fetchDfd).done => + foldersDfd = @folders.fetch() unless @get('folders_count') is 0 + filesDfd = @files.fetch() unless @get('files_count') is 0 + $.when(foldersDfd, filesDfd).done(@expandDfd.resolve) + + collapse: -> + @isExpanded = false + @trigger 'collapsed' + + toggle: -> + if @isExpanded + @collapse() + else + @expand() + + contents: -> + _(@files.models.concat(@folders.models)).sortBy (model) -> + (model.get('name') || model.get('display_name') || '').toLowerCase() + + previewUrlForFile: (file) -> + if @get('context_type') in ['Course', 'Group'] + "/#{ @get('context_type').toLowerCase() + 's' }/#{ @get('context_id') }/files/#{ file.get('id') }/preview" + else + # we need the full path with verifier for user files + file.get('url') diff --git a/app/coffeescripts/tinymce.coffee b/app/coffeescripts/tinymce.coffee index 6693aea7b32..d817eae0a92 100644 --- a/app/coffeescripts/tinymce.coffee +++ b/app/coffeescripts/tinymce.coffee @@ -8,6 +8,7 @@ define [ # instructure plugins 'tinymce/jscripts/tiny_mce/plugins/instructure_contextmenu/editor_plugin' 'tinymce/jscripts/tiny_mce/plugins/instructure_embed/editor_plugin' + 'tinymce/jscripts/tiny_mce/plugins/instructure_image/editor_plugin' 'tinymce/jscripts/tiny_mce/plugins/instructure_equation/editor_plugin' 'tinymce/jscripts/tiny_mce/plugins/instructure_equella/editor_plugin' 'tinymce/jscripts/tiny_mce/plugins/instructure_external_tools/editor_plugin' @@ -19,6 +20,7 @@ define [ markScriptsLoaded [ 'plugins/instructure_contextmenu/editor_plugin' 'plugins/instructure_embed/editor_plugin' + 'plugins/instructure_image/editor_plugin' 'plugins/instructure_equation/editor_plugin' 'plugins/instructure_equella/editor_plugin' 'plugins/instructure_external_tools/editor_plugin' diff --git a/app/coffeescripts/views/FileBrowserView.coffee b/app/coffeescripts/views/FileBrowserView.coffee new file mode 100644 index 00000000000..155e70b8738 --- /dev/null +++ b/app/coffeescripts/views/FileBrowserView.coffee @@ -0,0 +1,46 @@ +define [ + 'i18n!filebrowserview' + 'Backbone' + 'underscore' + 'jst/FileBrowserView' + 'compiled/views/FolderTreeView' + 'compiled/models/Folder' + 'compiled/str/splitAssetString' +], (I18n, Backbone, _, template, FolderTreeView, Folder, splitAssetString) -> + + class FileBrowserView extends Backbone.View + + template: template + + rootFolders: -> + # purposely sharing these across instances of FileBrowserView + # use a 'custom_name' to set I18n'd names for the root folders (the actual names are hard-coded) + FileBrowserView.rootFolders ||= do -> + contextFiles = null + contextTypeAndId = splitAssetString(ENV.context_asset_string || '') + if contextTypeAndId && contextTypeAndId.length == 2 && (contextTypeAndId[0] == 'courses' || contextTypeAndId[0] == 'groups') + contextFiles = new Folder + contextFiles.set 'custom_name', if contextTypeAndId[0] is 'courses' then I18n.t('course_files', 'Course files') else I18n.t('group_files', 'Group files') + contextFiles.url = "/api/v1/#{contextTypeAndId[0]}/#{contextTypeAndId[1]}/folders/root" + contextFiles.fetch() + + myFiles = new Folder + myFiles.set 'custom_name', I18n.t('my_files', 'My files') + myFiles.url = '/api/v1/users/self/folders/root' + myFiles.fetch() + + result = [] + result.push contextFiles if contextFiles + result.push myFiles + result + + initialize: -> + @contentTypes = @options?.contentTypes + super + + afterRender: -> + @$folderTree = @$el.children('.folderTree') + for folder in @rootFolders() + new FolderTreeView({model: folder, contentTypes: @contentTypes}).$el.appendTo(@$folderTree) + super + \ No newline at end of file diff --git a/app/coffeescripts/views/FindFlickrImageView.coffee b/app/coffeescripts/views/FindFlickrImageView.coffee new file mode 100644 index 00000000000..9a8a61d06f6 --- /dev/null +++ b/app/coffeescripts/views/FindFlickrImageView.coffee @@ -0,0 +1,45 @@ +define [ + 'Backbone' + 'underscore' + 'str/htmlEscape' + 'jst/FindFlickrImageView' + 'jst/FindFlickrImageResult' +], (Backbone, _, h, template, resultTemplate) -> + + class FindFlickrImageView extends Backbone.View + + tagName: 'form' + + attributes: + 'class': 'bootstrap-form form-horizontal FindFlickrImageView' + + template: template + + events: + 'submit' : 'searchFlickr' + 'change .flickrSearchTerm' : 'hideResultsIfEmptySearch' + 'input .flickrSearchTerm' : 'hideResultsIfEmptySearch' + + hideResultsIfEmptySearch: -> + @renderResults([]) unless @$('.flickrSearchTerm').val() + + searchFlickr: (event) -> + event?.preventDefault() + return unless query = @$('.flickrSearchTerm').val() + flickrUrl = 'https://secure.flickr.com/services/rest/?method=flickr.photos.search&format=json' + + '&api_key=734839aadcaa224c4e043eaf74391e50&sort=relevance&license=1,2,3,4,5,6' + + "&text=#{query}&per_page=150&jsoncallback=?" + @request?.abort() + @$('.flickrResults').show().disableWhileLoading @request = $.getJSON flickrUrl, (data) => + photos = data.photos.photo + @renderResults(photos) + + renderResults: (photos) -> + html = _.map photos, (photo) -> + resultTemplate + thumb: "https://farm#{photo.farm}.static.flickr.com/#{photo.server}/#{photo.id}_#{photo.secret}_s.jpg" + fullsize: "https://farm#{photo.farm}.static.flickr.com/#{photo.server}/#{photo.id}_#{photo.secret}.jpg" + source: "https://secure.flickr.com/photos/#{photo.owner}/#{photo.id}" + title: photo.title + + @$('.flickrResults').showIf(!!photos.length).html html.join('') diff --git a/app/coffeescripts/views/FolderTreeView.coffee b/app/coffeescripts/views/FolderTreeView.coffee new file mode 100644 index 00000000000..13d79057031 --- /dev/null +++ b/app/coffeescripts/views/FolderTreeView.coffee @@ -0,0 +1,72 @@ +define [ + 'Backbone' + 'underscore' + 'compiled/fn/preventDefault' + 'compiled/models/Folder' + 'jst/FolderTreeItem' +], (Backbone, _, preventDefault, Folder, treeItemTemplate) -> + + class FolderTreeView extends Backbone.View + + tagName: 'li' + + attributes: -> + 'role': 'treeitem' + 'aria-expanded': "#{!!@model.isExpanded}" + + events: + 'click .folderLabel': 'toggle' + + # you can set an optional `@options.contentTypes` attribute with an array of + # content-types files that you want to show + initialize: -> + @model.on 'all', @render, this + @model.files.on 'all', @render, this + @model.folders.on 'all', @render, this + @render() + super + + render: -> + $focusedChild = @$(document.activeElement) + @renderSelf() + @renderContents() + # restore focus for keyboard users + @$el.find($focusedChild).focus() if $focusedChild.length + + toggle: (event) -> + # prevent it from bubbling up to parents and from following link + event.preventDefault() + event.stopPropagation() + + @model.toggle() + @$el.attr(@attributes()) + + title_text: -> + @model.get('custom_name') || @model.get('name') + + renderSelf: -> + @$label ||= $("").prependTo(@$el) + @$label + .text(@title_text()) + .toggleClass('expanded', !!@model.isExpanded) + .toggleClass('loading after', !!@model.isExpanding) + + renderContents: -> + @$folderContents?.detach() + if @model.isExpanded + @$folderContents = $("
    ").appendTo(@$el) + _.each @model.contents(), (model) => + node = @["viewFor_#{model.cid}"] ||= + if model.constructor is Folder + # recycle DOM nodes to prevent zombies that still respond to model events, + # sad that I have to attach something to the model though + new FolderTreeView( + model: model + contentTypes: @options.contentTypes + ).el + else if !@options.contentTypes || (model.get('content-type') in @options.contentTypes) + $ treeItemTemplate + title: model.get 'display_name' + thumbnail_url: model.get 'thumbnail_url' + preview_url: @model.previewUrlForFile(model) + @$folderContents.append node if node diff --git a/app/coffeescripts/views/tinymce/InsertUpdateImageView.coffee b/app/coffeescripts/views/tinymce/InsertUpdateImageView.coffee new file mode 100644 index 00000000000..1a15091cad3 --- /dev/null +++ b/app/coffeescripts/views/tinymce/InsertUpdateImageView.coffee @@ -0,0 +1,141 @@ +define [ + 'i18n!editor' + 'jquery' + 'underscore' + 'str/htmlEscape' + 'compiled/fn/preventDefault' + 'compiled/views/DialogBaseView' + 'jst/tinymce/InsertUpdateImageView' +], (I18n, $, _, h, preventDefault, DialogBaseView, template) -> + + class InsertUpdateImageView extends DialogBaseView + + template: template + + events: + 'change [name="image[width]"]' : 'constrainProportions' + 'change [name="image[height]"]' : 'constrainProportions' + 'click .flickrImageResult, .treeFile' : 'onFileLinkClick' + 'change [name="image[src]"]' : 'onImageUrlChange' + 'tabsshow .imageSourceTabs': 'onTabsshow' + 'dblclick .flickrImageResult, .treeFile' : 'onFileLinkDblclick' + + dialogOptions: + width: 625 + title: I18n.t 'titles.insert_edit_image', 'Insert / Edit Image' + + initialize: (@editor, selectedNode) -> + @$editor = $("##{@editor.id}") + @prevSelection = @editor.selection.getBookmark() + @$selectedNode = $(selectedNode) + super + @render() + @show() + if @$selectedNode.prop('nodeName') is 'IMG' + @setSelectedImage + src: @$selectedNode.attr('src') + alt: @$selectedNode.attr('alt') + width: @$selectedNode.width() + height: @$selectedNode.height() + + afterRender: -> + @$('.imageSourceTabs').tabs() + + onTabsshow: (event, ui) -> + loadTab = (fn) => + return if @["#{ui.panel.id}IsLoaded"] + @["#{ui.panel.id}IsLoaded"] = true + loadingDfd = $.Deferred() + $(ui.panel).disableWhileLoading loadingDfd + fn(loadingDfd.resolve) + switch ui.panel.id + when 'tabUploaded' + loadTab (done) => + require [ + 'compiled/views/FileBrowserView', + 'compiled/util/mimeClass' + ], (FileBrowserView, mimeClass) => + contentTypes = _.compact _.map mimeClass.mimeClasses, (val, key) -> key if val is 'image' + new FileBrowserView({contentTypes}).render().$el.appendTo(ui.panel) + done() + when 'tabFlickr' + loadTab (done) => + require ['compiled/views/FindFlickrImageView'], (FindFlickrImageView) => + new FindFlickrImageView().render().$el.appendTo(ui.panel) + done() + + setAspectRatio: -> + width = Number @$("[name='image[width]']").val() + height = Number @$("[name='image[height]']").val() + if width && height + @aspectRatio = width / height + else + delete @aspectRatio + + constrainProportions: (event) => + val = Number $(event.target).val() + if @aspectRatio && (val or (val is 0)) + if $(event.target).is('[name="image[height]"]') + @$('[name="image[width]"]').val Math.round(val * @aspectRatio) + else + @$('[name="image[height]"]').val Math.round(val / @aspectRatio) + + setSelectedImage: (attributes = {}) -> + # set given attributes immediately; update width and height after image loads + @$("[name='image[#{key}]']").val(value) for key, value of attributes + dfd = $.Deferred() + onLoad = ({target: img}) => + newAttributes = _.defaults attributes, + width: img.width + height: img.height + @$("[name='image[#{key}]']").val(value) for key, value of newAttributes + isValidImage = newAttributes.width && newAttributes.height + @setAspectRatio() + dfd.resolve newAttributes + onError = ({target: img}) => + newAttributes = + width: '' + height: '' + @$("[name='image[#{key}]']").val(value) for key, value of newAttributes + @$img = $('', attributes).load(onLoad).error(onError) + dfd + + getAttributes: -> + res = {} + for key in ['width', 'height'] + val = Number @$("[name='image[#{key}]']").val() + res[key] = val if val && val > 0 + for key in ['src', 'alt'] + val = @$("[name='image[#{key}]']").val() + res[key] = val if val + res + + onFileLinkClick: (event) -> + event.preventDefault() + @$('.active').removeClass('active').parent().removeAttr('aria-selected') + $a = $(event.currentTarget).addClass('active') + $a.parent().attr('aria-selected', true) + @flickr_link = $a.attr('data-linkto') + @setSelectedImage + src: $a.attr('data-fullsize') + alt: $a.attr('title') + + onFileLinkDblclick: (event) => + # click event is handled on the first click + @update() + + onImageUrlChange: (event) -> + @flickr_link = null + @setSelectedImage src: $(event.currentTarget).val() + + generateImageHtml: -> + img_tag = @editor.dom.createHTML("img", @getAttributes()) + if @flickr_link + img_tag = "#{img_tag}" + img_tag + + update: => + @editor.selection.moveToBookmark(@prevSelection) + @$editor.editorBox 'insert_code', @generateImageHtml() + @editor.focus() + @close() diff --git a/app/stylesheets/_variables.sass b/app/stylesheets/_variables.sass index dd910c1b064..cc4d22eddc8 100644 --- a/app/stylesheets/_variables.sass +++ b/app/stylesheets/_variables.sass @@ -28,6 +28,7 @@ $section_tabs_border_bottom_color: lighten($section_tabs_border_top_color, 4%) $section_tabs_to_be_hidden_color: #888 $hintTextColor: #888 +$button-text-color: #525252 $ui-state-default-gradient-top: #ededed $ui-state-default-gradient-bottom: #c4c4c4 diff --git a/app/stylesheets/bootstrap/_reset.scss b/app/stylesheets/bootstrap/_reset.scss index 7760c28689f..954dc2eb86a 100644 --- a/app/stylesheets/bootstrap/_reset.scss +++ b/app/stylesheets/bootstrap/_reset.scss @@ -79,7 +79,6 @@ sub { img { /* Responsive images (ensure images don't scale beyond their parents) */ max-width: 100%; /* Part 1: Set a maxium relative to the parent */ - width: auto\9; /* IE7-8 need help adjusting responsive images */ height: auto; /* Part 2: Scale the height according to the width, otherwise you get stretching */ vertical-align: middle; diff --git a/app/stylesheets/bootstrap_parts/_main.scss b/app/stylesheets/bootstrap_parts/_main.scss index c8d11e9f15f..564f8f423a3 100644 --- a/app/stylesheets/bootstrap_parts/_main.scss +++ b/app/stylesheets/bootstrap_parts/_main.scss @@ -42,6 +42,9 @@ //@import "bootstrap/tooltip"; // we do our own @import "popovers"; +// Components: Misc +@import "bootstrap/thumbnails"; + // Components: Misc @import "bootstrap/thumbnails"; diff --git a/app/stylesheets/bootstrap_parts/_overrides.scss b/app/stylesheets/bootstrap_parts/_overrides.scss index c7fe51328b9..d3ea68d9d4b 100644 --- a/app/stylesheets/bootstrap_parts/_overrides.scss +++ b/app/stylesheets/bootstrap_parts/_overrides.scss @@ -116,4 +116,4 @@ iframe#tool_content { blockquote p { font-size: inherit; -} \ No newline at end of file +} diff --git a/app/stylesheets/jst/FileBrowserView.scss b/app/stylesheets/jst/FileBrowserView.scss new file mode 100644 index 00000000000..6f9f185542f --- /dev/null +++ b/app/stylesheets/jst/FileBrowserView.scss @@ -0,0 +1,42 @@ +@import 'environment'; + +$triangle-edge-size: 5px; + +.folderTree { + &, ul { + @include reset-list; + } + ul { margin-left: 8px; } + li a { + padding: 1px 7px 1px 35px; + display: block; + background: url(/images/inst_tree/file_types/page_white.png) no-repeat 13px 3px; + position: relative; + &.folderLabel { + background-image: url(/images/inst_tree/folder.png); + &:before{ + position: absolute; + top: $triangle-edge-size; + left: 4px; + border: solid transparent; + border-width: $triangle-edge-size; + border-left-color: $button-text-color; + content: ''; + } + &.expanded:before{ + left: 0; + top: 7px; + border-left-color: transparent; + border-top-color: $button-text-color; + } + } + + &.active { background-color: $activeBG; } + } + .preview-thumbnail { + margin-left: -23px; + max-width: 200px; + height: 30px; + vertical-align: middle; + } +} \ No newline at end of file diff --git a/app/stylesheets/jst/FindFlickrImageView.scss b/app/stylesheets/jst/FindFlickrImageView.scss new file mode 100644 index 00000000000..5b30ca7e9fa --- /dev/null +++ b/app/stylesheets/jst/FindFlickrImageView.scss @@ -0,0 +1,30 @@ +@import 'environment'; + +.FindFlickrImageView { + .flickrResults { + padding: 0; + max-height: 200px; + overflow: auto; + margin-left: 0px; + margin-top: 10px; + li { + margin-bottom: 10px; + margin-left: 10px; + a.active { background-color: darken($activeBG, 25%); } + } + } +} + +.WikiSidebarView { + .flickrResults { + max-height: 130px; + li { + margin-bottom: 10px; + margin-left: 10px; + } + img { + width: 40px; + height: 40px; + } + } +} diff --git a/app/stylesheets/jst/tinymce/InsertUpdateImageView.scss b/app/stylesheets/jst/tinymce/InsertUpdateImageView.scss new file mode 100644 index 00000000000..b4b94c2e9f6 --- /dev/null +++ b/app/stylesheets/jst/tinymce/InsertUpdateImageView.scss @@ -0,0 +1,15 @@ +.insertUpdateImage { + .insertUpdateImageTabpane { + height: 200px; + overflow: auto; + } + .checkbox.inline { white-space: nowrap; } + // fix safari legend margin issue + legend { + margin-bottom: 0px; + } + legend + * { + margin-top: 20px; + -webkit-margin-collapse: separate; + } +} diff --git a/app/stylesheets/tree.scss b/app/stylesheets/tree.scss new file mode 100644 index 00000000000..e27f26d48ff --- /dev/null +++ b/app/stylesheets/tree.scss @@ -0,0 +1,50 @@ +@import 'environment'; + +$triangle-edge-size: 5px; + +.folderTree { + &, ul { + @include reset-list; + } + ul { margin-left: 8px; } + li a { + padding: 1px 7px 1px 35px; + display: block; + background: url(/images/inst_tree/file_types/page_white.png) no-repeat 13px 3px; + position: relative; + &.folderLabel { + background-image: url(/images/inst_tree/folder.png); + &:before{ + position: absolute; + top: $triangle-edge-size; + left: 4px; + border: solid transparent; + border-width: $triangle-edge-size; + border-left-color: $button-text-color; + content: ''; + } + &.expanded:before{ + left: 0; + top: 7px; + border-left-color: transparent; + border-top-color: $button-text-color; + } + &.loading:after { + width: image-width('ajax-loader-linear.gif'); + height: image-height('ajax-loader-linear.gif'); + background: url(/images/ajax-loader-linear.gif); + content: ''; + display: inline-block; + margin-left: 7px; + } + } + + &:focus { background-color: #f2fafd; } + } + .preview-thumbnail { + margin-left: -23px; + max-width: 200px; + height: 30px; + vertical-align: middle; + } +} \ No newline at end of file diff --git a/app/views/jst/FileBrowserView.handlebars b/app/views/jst/FileBrowserView.handlebars new file mode 100644 index 00000000000..3d5e1dc0fa8 --- /dev/null +++ b/app/views/jst/FileBrowserView.handlebars @@ -0,0 +1 @@ +
      \ No newline at end of file diff --git a/app/views/jst/FindFlickrImageResult.handlebars b/app/views/jst/FindFlickrImageResult.handlebars new file mode 100644 index 00000000000..f4d8b2efaf9 --- /dev/null +++ b/app/views/jst/FindFlickrImageResult.handlebars @@ -0,0 +1,10 @@ +
    • + + {{title}} + +
    • diff --git a/app/views/jst/FindFlickrImageView.handlebars b/app/views/jst/FindFlickrImageView.handlebars new file mode 100644 index 00000000000..e9dd1b35ed0 --- /dev/null +++ b/app/views/jst/FindFlickrImageView.handlebars @@ -0,0 +1,10 @@ +
      + +
      + diff --git a/app/views/jst/FolderTreeItem.handlebars b/app/views/jst/FolderTreeItem.handlebars new file mode 100644 index 00000000000..3da59b5cad9 --- /dev/null +++ b/app/views/jst/FolderTreeItem.handlebars @@ -0,0 +1,6 @@ +
    • + + + {{title}} + +
    • diff --git a/app/views/jst/tinymce/InsertUpdateImageView.handlebars b/app/views/jst/tinymce/InsertUpdateImageView.handlebars new file mode 100644 index 00000000000..e397c852a32 --- /dev/null +++ b/app/views/jst/tinymce/InsertUpdateImageView.handlebars @@ -0,0 +1,52 @@ +
      +
      + {{#t "image_source"}}Image Source{{/t}} + +
      +
      + {{#t "attributes"}}Attributes{{/t}} +
      + +
      + +

      {{#t "alt_help_text"}}Describe the image to improve accessibility{{/t}}

      +
      +
      +
      + +
      + + x + +

      {{#t "dimension_help_text"}}Aspect ratio will be preserved{{/t}}

      +
      +
      +
      +
      diff --git a/app/views/layouts/_foot.html.erb b/app/views/layouts/_foot.html.erb index e8a3c723b68..311678beeab 100644 --- a/app/views/layouts/_foot.html.erb +++ b/app/views/layouts/_foot.html.erb @@ -9,7 +9,7 @@ require = { translate: <%= include_js_translations? %>, baseUrl: '<%= js_base_url %>', - paths: <%= raw Canvas::RequireJs.paths %>, + paths: <%= raw Canvas::RequireJs.paths(true) %>, use: <%= raw Canvas::RequireJs.shims %> }; diff --git a/lib/canvas/require_js.rb b/lib/canvas/require_js.rb index 2955b8cb14b..3548bfe0daa 100644 --- a/lib/canvas/require_js.rb +++ b/lib/canvas/require_js.rb @@ -51,13 +51,13 @@ module Canvas bundle == '*' ? result : (result[bundle.to_s] || []) end - def paths + def paths(cache_busting = false) @paths ||= { :common => 'compiled/bundles/common', :jqueryui => 'vendor/jqueryui', :use => 'vendor/use', :uploadify => '../flash/uploadify/jquery.uploadify-3.1.min' - }.update(plugin_paths).update(Canvas::RequireJs::PluginExtension.paths).to_json.gsub(/([,{])/, "\\1\n ") + }.update(cache_busting ? cache_busting_paths : {}).update(plugin_paths).update(Canvas::RequireJs::PluginExtension.paths).to_json.gsub(/([,{])/, "\\1\n ") end def plugin_paths @@ -70,6 +70,10 @@ module Canvas end end + def cache_busting_paths + { 'compiled/tinymce' => 'compiled/tinymce.js?v2' } # hack: increment to purge browser cached bundles after tiny change + end + def shims <<-JS.gsub(%r{\A +|^ {8}}, '') { diff --git a/public/javascripts/full_files.js b/public/javascripts/full_files.js index 8a15e3959ec..cf34cff476f 100644 --- a/public/javascripts/full_files.js +++ b/public/javascripts/full_files.js @@ -1702,7 +1702,7 @@ define([ tinyOptions: { valid_elements: '*[*]', extended_valid_elements: '*[*]', - plugins: "autolink,instructure_external_tools,instructure_contextmenu,instructure_links,instructure_embed,instructure_equation,instructure_equella,media,paste,table,inlinepopups" + plugins: "autolink,instructure_external_tools,instructure_contextmenu,instructure_links,instructure_image,instructure_equation,instructure_equella,media,paste,table,inlinepopups" } }); $textarea.data('tinyIsVisible', !tinyIsVisible); diff --git a/public/javascripts/jquery.ajaxJSON.js b/public/javascripts/jquery.ajaxJSON.js index 9a60f9e72e5..9f7b81b0e3f 100644 --- a/public/javascripts/jquery.ajaxJSON.js +++ b/public/javascripts/jquery.ajaxJSON.js @@ -22,9 +22,9 @@ define([ 'jquery' /* $ */ ], function(INST, $) { - $.originalGetJSON = $.getJSON; + var _getJSON = $.getJSON; $.getJSON = function(url, data, callback) { - var xhr = $.originalGetJSON(url, data, callback); + var xhr = _getJSON.apply($, arguments); $.ajaxJSON.storeRequest(xhr, url, 'GET', data); return xhr; }; diff --git a/public/javascripts/tinymce.editor_box.js b/public/javascripts/tinymce.editor_box.js index fc3aafb5925..5e11cd95ff9 100644 --- a/public/javascripts/tinymce.editor_box.js +++ b/public/javascripts/tinymce.editor_box.js @@ -113,7 +113,7 @@ define([ if(width == 0) { width = $textarea.closest(":visible").width(); } - var instructure_buttons = ",instructure_embed,instructure_equation"; + var instructure_buttons = ",instructure_image,instructure_equation"; for(var idx in INST.editorButtons) { // maxVisibleEditorButtons should be the max number of external tool buttons // that are visible, INCLUDING the catchall "more external tools" button that @@ -150,7 +150,7 @@ define([ elements: id, theme : "advanced", plugins: "autolink,instructure_external_tools,instructure_contextmenu,instructure_links," + - "instructure_embed,instructure_equation,instructure_record,instructure_equella," + + "instructure_embed,instructure_image,instructure_equation,instructure_record,instructure_equella," + "media,paste,table,inlinepopups", dialog_type: 'modal', language_load: false, diff --git a/public/javascripts/tinymce/jscripts/tiny_mce/plugins/editor_plugin.js b/public/javascripts/tinymce/jscripts/tiny_mce/plugins/editor_plugin.js deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/public/javascripts/tinymce/jscripts/tiny_mce/plugins/instructure_embed/editor_plugin.js b/public/javascripts/tinymce/jscripts/tiny_mce/plugins/instructure_embed/editor_plugin.js index 5192f111ca8..c75c01f4859 100644 --- a/public/javascripts/tinymce/jscripts/tiny_mce/plugins/instructure_embed/editor_plugin.js +++ b/public/javascripts/tinymce/jscripts/tiny_mce/plugins/instructure_embed/editor_plugin.js @@ -149,12 +149,15 @@ define([ if (search === 'flickr') $flickrLink.click(); }); - + + /* replaced by instructure_image button + but this plugin is still used by the wiki sidebar (for now) editor.addButton('instructure_embed', { title: TRANSLATIONS.embed_image, cmd: 'instructureEmbed', image: url + '/img/button.gif' }); + */ }, getInfo: function () { diff --git a/public/javascripts/tinymce/jscripts/tiny_mce/plugins/instructure_image/editor_plugin.js b/public/javascripts/tinymce/jscripts/tiny_mce/plugins/instructure_image/editor_plugin.js new file mode 100644 index 00000000000..f51573ea6e3 --- /dev/null +++ b/public/javascripts/tinymce/jscripts/tiny_mce/plugins/instructure_image/editor_plugin.js @@ -0,0 +1,53 @@ +define([ + 'compiled/editor/stocktiny', + 'i18n!editor', + 'jquery', + 'str/htmlEscape', + 'jqueryui/dialog' +], function(tinymce, I18n, $, htmlEscape) { + + tinymce.create('tinymce.plugins.InstructureImagePlugin', { + init : function(ed, url) { + // Register commands + ed.addCommand('mceInstructureImage', function() { + var selectedNode = ed.selection.getNode(); + + // Internal image object like a flash placeholder + if (ed.dom.getAttrib(selectedNode, 'class', '').indexOf('mceItem') != -1) return; + + require(['compiled/views/tinymce/InsertUpdateImageView'], function(InsertUpdateImageView){ + new InsertUpdateImageView(ed, selectedNode); + }); + }); + + // Register buttons + ed.addButton('instructure_image', { + title : htmlEscape(I18n.t('embed_image', 'Embed Image')), + cmd : 'mceInstructureImage', + image : url + '/img/button.gif' + }); + + // highlight our button when an image is selected + ed.onNodeChange.add(function(ed, cm, e) { + if(e.nodeName == 'IMG' && e.className != 'equation_image') { + cm.setActive('instructure_image', true); + } else { + cm.setActive('instructure_image', false); + } + }); + }, + + getInfo : function() { + return { + longname : 'Instructure image', + author : 'Instructure', + authorurl : 'http://instructure.com', + infourl : 'http://instructure.com', + version : '1' + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('instructure_image', tinymce.plugins.InstructureImagePlugin); +}); \ No newline at end of file diff --git a/public/javascripts/tinymce/jscripts/tiny_mce/plugins/instructure_embed/img/button.gif b/public/javascripts/tinymce/jscripts/tiny_mce/plugins/instructure_image/img/button.gif similarity index 100% rename from public/javascripts/tinymce/jscripts/tiny_mce/plugins/instructure_embed/img/button.gif rename to public/javascripts/tinymce/jscripts/tiny_mce/plugins/instructure_image/img/button.gif diff --git a/spec/factories/attachment_factory.rb b/spec/factories/attachment_factory.rb index a261866b1ea..84828e91eb0 100644 --- a/spec/factories/attachment_factory.rb +++ b/spec/factories/attachment_factory.rb @@ -53,3 +53,8 @@ end def stub_png_data(filename = 'test my file? hai!&.png', data = nil) stub_file_data(filename, data, 'image/png') end + +def jpeg_data_frd + fixture_path = File.expand_path(File.dirname(__FILE__) + '/../fixtures/test_image.jpg') + ActionController::TestUploadedFile.new(fixture_path, 'image/jpeg', true) +end diff --git a/spec/fixtures/test_image.jpg b/spec/fixtures/test_image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2498146173b96d10ba64d92b67da7fbd87079329 GIT binary patch literal 10707 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 literal 0 HcmV?d00001 diff --git a/spec/selenium/common.rb b/spec/selenium/common.rb index 50eb3cb9e87..d5e96f22c11 100644 --- a/spec/selenium/common.rb +++ b/spec/selenium/common.rb @@ -824,6 +824,17 @@ shared_examples_for "all selenium tests" do temp_file = open(element.attribute('src')) temp_file.size.should > 0 end + + def check_element_attrs(element, attrs) + element.should be_displayed + attrs.each do |k, v| + if v.is_a? Regexp + element.attribute(k).should match v + else + element.attribute(k).should == v + end + end + end def check_file(element) require 'open-uri' diff --git a/spec/selenium/eportfolios_spec.rb b/spec/selenium/eportfolios_spec.rb index aaa0eb9b6de..135fdafbc1f 100755 --- a/spec/selenium/eportfolios_spec.rb +++ b/spec/selenium/eportfolios_spec.rb @@ -90,9 +90,9 @@ describe "eportfolios" do edit_link.click f('.add_content_link.add_rich_content_link').click wait_for_tiny(f('textarea.edit_section')) - f("img[alt='Embed Image']").click - f(".flickr_search_link").click - f("#instructure_image_search").should be_displayed + f('a.mce_instructure_image').click + f('a[href="#tabFlickr"]').click + f('form.FindFlickrImageView').should be_displayed end diff --git a/spec/selenium/helpers/wiki_and_tiny_common.rb b/spec/selenium/helpers/wiki_and_tiny_common.rb index bd27089eaa5..7e8bf65c446 100644 --- a/spec/selenium/helpers/wiki_and_tiny_common.rb +++ b/spec/selenium/helpers/wiki_and_tiny_common.rb @@ -91,15 +91,16 @@ require File.expand_path(File.dirname(__FILE__) + '/../common') def add_flickr_image(el) require 'open-uri' - - el.find_element(:css, '.mce_instructure_embed').click - f('.flickr_search_link').click - f('#image_search_form > input').send_keys('angel') - submit_form('#image_search_form') + + el.find_element(:css, '.mce_instructure_image').click + dialog = ff('.ui-dialog').reverse.detect(&:displayed?) + f('a[href="#tabFlickr"]', dialog).click + f('.FindFlickrImageView .flickrSearchTerm', dialog).send_keys('angel') + submit_form(f('.FindFlickrImageView', dialog)) wait_for_ajax_requests - keep_trying_until { f('.image_link').should be_displayed } + keep_trying_until { f('.flickrImageResult', dialog).should be_displayed } # sometimes flickr has broken images; choose the first one that works - image = ff('.image_link').detect do |image| + image = ff('.flickrImageResult img', dialog).detect do |image| begin temp_file = open(image.attribute('src')) temp_file.size > 0 @@ -109,6 +110,35 @@ require File.expand_path(File.dirname(__FILE__) + '/../common') end raise "Couldn't find an image on flickr!" unless image image.click + f('.ui-dialog-buttonset .btn-primary', dialog).click + wait_for_ajaximations + end + + def add_canvas_image(el, folder, filename) + el.find_element(:css, '.mce_instructure_image').click + dialog = ff('.ui-dialog').reverse.detect(&:displayed?) + f('a[href="#tabUploaded"]', dialog).click + keep_trying_until { f('.folderLabel', dialog).displayed? } + folder_el = ff('.folderLabel', dialog).detect { |el| el.text == folder } + folder_el.should_not be_nil + folder_el.click + keep_trying_until { f('.treeFile', dialog).displayed? } + file_el = f(".treeFile[title=\"#{filename}\"]", dialog) + file_el.should_not be_nil + file_el.click + wait_for_ajaximations + f('.ui-dialog-buttonset .btn-primary', dialog).click + wait_for_ajaximations + end + + def add_url_image(el, url, alt_text) + el.find_element(:css, '.mce_instructure_image').click + dialog = ff('.ui-dialog').reverse.detect(&:displayed?) + f('a[href="#tabUrl"]', dialog).click + f('[name="image[src]"]', dialog).send_keys(url) + f('[name="image[alt]"]', dialog).send_keys(alt_text) + f('.ui-dialog-buttonset .btn-primary', dialog).click + wait_for_ajaximations end def add_image_to_rce diff --git a/spec/selenium/teacher_wiki_and_tiny_images_spec.rb b/spec/selenium/teacher_wiki_and_tiny_images_spec.rb index 7febe266875..89dbbae5460 100644 --- a/spec/selenium/teacher_wiki_and_tiny_images_spec.rb +++ b/spec/selenium/teacher_wiki_and_tiny_images_spec.rb @@ -9,6 +9,7 @@ describe "Wiki pages and Tiny WYSIWYG editor Images" do before (:each) do course_with_teacher_logged_in + @blank_page = @course.wiki.wiki_pages.create! :title => 'blank' end after(:each) do @@ -142,30 +143,59 @@ describe "Wiki pages and Tiny WYSIWYG editor Images" do end it "should add image from flickr" do - get "/courses/#{@course.id}/wiki" - - #add image from flickr to rce - f('.wiki_switch_views_link').click - clear_wiki_rce - f('.wiki_switch_views_link').click + get "/courses/#{@course.id}/wiki/blank" + wait_for_ajaximations + f('.edit_link').click add_flickr_image(driver) in_frame "wiki_page_body_ifr" do f('#tinymce img').should be_displayed end - - submit_form('#new_wiki_page') - wait_for_ajax_requests - get "/courses/#{@course.id}/wiki" #can't just wait for the dom, for some reason it stays in edit mode - wait_for_ajax_requests - + submit_form("#edit_wiki_page_#{@blank_page.id}") + keep_trying_until { f('#wiki_body').displayed? } check_image(f('#wiki_body img')) end + it "should add image via url" do + get "/courses/#{@course.id}/wiki/blank" + wait_for_ajaximations + f('.edit_link').click + add_url_image(driver, 'http://example.com/image.png', 'alt text') + submit_form("#edit_wiki_page_#{@blank_page.id}") + keep_trying_until { f('#wiki_body').displayed? } + check_element_attrs(f('#wiki_body img'), :src => 'http://example.com/image.png', :alt => 'alt text') + end + + describe "canvas images" do + before do + @course_root = Folder.root_folders(@course).first + @course_attachment = @course.attachments.create! :uploaded_data => jpeg_data_frd, :filename => 'course.jpg', :display_name => 'course.jpg', :folder => @course_root + @teacher_root = Folder.root_folders(@teacher).first + @teacher_attachment = @teacher.attachments.create! :uploaded_data => jpeg_data_frd, :filename => 'teacher.jpg', :display_name => 'teacher.jpg', :folder => @teacher_root + get "/courses/#{@course.id}/wiki/blank" + wait_for_ajaximations + f('.edit_link').click + end + + it "should add a course image" do + add_canvas_image(driver, 'Course files', 'course.jpg') + submit_form("#edit_wiki_page_#{@blank_page.id}") + keep_trying_until { f('#wiki_body').displayed? } + check_element_attrs(f('#wiki_body img'), :src => /\/files\/#{@course_attachment.id}/, :alt => 'course.jpg') + end + + it "should add a user image" do + add_canvas_image(driver, 'My files', 'teacher.jpg') + submit_form("#edit_wiki_page_#{@blank_page.id}") + keep_trying_until { f('#wiki_body').displayed? } + check_element_attrs(f('#wiki_body img'), :src => /\/files\/#{@teacher_attachment.id}/, :alt => 'teacher.jpg') + end + end it "should put flickr images into the right editor" do get "/courses/#{@course.id}/quizzes" + wait_for_ajaximations f(".new-quiz-link").click - keep_trying_until { f(".mce_instructure_embed").should be_displayed } + keep_trying_until { f(".mce_instructure_image").displayed? } add_flickr_image(driver) click_questions_tab @@ -181,4 +211,3 @@ describe "Wiki pages and Tiny WYSIWYG editor Images" do end end end -