fancy page revisions
test plan: - with draft state disabled * ensure the page revisions still behaves as would be expected - with draft state enabled * navigate to a page * using the gear menu, select 'View Page History' - the revisions list should open with the latest revision selected * the 'Next' and 'Previous' page buttons should work * restoring a revision should work - should restore title, url, and page content - restoring a revision that is 'Same as latest' is not allowed * clicking the 'X' next to Revision History or the page breadcrumb should navigate back to the page view - any browser windows opened to the revisions page should show the alternate page, if refreshed (when transitioning to/from draft state being enabled) closes CNVS-8486 Change-Id: Ie22bf82ead396cbfa5a206c3a1234859cb2df7ff Reviewed-on: https://gerrit.instructure.com/25922 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Sterling Cobb <sterling@instructure.com> QA-Review: Nathan Rogowski <nathan@instructure.com> Product-Review: Bracken Mosbacker <bracken@instructure.com>
This commit is contained in:
parent
22227953ae
commit
12077de92d
|
@ -0,0 +1,31 @@
|
|||
require [
|
||||
'jquery'
|
||||
'compiled/models/WikiPage'
|
||||
'compiled/collections/WikiPageRevisionsCollection'
|
||||
'compiled/views/wiki/WikiPageContentView'
|
||||
'compiled/views/wiki/WikiPageRevisionsView'
|
||||
], ($, WikiPage, WikiPageRevisionsCollection, WikiPageContentView, WikiPageRevisionsView) ->
|
||||
|
||||
$('body').addClass('show revisions')
|
||||
|
||||
wikiPage = new WikiPage ENV.WIKI_PAGE, revision: ENV.WIKI_PAGE_REVISION, contextAssetString: ENV.context_asset_string
|
||||
revisions = new WikiPageRevisionsCollection [],
|
||||
parentModel: wikiPage
|
||||
|
||||
revisionsView = new WikiPageRevisionsView
|
||||
collection: revisions
|
||||
revisionsView.on 'selectionChanged', (newSelection) ->
|
||||
contentView.setModel(newSelection.model)
|
||||
if !newSelection.model.get('title') || newSelection.model.get('title') == ''
|
||||
contentView.$el.disableWhileLoading newSelection.model.fetch()
|
||||
revisionsView.$el.appendTo('#wiki_page_revisions')
|
||||
revisionsView.render()
|
||||
|
||||
contentView = new WikiPageContentView
|
||||
contentView.$el.appendTo('#wiki_page_revisions')
|
||||
contentView.on 'render', ->
|
||||
revisionsView.reposition()
|
||||
contentView.render()
|
||||
|
||||
revisionsView.collection.setParams per_page: 10
|
||||
revisionsView.collection.fetch()
|
|
@ -0,0 +1,32 @@
|
|||
define [
|
||||
'underscore'
|
||||
'Backbone'
|
||||
'compiled/collections/PaginatedCollection'
|
||||
'compiled/models/WikiPageRevision'
|
||||
], (_, Backbone, PaginatedCollection, WikiPageRevision) ->
|
||||
|
||||
revisionOptions = ['parentModel']
|
||||
|
||||
class WikiPageRevisionsCollection extends PaginatedCollection
|
||||
model: WikiPageRevision
|
||||
|
||||
url: ->
|
||||
"#{@parentModel.url()}/revisions"
|
||||
|
||||
initialize: (models, options) ->
|
||||
super
|
||||
_.extend(this, _.pick(options || {}, revisionOptions))
|
||||
|
||||
if @parentModel
|
||||
collection = @
|
||||
parentModel = collection.parentModel
|
||||
setupModel = (model) ->
|
||||
model.page = parentModel
|
||||
model.pageUrl = parentModel.get('url')
|
||||
model.contextAssetString = parentModel.contextAssetString
|
||||
collection.latest = model if !!model.get('latest')
|
||||
|
||||
@on 'reset', (models) ->
|
||||
models.each setupModel
|
||||
@on 'add', (model) ->
|
||||
setupModel(model)
|
|
@ -0,0 +1,85 @@
|
|||
define [
|
||||
'underscore'
|
||||
'jquery'
|
||||
], (_, $) ->
|
||||
|
||||
# Floating sticky
|
||||
#
|
||||
# allows an element to float (using fixed positioning) as the user
|
||||
# scrolls, "sticking" to the top of the window. the difference from
|
||||
# a regular sticky implementation is that the element is constrained
|
||||
# by a containing element (or top and bottom elements), allowing the
|
||||
# element to float and stick, but only within the given bounds.
|
||||
#
|
||||
# to use, simply call .floatingSticky(containing_element) on a
|
||||
# jQuery object. optionally the top or bottom constraining element
|
||||
# can be overridden by providing {top:...} or {bottom:...} as the
|
||||
# last argument when calling .floatingSticky(...).
|
||||
#
|
||||
# the returned array has a floating sticky instance for each object
|
||||
# in the jQuery set, allowing calls to reposition() (in case the
|
||||
# element should be repositioned outside of a scroll/resize event)
|
||||
# or remove() to remove the floating sticky instance from the
|
||||
# element.
|
||||
|
||||
instanceID = 0
|
||||
class FloatingSticky
|
||||
constructor: (el, container, options={}) ->
|
||||
@instanceID = "floatingSticky#{instanceID++}"
|
||||
|
||||
@$window = $(window)
|
||||
@$el = $(el)
|
||||
@$top = $(options.top || container)
|
||||
@$bottom = $(options.bottom || container)
|
||||
|
||||
@$el.data('floatingSticky', this)
|
||||
|
||||
@$window.on "scroll.#{@instanceID} resize.#{@instanceID}", =>
|
||||
@reposition()
|
||||
@reposition()
|
||||
|
||||
remove: ->
|
||||
@$window.off @instanceID
|
||||
@$el.data('floatingSticky', null)
|
||||
|
||||
reposition: ->
|
||||
windowTop = @$window.scrollTop()
|
||||
windowHeight = @$window.height()
|
||||
|
||||
# handle overscroll (up or down)
|
||||
if windowTop < 0
|
||||
windowTop = 0
|
||||
else
|
||||
windowTop = Math.min(windowTop, document.body.scrollHeight - windowHeight)
|
||||
|
||||
# handle top of container
|
||||
containerTop = @$top.offset().top
|
||||
if windowTop < containerTop
|
||||
if windowTop == 0
|
||||
newTop = containerTop
|
||||
else
|
||||
newTop = containerTop - windowTop
|
||||
|
||||
# handle bottom of container
|
||||
else
|
||||
newTop = 0
|
||||
elHeight = @$el.height()
|
||||
containerBottom = @$bottom.offset().top + @$bottom.height()
|
||||
|
||||
# stay within the container
|
||||
if windowTop + elHeight > containerBottom
|
||||
newTop = containerBottom - elHeight - windowTop
|
||||
|
||||
# but don't go above the container
|
||||
if newTop < containerTop - windowTop
|
||||
newTop = containerTop - windowTop
|
||||
|
||||
@$el.css(top: newTop)
|
||||
|
||||
$.fn.floatingSticky = (container, options={}) ->
|
||||
@map ->
|
||||
floatingSticky = $(this).data('floatingSticky')
|
||||
floatingSticky = new FloatingSticky(this, container, options) unless floatingSticky
|
||||
floatingSticky
|
||||
|
||||
FloatingSticky
|
|
@ -47,7 +47,7 @@ define [
|
|||
latestRevision: (options) ->
|
||||
if !@_latestRevision && @get('url')
|
||||
unless @_latestRevision
|
||||
revisionOptions = _.extend({}, {@contextAssetString, pageUrl: @get('url'), latest: true, summary: true}, options)
|
||||
revisionOptions = _.extend({}, {@contextAssetString, page: @, pageUrl: @get('url'), latest: true, summary: true}, options)
|
||||
@_latestRevision = new WikiPageRevision({revision_id: @revision}, revisionOptions)
|
||||
@_latestRevision
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
define [
|
||||
'underscore'
|
||||
'Backbone'
|
||||
'i18n!pages'
|
||||
'compiled/backbone-ext/DefaultUrlMixin'
|
||||
'compiled/str/splitAssetString'
|
||||
], (_, Backbone, DefaultUrlMixin, splitAssetString) ->
|
||||
], (_, Backbone, I18n, DefaultUrlMixin, splitAssetString) ->
|
||||
|
||||
pageRevisionOptions = ['contextAssetString', 'pageUrl', 'latest', 'summary']
|
||||
pageRevisionOptions = ['contextAssetString', 'page', 'pageUrl', 'latest', 'summary']
|
||||
|
||||
class WikiPageRevision extends Backbone.Model
|
||||
@mixin DefaultUrlMixin
|
||||
|
@ -13,7 +14,9 @@ define [
|
|||
initialize: (attributes, options) ->
|
||||
super
|
||||
_.extend(this, _.pick(options || {}, pageRevisionOptions))
|
||||
@set(id: attributes.url) if attributes?.url
|
||||
|
||||
# the CollectionView managing the revisions "accidentally" passes in a url, so we have to nuke it here...
|
||||
delete @url if _.has(@, 'url')
|
||||
|
||||
urlRoot: ->
|
||||
"/api/v1/#{@_contextPath()}/pages/#{@pageUrl}/revisions"
|
||||
|
@ -21,7 +24,7 @@ define [
|
|||
url: ->
|
||||
base = @urlRoot()
|
||||
return "#{base}/latest" if @latest
|
||||
return "#{base}/#{@get('id')}" if @get('id')
|
||||
return "#{base}/#{@get('revision_id')}" if @get('revision_id')
|
||||
return base
|
||||
|
||||
fetch: (options={}) ->
|
||||
|
@ -51,3 +54,9 @@ define [
|
|||
|
||||
toJSON: ->
|
||||
_.omit super, 'id'
|
||||
|
||||
restore: ->
|
||||
d = $.ajaxJSON(@url(), 'POST').fail ->
|
||||
$.flashError I18n.t 'restore_failed', 'Failed to restore page revision'
|
||||
$('#wiki_page_revisions').disableWhileLoading($.Deferred())
|
||||
d
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
define [
|
||||
'Backbone'
|
||||
'jst/wiki/WikiPageContent'
|
||||
], (Backbone, template) ->
|
||||
|
||||
class WikiPageContentView extends Backbone.View
|
||||
tagName: 'article'
|
||||
className: 'show-content user_content'
|
||||
template: template
|
||||
|
||||
@optionProperty 'modules_path'
|
||||
@optionProperty 'wiki_pages_path'
|
||||
@optionProperty 'wiki_page_edit_path'
|
||||
@optionProperty 'wiki_page_history_path'
|
||||
@optionProperty 'WIKI_RIGHTS'
|
||||
@optionProperty 'PAGE_RIGHTS'
|
||||
@optionProperty 'course_id'
|
||||
@optionProperty 'course_home'
|
||||
@optionProperty 'course_title'
|
||||
|
||||
initialize: ->
|
||||
super
|
||||
@WIKI_RIGHTS ||= {}
|
||||
@PAGE_RIGHTS ||= {}
|
||||
@setModel(@model)
|
||||
|
||||
afterRender: ->
|
||||
super
|
||||
$.publish('userContent/change')
|
||||
@trigger('render')
|
||||
|
||||
setModel: (model) ->
|
||||
@model?.off null, null, @
|
||||
|
||||
@model = model
|
||||
@model?.on 'change:title', (=> @render()), @
|
||||
@model?.on 'change:body', (=> @render()), @
|
||||
@render()
|
||||
|
||||
toJSON: ->
|
||||
json = super
|
||||
json.modules_path = @modules_path
|
||||
json.wiki_pages_path = @wiki_pages_path
|
||||
json.wiki_page_edit_path = @wiki_page_edit_path
|
||||
json.wiki_page_history_path = @wiki_page_history_path
|
||||
json.course_home = @course_home
|
||||
json.course_title = @course_title
|
||||
json.CAN =
|
||||
VIEW_PAGES: !!@WIKI_RIGHTS.read
|
||||
PUBLISH: !!@WIKI_RIGHTS.manage && json.contextName == 'courses'
|
||||
UPDATE_CONTENT: !!@PAGE_RIGHTS.update || !!@PAGE_RIGHTS.update_content
|
||||
DELETE: !!@PAGE_RIGHTS.delete && !@course_home
|
||||
READ_REVISIONS: !!@PAGE_RIGHTS.read_revisions
|
||||
json.CAN.ACCESS_GEAR_MENU = json.CAN.DELETE || json.CAN.READ_REVISIONS
|
||||
json.CAN.VIEW_TOOLBAR = json.CAN.VIEW_PAGES || json.CAN.PUBLISH || json.CAN.UPDATE_CONTENT || json.CAN.ACCESS_GEAR_MENU
|
||||
|
||||
json.lock_info = _.clone(json.lock_info) if json.lock_info
|
||||
if json.lock_info?.unlock_at
|
||||
json.lock_info.unlock_at = if Date.parse(json.lock_info.unlock_at) < Date.now()
|
||||
null
|
||||
else
|
||||
$.parseFromISO(json.lock_info.unlock_at).datetime_formatted
|
||||
|
||||
json
|
|
@ -0,0 +1,39 @@
|
|||
define [
|
||||
'underscore'
|
||||
'Backbone'
|
||||
'jst/wiki/WikiPageRevision'
|
||||
], (_, Backbone, template) ->
|
||||
|
||||
class WikiPageRevisionView extends Backbone.View
|
||||
tagName: 'li'
|
||||
className: 'revision clearfix'
|
||||
template: template
|
||||
|
||||
events:
|
||||
'click .restore-link': 'restore'
|
||||
|
||||
initialize: ->
|
||||
super
|
||||
@model.on 'change', => @render()
|
||||
|
||||
afterRender: ->
|
||||
super
|
||||
@$el.toggleClass('selected', !!@model.get('selected'))
|
||||
@$el.toggleClass('latest', !!@model.get('latest'))
|
||||
|
||||
toJSON: ->
|
||||
latest = @model.collection?.latest
|
||||
json = _.extend {}, super,
|
||||
IS:
|
||||
LATEST: !!@model.get('latest')
|
||||
SELECTED: !!@model.get('selected')
|
||||
LOADED: !!@model.get('title') && !!@model.get('body')
|
||||
json.IS.SAME_AS_LATEST = json.IS.LOADED && (@model.get('title') == latest?.get('title')) && (@model.get('body') == latest?.get('body'))
|
||||
json.updated_at = $.parseFromISO(json.updated_at).datetime_formatted
|
||||
json.edited_by = json.edited_by?.display_name
|
||||
json
|
||||
|
||||
restore: (ev) ->
|
||||
ev?.preventDefault()
|
||||
@model.restore().done =>
|
||||
window.location.reload()
|
|
@ -0,0 +1,98 @@
|
|||
define [
|
||||
'underscore'
|
||||
'Backbone'
|
||||
'compiled/views/CollectionView'
|
||||
'compiled/views/wiki/WikiPageRevisionView'
|
||||
'jst/wiki/WikiPageRevisions'
|
||||
'compiled/jquery/floatingSticky'
|
||||
], (_, Backbone, CollectionView, WikiPageRevisionView, template) ->
|
||||
|
||||
class WikiPageRevisionsView extends CollectionView
|
||||
className: 'show-revisions'
|
||||
template: template
|
||||
itemView: WikiPageRevisionView
|
||||
|
||||
@mixin
|
||||
events:
|
||||
'click .prev-button': 'prevPage'
|
||||
'click .next-button': 'nextPage'
|
||||
'click .close-button': 'close'
|
||||
els:
|
||||
'#ticker': '$ticker'
|
||||
'aside': '$aside'
|
||||
'.revisions-list': '$revisionsList'
|
||||
|
||||
initialize: (options) ->
|
||||
super
|
||||
@selectedRevision = null
|
||||
|
||||
# handle selection changes
|
||||
@on 'selectionChanged', (newSelection, oldSelection) =>
|
||||
oldSelection.model?.set('selected', false)
|
||||
newSelection.model?.set('selected', true)
|
||||
|
||||
# reposition after rendering
|
||||
@on 'render renderItem', => @reposition()
|
||||
|
||||
afterRender: ->
|
||||
super
|
||||
$.publish('userContent/change')
|
||||
@trigger('render')
|
||||
|
||||
@floatingSticky = @$aside.floatingSticky('#main', {top: '#content'})
|
||||
|
||||
remove: ->
|
||||
if @floatingSticky
|
||||
_.each @floatingSticky, (sticky) -> sticky.remove()
|
||||
@floatingSticky = null
|
||||
|
||||
super
|
||||
|
||||
renderItem: ->
|
||||
super
|
||||
@trigger('renderItem')
|
||||
|
||||
attachItemView: (model, view) ->
|
||||
if !!@selectedRevision && @selectedRevision.get('revision_id') == model.get('revision_id')
|
||||
model.set(@selectedRevision.attributes)
|
||||
model.set('selected', true)
|
||||
@setSelectedModelAndView(model, view)
|
||||
else
|
||||
model.set('selected', false)
|
||||
|
||||
selectModel = =>
|
||||
@setSelectedModelAndView(model, view)
|
||||
selectModel() unless @selectedModel
|
||||
|
||||
view.$el.on 'click', selectModel
|
||||
|
||||
setSelectedModelAndView: (model, view) ->
|
||||
oldSelectedModel = @selectedModel
|
||||
oldSelectedView = @selectedView
|
||||
@selectedModel = model
|
||||
@selectedView = view
|
||||
@selectedRevision = model
|
||||
@trigger 'selectionChanged', {model: model, view: view}, {model: oldSelectedModel, view: oldSelectedView}
|
||||
|
||||
reposition: ->
|
||||
if @floatingSticky
|
||||
_.each @floatingSticky, (sticky) -> sticky.reposition()
|
||||
|
||||
prevPage: (ev) ->
|
||||
ev?.preventDefault()
|
||||
@$el.disableWhileLoading @collection.fetch page: 'prev', reset: true
|
||||
|
||||
nextPage: (ev) ->
|
||||
ev?.preventDefault()
|
||||
@$el.disableWhileLoading @collection.fetch page: 'next', reset: true
|
||||
|
||||
close: (ev) ->
|
||||
ev?.preventDefault()
|
||||
window.location.href = @collection.parentModel.get('html_url')
|
||||
|
||||
toJSON: ->
|
||||
json = super
|
||||
json.CAN =
|
||||
FETCH_PREV: @collection.canFetch('prev')
|
||||
FETCH_NEXT: @collection.canFetch('next')
|
||||
json
|
|
@ -23,6 +23,11 @@ class WikiPageRevisionsController < ApplicationController
|
|||
before_filter { |c| c.active_tab = "pages" }
|
||||
|
||||
def index
|
||||
if @context.draft_state_enabled?
|
||||
redirect_to polymorphic_url([@context, :named_page_revisions], :wiki_page_id => @page)
|
||||
return
|
||||
end
|
||||
|
||||
if authorized_action(@page, @current_user, :update_content)
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
|
@ -54,6 +59,11 @@ class WikiPageRevisionsController < ApplicationController
|
|||
end
|
||||
|
||||
def show
|
||||
if @context.draft_state_enabled?
|
||||
redirect_to polymorphic_url([@context, :named_page_revisions], :wiki_page_id => @page)
|
||||
return
|
||||
end
|
||||
|
||||
if authorized_action(@page, @current_user, :update_content)
|
||||
if params[:id] == "latest"
|
||||
@revision = @page.versions[0]
|
||||
|
|
|
@ -21,8 +21,8 @@ class WikiPagesController < ApplicationController
|
|||
|
||||
before_filter :require_context
|
||||
before_filter :get_wiki_page
|
||||
before_filter :set_js_rights, :only => [:pages_index, :show_page, :edit_page]
|
||||
before_filter :set_js_wiki_data, :only => [:pages_index, :show_page, :edit_page]
|
||||
before_filter :set_js_rights, :only => [:pages_index, :show_page, :edit_page, :page_revisions]
|
||||
before_filter :set_js_wiki_data, :only => [:pages_index, :show_page, :edit_page, :page_revisions]
|
||||
add_crumb(proc { t '#crumbs.wiki_pages', "Pages"}) do |c|
|
||||
url = nil
|
||||
context = c.instance_variable_get('@context')
|
||||
|
@ -221,6 +221,26 @@ class WikiPagesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def page_revisions
|
||||
if !@context.draft_state_enabled?
|
||||
redirect_to polymorphic_url([@context, @page, :wiki_page_revisions])
|
||||
return
|
||||
end
|
||||
|
||||
if is_authorized_action?(@page, @current_user, :read_revisions)
|
||||
add_crumb(@page.title, polymorphic_url([@context, :named_page], :wiki_page_id => @page))
|
||||
add_crumb(t("#crumbs.revisions", "Revisions"))
|
||||
|
||||
@padless = true
|
||||
render
|
||||
else
|
||||
if authorized_action(@page, @current_user, :read)
|
||||
flash[:warning] = t('notices.cannot_read_revisions', 'You are not allowed to review the historical revisions of "%{title}".', :title => @page.title)
|
||||
redirect_to polymorphic_url([@context, :named_page], :wiki_page_id => @page)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def context_wiki_page_url(opts={})
|
||||
|
|
|
@ -23,7 +23,15 @@ $item-hover-background: #eef7ff
|
|||
$shadow: 0 1px 0 rgba(0,0,0,0.15)
|
||||
$table-shadow: 0 1px 0 #dde0e4
|
||||
|
||||
.pages .sticky
|
||||
$revision-sidebar-width: 270px
|
||||
$revision-sidebar-background-color: #f6f8fa
|
||||
$revision-sidebar-selected-color: #ffffff
|
||||
$revision-sidebar-hover-color: #eef1f3
|
||||
$revision-sidebar-dimmed-color: #999999
|
||||
$revision-button-color: #999999
|
||||
$revision-button-hover-color: #2590cf
|
||||
|
||||
.pages .sticky-toolbar .sticky
|
||||
position: fixed
|
||||
z-index: 1000
|
||||
top: 0px
|
||||
|
@ -35,15 +43,16 @@ $table-shadow: 0 1px 0 #dde0e4
|
|||
max-width: $max_main_width
|
||||
border-color: $menu-border-color
|
||||
|
||||
.pages.with-left-side .sticky
|
||||
.pages.with-left-side .sticky-toolbar .sticky
|
||||
margin-left: $left_side_width + 1px
|
||||
.pages.with-right-side .sticky
|
||||
.pages.with-right-side .sticky-toolbar .sticky
|
||||
margin-right: $right_side_width + 1px
|
||||
|
||||
.pages
|
||||
.pages:not(.revisions)
|
||||
#breadcrumbs
|
||||
:border-bottom $pages-border
|
||||
|
||||
.pages
|
||||
.header-bar-outer-container
|
||||
:height 64px
|
||||
:clear right
|
||||
|
@ -296,10 +305,135 @@ $table-shadow: 0 1px 0 #dde0e4
|
|||
:margin-top 0
|
||||
:margin-bottom 0
|
||||
|
||||
.pages.show.revisions
|
||||
.revision:first-child
|
||||
:border-top $pages-inner-border
|
||||
|
||||
#wiki_page_revisions
|
||||
:position relative
|
||||
:min-height 502px
|
||||
.show-content
|
||||
:float left
|
||||
:margin-right $revision-sidebar-width + 1px
|
||||
.show-revisions
|
||||
:position absolute
|
||||
:top 0
|
||||
:bottom 0
|
||||
:right 0
|
||||
:width $revision-sidebar-width
|
||||
:background-color $revision-sidebar-background-color
|
||||
:border-left $pages-border
|
||||
aside
|
||||
:width $revision-sidebar-width
|
||||
:position fixed
|
||||
.revision-history
|
||||
:text-transform uppercase
|
||||
:font-size 14px
|
||||
:font-weight bold
|
||||
:line-height 40px
|
||||
:padding 4px 11px
|
||||
:margin-top -1px
|
||||
:margin-left -1px
|
||||
:border-left $pages-border
|
||||
:border-top $pages-border
|
||||
:border-bottom none
|
||||
:color $subdued-text-color
|
||||
:position relative
|
||||
.revision-history .close-button
|
||||
:position absolute
|
||||
:top 3px
|
||||
:bottom 3px
|
||||
:right 3px
|
||||
:width 42px
|
||||
:text-align center
|
||||
:color dimgrey
|
||||
&:hover
|
||||
:color $revision-button-hover-color
|
||||
i.icon-x
|
||||
:position absolute
|
||||
:top 50%
|
||||
:margin-top -8px
|
||||
:right 13px
|
||||
ul.revisions-list
|
||||
:margin 0 0 0 -1px
|
||||
:height 400px
|
||||
|
||||
.revision
|
||||
:list-style-type none
|
||||
:overflow hidden
|
||||
:width $revision-sidebar-width + 1px
|
||||
:margin-top -1px
|
||||
|
||||
:border-left none
|
||||
:border-right none
|
||||
:border-top 1px solid transparent
|
||||
:border-bottom 1px solid transparent
|
||||
&.selected
|
||||
:border-top $pages-border
|
||||
:border-bottom $pages-border
|
||||
|
||||
// height and transitions
|
||||
:height 34px
|
||||
:transition height 400ms
|
||||
&.selected, &.latest
|
||||
:height 56px
|
||||
:transition height 400ms
|
||||
|
||||
.revision-content
|
||||
:margin-left 1px
|
||||
:padding 7px 8px
|
||||
&:not(.selected):hover .revision-content
|
||||
:background-color $revision-sidebar-hover-color
|
||||
:cursor pointer
|
||||
.revision-actions
|
||||
:margin-top 2px
|
||||
:font-style italic
|
||||
:color $revision-sidebar-dimmed-color
|
||||
.revision-actions a.restore-link
|
||||
:font-style normal
|
||||
:cursor pointer
|
||||
&.selected
|
||||
:background-color $revision-sidebar-selected-color
|
||||
|
||||
.revision-nav-buttons
|
||||
:position relative
|
||||
:padding 0
|
||||
:height 40px
|
||||
:margin-top 14px
|
||||
:border-top $pages-inner-border
|
||||
a
|
||||
:user-select none
|
||||
:font-weight bold
|
||||
:font-size 12px
|
||||
:text-transform uppercase
|
||||
:padding 10px 10px
|
||||
:color $revision-button-color
|
||||
a:hover
|
||||
:text-decoration none
|
||||
:cursor pointer
|
||||
:color $revision-button-hover-color
|
||||
:border-radius 3px
|
||||
.prev-button
|
||||
:position absolute
|
||||
:left 3px
|
||||
:top 3px
|
||||
:padding 7px 10px 7px 5px
|
||||
.next-button
|
||||
:position absolute
|
||||
:right 3px
|
||||
:top 3px
|
||||
:padding 7px 5px 7px 10px
|
||||
|
||||
@media print
|
||||
#breadcrumbs, .header-bar-outer-container
|
||||
:display none
|
||||
|
||||
.pages.show.revisions
|
||||
.show-revisions
|
||||
:display none
|
||||
.show-content
|
||||
:margin-right 0
|
||||
|
||||
#wiki_show_view_main
|
||||
:overflow auto
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<header>
|
||||
<h2 class="page-title">{{title}}</h2>
|
||||
</header>
|
||||
|
||||
{{#if locked_for_user}}
|
||||
<div class="locked-alert alert alert-warning">
|
||||
{{#if lock_info.context_module.prerequisites}}
|
||||
{{#if lock_info.unlock_at}}
|
||||
{{#t 'page_locked_by_modules_until'}}This page will be available on {{lock_info.unlock_at}} if you have completed these modules:{{/t}}
|
||||
{{else}}
|
||||
{{#t 'page_locked_by_modules'}}This page will be available once you have completed these modules:{{/t}}
|
||||
{{/if}}
|
||||
<ul>
|
||||
{{#each lock_info.context_module.prerequisites}}{{#ifEqual this.type "context_module"}}
|
||||
{{#if this.name}}
|
||||
<li>
|
||||
{{#ifAll ../../../../modules_path this.id}}
|
||||
<a href="{{../../../../modules_path}}/{{this.id}}">{{this.name}}</a>
|
||||
{{else}}
|
||||
{{this.name}}
|
||||
{{/ifAll}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/ifEqual}}{{/each}}
|
||||
</ul>
|
||||
{{else}}
|
||||
{{#if lock_info.unlock_at}}
|
||||
{{#t 'page_locked_until'}}This page will be available on {{lock_info.unlock_at}}{{/t}}
|
||||
{{else}}
|
||||
{{#t 'page_locked'}}This page is locked{{/t}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{else}}
|
||||
{{{body}}}
|
||||
{{/if}}
|
|
@ -0,0 +1,23 @@
|
|||
<div class="revision-content">
|
||||
<div class="revision-details">
|
||||
{{#if edited_by}}
|
||||
{{#t 'revision_summary'}}<strong>{{updated_at}}</strong> by {{edited_by}}{{/t}}
|
||||
{{else}}
|
||||
<strong>{{updated_at}}</strong>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if IS.LATEST}}
|
||||
<div class="revision-actions">{{#t 'latest_revision'}}Latest Revision{{/t}}</div>
|
||||
{{else}}
|
||||
{{#ifAll IS.SELECTED IS.LOADED}}
|
||||
<div class="revision-actions">
|
||||
{{#if IS.SAME_AS_LATEST}}
|
||||
{{#t 'same_as_latest'}}Same as <strong>Latest</strong>{{/t}}
|
||||
{{else}}
|
||||
<a class="restore-link">{{#t 'restore_revision'}}Restore this revision{{/t}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/ifAll}}
|
||||
{{/if}}
|
||||
</div>
|
|
@ -0,0 +1,18 @@
|
|||
<aside>
|
||||
<div class="revision-history">
|
||||
{{#t 'revision_history'}}Revision History{{/t}}
|
||||
<a href="#" class="close-button"><i class="icon-x"></i></a>
|
||||
</div>
|
||||
<ul class="collectionViewItems revisions-list">
|
||||
</ul>
|
||||
{{#ifAny CAN.FETCH_PREV CAN.FETCH_NEXT}}
|
||||
<div class="revision-nav-buttons">
|
||||
{{#if CAN.FETCH_PREV}}
|
||||
<a class="prev-button"><i class="icon-mini-arrow-left"></i> Previous</a>
|
||||
{{/if}}
|
||||
{{#if CAN.FETCH_NEXT}}
|
||||
<a class="next-button">Next <i class="icon-mini-arrow-right"></i></a>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/ifAny}}
|
||||
</aside>
|
|
@ -0,0 +1,6 @@
|
|||
<%
|
||||
content_for :page_title, t('page_revisions_title', '%{title} revisions : %{context_name}', :title => @page.title, :context_name => @context.name)
|
||||
js_bundle :wiki_page_revisions
|
||||
%>
|
||||
<div id="wiki_page_revisions" class="clearfix">
|
||||
</div>
|
|
@ -155,6 +155,7 @@ FakeRails3Routes.draw do
|
|||
get 'pages' => 'wiki_pages#pages_index'
|
||||
get 'pages/:wiki_page_id' => 'wiki_pages#show_page', :wiki_page_id => /[^\/]+/, :as => :named_page
|
||||
get 'pages/:wiki_page_id/edit' => 'wiki_pages#edit_page', :wiki_page_id => /[^\/]+/, :as => :edit_named_page
|
||||
get 'pages/:wiki_page_id/revisions' => 'wiki_pages#page_revisions', :wiki_page_id => /[^\/]+/, :as => :named_page_revisions
|
||||
|
||||
resources :wiki_pages, :path => :wiki do
|
||||
match 'revisions/latest' => 'wiki_page_revisions#latest_version_number', :as => :latest_version_number
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
define [
|
||||
'compiled/models/WikiPage'
|
||||
'compiled/collections/WikiPageRevisionsCollection'
|
||||
], (WikiPage, WikiPageRevisionsCollection) ->
|
||||
|
||||
module 'WikiPageRevisionsCollection'
|
||||
|
||||
test 'parentModel accepted in constructor', ->
|
||||
parentModel = new WikiPage
|
||||
collection = new WikiPageRevisionsCollection([], parentModel: parentModel)
|
||||
strictEqual collection.parentModel, parentModel, 'parentModel accepted in constructor'
|
||||
|
||||
test 'url based on parentModel', ->
|
||||
parentModel = new WikiPage {url: 'a-page'}, contextAssetString: 'course_73'
|
||||
collection = new WikiPageRevisionsCollection([], parentModel: parentModel)
|
||||
equal collection.url(), '/api/v1/courses/73/pages/a-page/revisions', 'url built properly'
|
||||
|
||||
test 'child models inherit parent url propertly', ->
|
||||
parentModel = new WikiPage {url: 'a-page'}, contextAssetString: 'course_73'
|
||||
collection = new WikiPageRevisionsCollection([], parentModel: parentModel)
|
||||
collection.add(revision_id: 37)
|
||||
equal collection.models.length, 1, 'child model added'
|
||||
equal collection.models[0].url(), '/api/v1/courses/73/pages/a-page/revisions/37', 'child url built properly'
|
|
@ -1,12 +1,16 @@
|
|||
define [
|
||||
'jquery'
|
||||
'underscore'
|
||||
'compiled/models/WikiPage'
|
||||
'compiled/models/WikiPageRevision'
|
||||
], (_, WikiPageRevision) ->
|
||||
], ($, _, WikiPage, WikiPageRevision) ->
|
||||
|
||||
module 'WikiPageRevision::urls'
|
||||
test 'captures contextAssetString, pageUrl, latest, and summary as constructor options', ->
|
||||
revision = new WikiPageRevision {}, contextAssetString: 'course_73', pageUrl: 'page-url', latest: true, summary: true
|
||||
test 'captures contextAssetString, page, pageUrl, latest, and summary as constructor options', ->
|
||||
page = new WikiPage
|
||||
revision = new WikiPageRevision {}, contextAssetString: 'course_73', page: page, pageUrl: 'page-url', latest: true, summary: true
|
||||
strictEqual revision.contextAssetString, 'course_73', 'contextAssetString'
|
||||
strictEqual revision.page, page, 'page'
|
||||
strictEqual revision.pageUrl, 'page-url', 'pageUrl'
|
||||
strictEqual revision.latest, true, 'latest'
|
||||
strictEqual revision.summary, true, 'summary'
|
||||
|
@ -19,24 +23,29 @@ define [
|
|||
revision = new WikiPageRevision {}, contextAssetString: 'course_73', pageUrl: 'page-url'
|
||||
strictEqual revision.url(), '/api/v1/courses/73/pages/page-url/revisions', 'base url'
|
||||
|
||||
test 'url is affected by the latest flag', ->
|
||||
revision = new WikiPageRevision {id: 42}, contextAssetString: 'course_73', pageUrl: 'page-url', latest: true
|
||||
strictEqual revision.url(), '/api/v1/courses/73/pages/page-url/revisions/latest', 'latest'
|
||||
test 'url is affected by the revision_id attribute', ->
|
||||
revision = new WikiPageRevision {revision_id: 42}, contextAssetString: 'course_73', pageUrl: 'page-url'
|
||||
strictEqual revision.url(), '/api/v1/courses/73/pages/page-url/revisions/42', 'revision 42'
|
||||
|
||||
test 'url is affected by the id', ->
|
||||
revision = new WikiPageRevision {id: 42}, contextAssetString: 'course_73', pageUrl: 'page-url'
|
||||
strictEqual revision.url(), '/api/v1/courses/73/pages/page-url/revisions/42', 'id'
|
||||
test 'url is affected by the latest flag', ->
|
||||
revision = new WikiPageRevision {revision_id: 42}, contextAssetString: 'course_73', pageUrl: 'page-url', latest: true
|
||||
strictEqual revision.url(), '/api/v1/courses/73/pages/page-url/revisions/latest', 'latest'
|
||||
|
||||
module 'WikiPageRevision::parse'
|
||||
test 'parse sets the id to the url', ->
|
||||
revision = new WikiPageRevision {url: 'url'}
|
||||
strictEqual revision.get('id'), 'url', 'url set through constructor'
|
||||
revision = new WikiPageRevision
|
||||
strictEqual revision.parse({url: 'bob'}).id, 'bob', 'url set through parse'
|
||||
|
||||
test 'toJSON omits the id', ->
|
||||
revision = new WikiPageRevision {url: 'url'}
|
||||
strictEqual revision.toJSON().id, undefined, 'id omitted'
|
||||
|
||||
test 'restore POSTs to the revision', ->
|
||||
revision = new WikiPageRevision {revision_id: 42}, contextAssetString: 'course_73', pageUrl: 'page-url'
|
||||
mock = @mock($)
|
||||
mock.expects('ajaxJSON').atLeast(1).withArgs('/api/v1/courses/73/pages/page-url/revisions/42', 'POST').returns($.Deferred())
|
||||
revision.restore()
|
||||
|
||||
|
||||
module 'WikiPageRevision::fetch'
|
||||
test 'the summary flag is passed to the server', ->
|
||||
|
|
|
@ -29,6 +29,10 @@ define [
|
|||
wikiPage = new WikiPage {url: 'url'}, revision: 42
|
||||
equal wikiPage.latestRevision().get('revision_id'), 42, 'revision passed to latestRevision'
|
||||
|
||||
test 'wiki page passed to latestRevision', ->
|
||||
wikiPage = new WikiPage {url: 'url'}
|
||||
equal wikiPage.latestRevision().page, wikiPage, 'wiki page passed to latestRevision'
|
||||
|
||||
test 'latestRevision should be marked as latest', ->
|
||||
wikiPage = new WikiPage {url: 'url'}
|
||||
equal wikiPage.latestRevision().latest, true, 'marked as latest'
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
define [
|
||||
'compiled/models/WikiPage'
|
||||
'compiled/views/wiki/WikiPageContentView'
|
||||
], (WikiPage, WikiPageContentView) ->
|
||||
|
||||
module 'WikiPageContentView'
|
||||
|
||||
test 'setModel causes a re-render', ->
|
||||
wikiPage = new WikiPage
|
||||
contentView = new WikiPageContentView
|
||||
@mock(contentView).expects('render').atLeast(1)
|
||||
contentView.setModel(wikiPage)
|
||||
|
||||
test 'setModel binds to the model change:title trigger', ->
|
||||
wikiPage = new WikiPage
|
||||
contentView = new WikiPageContentView
|
||||
contentView.setModel(wikiPage)
|
||||
@mock(contentView).expects('render').atLeast(1)
|
||||
wikiPage.set('title', 'A New Title')
|
||||
|
||||
test 'setModel binds to the model change:title trigger', ->
|
||||
wikiPage = new WikiPage
|
||||
contentView = new WikiPageContentView
|
||||
contentView.setModel(wikiPage)
|
||||
@mock(contentView).expects('render').atLeast(1)
|
||||
wikiPage.set('body', 'A New Body')
|
||||
|
||||
test 'render publishes a "userContent/change" (to enhance user content)', ->
|
||||
contentView = new WikiPageContentView
|
||||
$.subscribe('userContent/change', @mock().atLeast(1))
|
||||
contentView.render()
|
|
@ -0,0 +1,37 @@
|
|||
define [
|
||||
'compiled/models/WikiPageRevision'
|
||||
'compiled/collections/WikiPageRevisionsCollection'
|
||||
'compiled/views/wiki/WikiPageRevisionView'
|
||||
], (WikiPageRevision, WikiPageRevisionsCollection, WikiPageRevisionView) ->
|
||||
|
||||
module 'WikiPageRevisionView'
|
||||
|
||||
test 'binds to model change triggers', ->
|
||||
revision = new WikiPageRevision
|
||||
view = new WikiPageRevisionView model: revision
|
||||
@mock(view).expects('render').atLeast(1)
|
||||
revision.set('body', 'A New Body')
|
||||
|
||||
test 'restore delegates to model.restore', ->
|
||||
revision = new WikiPageRevision
|
||||
view = new WikiPageRevisionView model: revision
|
||||
@mock(revision).expects('restore').atLeast(1).returns($.Deferred())
|
||||
view.restore()
|
||||
|
||||
test 'toJSON serializes expected values', ->
|
||||
attributes =
|
||||
latest: true
|
||||
selected: true
|
||||
title: 'Title'
|
||||
body: 'Body'
|
||||
|
||||
revision = new WikiPageRevision attributes
|
||||
collection = new WikiPageRevisionsCollection [revision]
|
||||
collection.latest = new WikiPageRevision attributes
|
||||
view = new WikiPageRevisionView model: revision
|
||||
json = view.toJSON()
|
||||
|
||||
strictEqual json.IS?.LATEST, true, 'IS.LATEST'
|
||||
strictEqual json.IS?.SELECTED, true, 'IS.SELECTED'
|
||||
strictEqual json.IS?.LOADED, true, 'IS.LOADED'
|
||||
strictEqual json.IS?.SAME_AS_LATEST, true, 'IS.SAME_AS_LATEST'
|
|
@ -0,0 +1,54 @@
|
|||
define [
|
||||
'compiled/collections/WikiPageRevisionsCollection'
|
||||
'compiled/views/wiki/WikiPageRevisionsView'
|
||||
], (WikiPageRevisionsCollection, WikiPageRevisionsView) ->
|
||||
|
||||
module 'WikiPageRevisionsView'
|
||||
|
||||
test 'selecting a model/view sets the selected attribute on the model', ->
|
||||
fixture = $('<div id="main"><div id="content"></div></div>').appendTo('#fixtures')
|
||||
|
||||
collection = new WikiPageRevisionsCollection
|
||||
view = new WikiPageRevisionsView collection: collection
|
||||
view.$el.appendTo('#content')
|
||||
view.render()
|
||||
|
||||
collection.add(revision_id: 21)
|
||||
collection.add(revision_id: 37)
|
||||
strictEqual collection.models.length, 2, 'models added to collection'
|
||||
|
||||
view.setSelectedModelAndView(collection.models[0], collection.models[0].view)
|
||||
strictEqual collection.models[0].get('selected'), true, 'selected attribute set'
|
||||
strictEqual collection.models[1].get('selected'), false, 'selected attribute not set'
|
||||
|
||||
view.setSelectedModelAndView(collection.models[1], collection.models[1].view)
|
||||
strictEqual collection.models[0].get('selected'), false, 'selected attribute not set'
|
||||
strictEqual collection.models[1].get('selected'), true, 'selected attribute set'
|
||||
|
||||
fixture.remove()
|
||||
|
||||
test 'prevPage fetches previous page from collection', ->
|
||||
collection = new WikiPageRevisionsCollection
|
||||
@mock(collection).expects('fetch').atLeast(1).withArgs(page: 'prev', reset: true).returns($.Deferred())
|
||||
view = new WikiPageRevisionsView collection: collection
|
||||
view.prevPage()
|
||||
|
||||
test 'nextPage fetches next page from collection', ->
|
||||
collection = new WikiPageRevisionsCollection
|
||||
@mock(collection).expects('fetch').atLeast(1).withArgs(page: 'next', reset: true).returns($.Deferred())
|
||||
view = new WikiPageRevisionsView collection: collection
|
||||
view.nextPage()
|
||||
|
||||
test 'toJSON - CAN.FETCH_PREV', ->
|
||||
collection = new WikiPageRevisionsCollection
|
||||
view = new WikiPageRevisionsView collection: collection
|
||||
@stub(collection, 'canFetch', (arg) -> arg == 'prev')
|
||||
|
||||
strictEqual view.toJSON().CAN?.FETCH_PREV, true, 'can fetch previous'
|
||||
|
||||
test 'toJSON - CAN.FETCH_NEXT', ->
|
||||
collection = new WikiPageRevisionsCollection
|
||||
view = new WikiPageRevisionsView collection: collection
|
||||
@stub(collection, 'canFetch', (arg) -> arg == 'next')
|
||||
|
||||
strictEqual view.toJSON().CAN?.FETCH_NEXT, true, 'can fetch next'
|
|
@ -129,6 +129,18 @@ describe WikiPagesController do
|
|||
response.code.should == '302'
|
||||
response.redirected_to.should =~ %r{/pages/a-page\z}
|
||||
end
|
||||
|
||||
it "should forward /wiki/name/revisions to /pages/name/revisions" do
|
||||
get @base_url + "wiki/a-page/revisions"
|
||||
response.code.should == '302'
|
||||
response.redirected_to.should =~ %r{/pages/a-page/revisions\z}
|
||||
end
|
||||
|
||||
it "should forward /wiki/name/revisions/revision to /pages/name/revisions" do
|
||||
get @base_url + "wiki/a-page/revisions/42"
|
||||
response.code.should == '302'
|
||||
response.redirected_to.should =~ %r{/pages/a-page/revisions\z}
|
||||
end
|
||||
end
|
||||
|
||||
context "draft state disabled" do
|
||||
|
@ -149,6 +161,12 @@ describe WikiPagesController do
|
|||
response.code.should == '302'
|
||||
response.redirected_to.should =~ %r{/wiki/a-page#edit\z}
|
||||
end
|
||||
|
||||
it "should forward /pages/name/revisions to /wiki/name/revisions" do
|
||||
get @base_url + "pages/a-page/revisions"
|
||||
response.code.should == '302'
|
||||
response.redirected_to.should =~ %r{/wiki/a-page/revisions\z}
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue