scrivito_editors 0.0.8

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 (82) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +48 -0
  3. data/LICENSE +4 -0
  4. data/README.md +55 -0
  5. data/Rakefile +9 -0
  6. data/app/assets/fonts/editing_icons-webfont.eot +0 -0
  7. data/app/assets/fonts/editing_icons-webfont.ttf +0 -0
  8. data/app/assets/fonts/editing_icons-webfont.woff +0 -0
  9. data/app/assets/javascripts/jquery_additions/jquery_center.js.coffee +6 -0
  10. data/app/assets/javascripts/mediabrowser/inspector.js.coffee +65 -0
  11. data/app/assets/javascripts/mediabrowser/mediabrowser.js.coffee +420 -0
  12. data/app/assets/javascripts/mediabrowser/uploader.js.coffee +132 -0
  13. data/app/assets/javascripts/scrivito_editors.js +18 -0
  14. data/app/assets/javascripts/scrivito_editors/date_editor.js.coffee +50 -0
  15. data/app/assets/javascripts/scrivito_editors/enum_editor.js.coffee +36 -0
  16. data/app/assets/javascripts/scrivito_editors/html_editor.js.coffee +140 -0
  17. data/app/assets/javascripts/scrivito_editors/linklist_editor.js.coffee +176 -0
  18. data/app/assets/javascripts/scrivito_editors/multienum_editor.js.coffee +37 -0
  19. data/app/assets/javascripts/scrivito_editors/placeholder.js.coffee +22 -0
  20. data/app/assets/javascripts/scrivito_editors/reference_editor.js.coffee +28 -0
  21. data/app/assets/javascripts/scrivito_editors/referencelist_editor.js.coffee +111 -0
  22. data/app/assets/javascripts/scrivito_editors/slider_editor.js.coffee +39 -0
  23. data/app/assets/javascripts/scrivito_editors/string_editor.js.coffee +83 -0
  24. data/app/assets/javascripts/scrivito_editors/text_editor.js.coffee +85 -0
  25. data/app/assets/stylesheets/scrivito_editors.css +16 -0
  26. data/app/assets/stylesheets/scrivito_editors/buttons.css +161 -0
  27. data/app/assets/stylesheets/scrivito_editors/editors/linklist_editor.css +105 -0
  28. data/app/assets/stylesheets/scrivito_editors/editors/referencelist_editor.css +67 -0
  29. data/app/assets/stylesheets/scrivito_editors/editors/text_editor.css +7 -0
  30. data/app/assets/stylesheets/scrivito_editors/icons.css.erb +229 -0
  31. data/app/assets/stylesheets/scrivito_editors/mediabrowser.css +1010 -0
  32. data/app/assets/stylesheets/scrivito_editors/placeholder.css +17 -0
  33. data/app/assets/stylesheets/scrivito_editors/widget_preview.css +38 -0
  34. data/app/controllers/scrivito_editors/mediabrowser_controller.rb +36 -0
  35. data/app/views/layouts/scrivito_editors/mediabrowser/inspector.html.erb +11 -0
  36. data/app/views/scrivito_editors/mediabrowser/_buttons.html.erb +16 -0
  37. data/app/views/scrivito_editors/mediabrowser/_header.html.erb +25 -0
  38. data/app/views/scrivito_editors/mediabrowser/modal.html.erb +12 -0
  39. data/app/views/scrivito_editors/obj/details.html +5 -0
  40. data/config/initializers/mediabrowser.rb +13 -0
  41. data/config/routes.rb +5 -0
  42. data/lib/scrivito_editors.rb +4 -0
  43. data/lib/scrivito_editors/engine.rb +7 -0
  44. data/lib/scrivito_editors/version.rb +3 -0
  45. data/spec/dummy/README.rdoc +28 -0
  46. data/spec/dummy/Rakefile +6 -0
  47. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  48. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  49. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  50. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  51. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  52. data/spec/dummy/bin/bundle +3 -0
  53. data/spec/dummy/bin/rails +4 -0
  54. data/spec/dummy/bin/rake +4 -0
  55. data/spec/dummy/config.ru +4 -0
  56. data/spec/dummy/config/application.rb +23 -0
  57. data/spec/dummy/config/boot.rb +5 -0
  58. data/spec/dummy/config/database.yml +25 -0
  59. data/spec/dummy/config/environment.rb +5 -0
  60. data/spec/dummy/config/environments/development.rb +29 -0
  61. data/spec/dummy/config/environments/production.rb +80 -0
  62. data/spec/dummy/config/environments/test.rb +36 -0
  63. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  64. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  65. data/spec/dummy/config/initializers/inflections.rb +16 -0
  66. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  67. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  68. data/spec/dummy/config/initializers/session_store.rb +3 -0
  69. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  70. data/spec/dummy/config/locales/en.yml +23 -0
  71. data/spec/dummy/config/routes.rb +4 -0
  72. data/spec/dummy/public/404.html +58 -0
  73. data/spec/dummy/public/422.html +58 -0
  74. data/spec/dummy/public/500.html +57 -0
  75. data/spec/dummy/public/favicon.ico +0 -0
  76. data/spec/spec_helper.rb +13 -0
  77. data/vendor/assets/fonts/redactor-font.eot +0 -0
  78. data/vendor/assets/javascripts/jquery-ui-timepicker-addon.min.js +5 -0
  79. data/vendor/assets/javascripts/redactor.js +7869 -0
  80. data/vendor/assets/stylesheets/jquery-ui-timepicker-addon.min.css +5 -0
  81. data/vendor/assets/stylesheets/redactor.css.erb +968 -0
  82. metadata +240 -0
@@ -0,0 +1,132 @@
1
+ @MediabrowserUploader = do ->
2
+ dropZoneSelector: '.editing-mediabrowser-items'
3
+ dropOverCssClass: 'uploader-drag-over'
4
+ mimeTypeMapping:
5
+ 'image/*': 'Image'
6
+ 'video/*': 'Video'
7
+
8
+ _initializeBindings: ->
9
+ dropZone = @modal.find(@dropZoneSelector)
10
+
11
+ dropZone.on 'dragover', (event) =>
12
+ $(event.currentTarget).addClass(@dropOverCssClass)
13
+ event.preventDefault()
14
+
15
+ dropZone.on 'dragleave', (event) =>
16
+ $(event.currentTarget).removeClass(@dropOverCssClass)
17
+ event.preventDefault()
18
+
19
+ dropZone.on 'drop', (event) =>
20
+ $(event.currentTarget).removeClass(@dropOverCssClass)
21
+ @_onDrop(event)
22
+ event.preventDefault()
23
+
24
+ _objClassForMimeType: (mimeType) ->
25
+ for mime, objClass of @mimeTypeMapping
26
+ return objClass if mimeType.match(mime)
27
+
28
+ undefined
29
+
30
+ _processQueue: (queue, createdObjs, promise) ->
31
+ promise.then (data) =>
32
+ @onUploadSuccess(data)
33
+
34
+ file = queue.pop()
35
+
36
+ if file?
37
+ @_createResource(file).then (obj) =>
38
+ @_updateProgress(file, '100%')
39
+ createdObjs.push(obj)
40
+ .always =>
41
+ @_processQueue(queue, createdObjs, promise)
42
+
43
+ return promise
44
+ else
45
+ return promise.resolve(createdObjs)
46
+
47
+ _addProgressWrapper: () ->
48
+ itemsElement = $('.editing-mediabrowser-items').empty()
49
+
50
+ $('<div></div>')
51
+ .addClass('editing-mediabrowser-loading')
52
+ .appendTo itemsElement
53
+
54
+ $('<div></div>')
55
+ .addClass('editing-mediabrowser-progress-wrapper')
56
+ .appendTo itemsElement
57
+
58
+ _addProgress: (file) ->
59
+ progressBar = $('<div></div>')
60
+ .addClass('editing-mediabrowser-progress-bar')
61
+ .css('width', '10%')
62
+
63
+ progress = $('<div></div>')
64
+ .addClass('editing-mediabrowser-progress')
65
+ .html(progressBar)
66
+
67
+ fileName = $('<p></p>')
68
+ .html(file.name)
69
+
70
+ $('<div></div>')
71
+ .addClass('editing-mediabrowser-progress-file')
72
+ .append(fileName)
73
+ .append(progress)
74
+ .prependTo $('.editing-mediabrowser-progress-wrapper')
75
+
76
+ file['progressBar'] = progressBar
77
+
78
+ _updateProgress: (file, percent) ->
79
+ file.progressBar.css('width', percent)
80
+
81
+ _onDrop: (event) ->
82
+ dataTransfer = event.originalEvent.dataTransfer
83
+
84
+ unless dataTransfer?
85
+ return
86
+
87
+ files = dataTransfer.files
88
+
89
+ if files.length == 0
90
+ return
91
+
92
+ @onUploadStart(queue)
93
+ @_addProgressWrapper()
94
+
95
+ promise = $.Deferred()
96
+
97
+ queue = for file in files
98
+ @_addProgress(file)
99
+ file
100
+
101
+ @_processQueue(queue, [], promise)
102
+
103
+ promise
104
+
105
+ _randomResourceId: ->
106
+ hex = Math.floor(Math.random() * Math.pow(16, 8)).toString(16)
107
+
108
+ while (hex.length < 8)
109
+ hex = '0' + hex
110
+
111
+ hex
112
+
113
+ _createResource: (file) ->
114
+ objName = file.name.replace(/[^a-z0-9_.$\-]/ig, '-')
115
+ path = "_resources/#{@_randomResourceId()}/#{objName}"
116
+
117
+ scrivito.create_obj
118
+ blob: file
119
+ _path: path
120
+ _obj_class: @_objClassForMimeType(file.type)
121
+
122
+ init: (@modal) ->
123
+ @_initializeBindings()
124
+
125
+ # Hook for 3rd parties when the upload starts.
126
+ onUploadStart: (files) ->
127
+
128
+ # Hook for 3rd parties when the upload fails.
129
+ onUploadFailure: (error) ->
130
+
131
+ # Hook for 3rd parties when the upload was successful.
132
+ onUploadSuccess: (objs) ->
@@ -0,0 +1,18 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require jquery.ui.sortable
14
+ //= require jquery.ui.datepicker
15
+ //= require jquery.ui.slider
16
+ //= require jquery-ui-timepicker-addon.min
17
+ //= require redactor
18
+ //= require_tree .
@@ -0,0 +1,50 @@
1
+ $ ->
2
+ # Define editor behavior for date attributes.
3
+
4
+ scrivito.on 'editing', ->
5
+ template = ->
6
+ $('<input />')
7
+ .attr('type', 'text')
8
+
9
+ onKeyup = (event) ->
10
+ key = event.keyCode || event.which
11
+
12
+ switch key
13
+ when 27 # Esc
14
+ # Prevent the property view to close when the ESC key is pressed.
15
+ event.stopPropagation()
16
+
17
+ save = (dateTimeText) ->
18
+ element = $(this)
19
+ cmsField = element.data('cmsField')
20
+
21
+ if dateTimeText? && dateTimeText.length > 0
22
+ dateTimeText = new Date(dateTimeText)
23
+
24
+ cmsField.scrivito('save', dateTimeText)
25
+ .done ->
26
+ cmsField.trigger('scrivito_reload')
27
+
28
+ $('body').on 'click', '[data-scrivito-field-type="date"]:not(.hasDatepicker):not([data-editor]), [data-editor="date"]', (event) ->
29
+ event.preventDefault()
30
+
31
+ cmsField = $(this)
32
+ content = cmsField.html().trim()
33
+ dateFormat = cmsField.attr('data-date-format') || 'yy-mm-dd'
34
+ timeFormat = cmsField.attr('data-time-format') || 'HH:mm:ss'
35
+
36
+ $('body').keyup(onKeyup)
37
+
38
+ template()
39
+ .data('cmsField', cmsField)
40
+ .insertAfter(cmsField)
41
+ .val(content)
42
+ .keyup(onKeyup)
43
+ .datetimepicker(
44
+ dateFormat: dateFormat
45
+ timeFormat: timeFormat
46
+ onClose: save
47
+ )
48
+ .focus()
49
+
50
+ cmsField.hide()
@@ -0,0 +1,36 @@
1
+ $ ->
2
+ # Define editor behavior for enum attributes.
3
+
4
+ scrivito.on 'editing', ->
5
+ template = (values) ->
6
+ element = $('<select></select>')
7
+ .addClass('form-control')
8
+
9
+ $.each values, (index, value) ->
10
+ $('<option></option>')
11
+ .attr('value', value)
12
+ .text(value)
13
+ .appendTo(element)
14
+
15
+ element
16
+
17
+ save = (event) ->
18
+ element = $(event.currentTarget)
19
+ cmsField = element.data('cmsField')
20
+ content = element.val()
21
+ cmsField.scrivito('save', content).done ->
22
+ cmsField.trigger('scrivito_reload')
23
+
24
+ $(document).on 'click', '[data-scrivito-field-type="enum"]:not([data-editor]), [data-editor="enum"]', (event) ->
25
+ cmsField = $(event.currentTarget)
26
+ selected = cmsField.scrivito('content')
27
+ values = cmsField.data('values')
28
+
29
+ template(values)
30
+ .data('cmsField', cmsField)
31
+ .val(selected)
32
+ .insertAfter(cmsField)
33
+ .focusout(save)
34
+ .focus()
35
+
36
+ cmsField.hide()
@@ -0,0 +1,140 @@
1
+ $ ->
2
+ # Configuration and behavior of Redactor html editor. The editor is used for all html CMS
3
+ # attributes and provides autosave, undo and redo functionality on top of the default Redactor
4
+ # settings.
5
+
6
+ # Stores the id of the last triggered timeout for later reference.
7
+ timeoutID = undefined
8
+
9
+ # Milliseconds after which to save the content automatically.
10
+ autosaveInterval = 3000
11
+
12
+ # Stores the last saved content written to the CMS to allow comparison with editor content.
13
+ savedContent = undefined
14
+
15
+ # Stores the content before the editor is opened and changes occur.
16
+ originalContent = undefined
17
+
18
+ # Stores redactor options, custom settings and configures callbacks.
19
+ redactorOptions = ->
20
+ return {} =
21
+ # This option allows you to add your own buttons with callback to the toolbar.
22
+ # http://imperavi.com/redactor/docs/settings/#set-buttonsCustom
23
+ buttonsCustom:
24
+ undoButton:
25
+ title: 'Undo'
26
+ callback: undoAction
27
+ redoButton:
28
+ title: 'Redo'
29
+ callback: redoAction
30
+
31
+ # This setting defines the array of toolbar buttons.
32
+ # http://imperavi.com/redactor/docs/settings/#set-buttons
33
+ buttons: ['undoButton', 'redoButton',
34
+ '|', 'formatting',
35
+ '|', 'bold', 'italic', 'deleted', 'underline',
36
+ '|', 'unorderedlist', 'orderedlist',
37
+ '|', 'table', 'link',
38
+ '|', 'html'
39
+ ]
40
+
41
+ # This option allows you to set whether Redactor gets cursor focus on load or not.
42
+ # http://imperavi.com/redactor/docs/settings/#set-focus
43
+ focus: true
44
+
45
+ # With this option turned on, Redactor will automatically replace divs to paragraphs.
46
+ # http://imperavi.com/redactor/docs/settings/#set-convertDivs
47
+ convertDivs: false
48
+
49
+ # This callback is triggered after Redactor is launched.
50
+ # http://imperavi.com/redactor/docs/callbacks/#callback-initCallback
51
+ initCallback: ->
52
+ originalContent = @get()
53
+
54
+ # This callback fires every time when content changes in Redactor.
55
+ # http://imperavi.com/redactor/docs/callbacks/#callback-changeCallback
56
+ changeCallback: ->
57
+ autosaveAction(@)
58
+
59
+ # This callback is triggered when Redactor loses focus.
60
+ # http://imperavi.com/redactor/docs/callbacks/#callback-blurCallback
61
+ blurCallback: ->
62
+ saveContents(@).done =>
63
+ @.destroy()
64
+ reload(@)
65
+
66
+ # This callback is triggered when a key is released.
67
+ # http://imperavi.com/redactor/docs/callbacks/#callback-keyupCallback
68
+ keyupCallback: (event) ->
69
+ event.stopPropagation()
70
+ key = event.keyCode || event.which
71
+
72
+ if key == 27
73
+ cancelEditing(@)
74
+ else
75
+ autosaveAction(@)
76
+
77
+ # This callback allows to get pasted code after clean on paste.
78
+ # http://imperavi.com/redactor/docs/callbacks/#callback-pasteAfterCallback
79
+ pasteAfterCallback: (html) ->
80
+ autosaveAction(@)
81
+ html
82
+
83
+ # Registers a timeout to save the editor content after a certain interval. The timeout gets reset
84
+ # on every change.
85
+ autosaveAction = (editor) ->
86
+ if timeoutID
87
+ clearTimeout(timeoutID)
88
+
89
+ timeoutID = setTimeout ( ->
90
+ saveContents(editor)
91
+ ), autosaveInterval
92
+
93
+ undoAction = ->
94
+ @execCommand('undo')
95
+
96
+ redoAction = ->
97
+ @execCommand('redo')
98
+
99
+ # Saves the current editor content to the CMS if it has changed.
100
+ saveContents = (editor) ->
101
+ content = editor.get()
102
+
103
+ if savedContent != content
104
+ cmsField = editor.$element
105
+ cmsField.scrivito('save', content).done ->
106
+ savedContent = content
107
+
108
+ else
109
+ $.Deferred().resolve()
110
+
111
+ reload = (editor) ->
112
+ cmsField = editor.$element
113
+ cmsField.trigger('scrivito_reload')
114
+
115
+ # Restores the original content before the editor was opened, also saves it back to the CMS
116
+ # because autosave could have overwritten the content and closes the editor.
117
+ cancelEditing = (editor) ->
118
+ editor.set(originalContent)
119
+ saveContents(editor).done ->
120
+ reload(editor)
121
+ editor.destroy()
122
+
123
+ # Registers Redactor for all CMS html attributes found in the given scope of the DOM element.
124
+ addOnclickRedactorHandlers = (domElement) ->
125
+ domElement.on 'click', '[data-scrivito-field-type="html"]:not([data-editor]), [data-editor="html"]', (event) ->
126
+ event.preventDefault()
127
+ cmsField = $(@)
128
+
129
+ unless cmsField.hasClass('redactor_editor')
130
+ cmsField.html(cmsField.scrivito('content') || '')
131
+ cmsField.redactor(redactorOptions())
132
+ cmsField.redactor('focus')
133
+
134
+ # Registers all handlers when inplace editing is activated.
135
+ scrivito.on 'editing', ->
136
+ addOnclickRedactorHandlers($('body'))
137
+
138
+ # Registers all handlers when content has changed.
139
+ scrivito.on 'new_content', (domElement) ->
140
+ addOnclickRedactorHandlers($(domElement))
@@ -0,0 +1,176 @@
1
+ $ ->
2
+ # An editor for CMS linklist attributes.
3
+
4
+ # Creates the DOM for one link element of the linklist and substitutes the
5
+ # title and url attribute.
6
+ template = (attributes) ->
7
+ attributes ||= {}
8
+
9
+ title = attributes['title'] || ''
10
+ url = attributes['url'] || ''
11
+
12
+ $("<input type=\"text\" name=\"title\" value=\"#{title}\" placeholder=\"Title\" />
13
+ <input type=\"text\" name=\"url\" value=\"#{url}\" placeholder=\"Url\" class=\"editing-url\" />
14
+ <div class=\"actions\">
15
+ <a href=\"#\" class=\"editing-button mediabrowser-open editing-green\">
16
+ <i class=\"editing-icon editing-icon-search\" />
17
+ </a>
18
+ <a href=\"#\" class=\"editing-button editing-red delete\">
19
+ <i class=\"editing-icon editing-icon-trash\" />
20
+ </a>
21
+ </div>")
22
+
23
+ mediabrowserButtonTemplate = ->
24
+ icon = $('<i></i>')
25
+ .addClass('editing-icon')
26
+ .addClass('editing-icon-plus')
27
+
28
+ button = $('<button></button>')
29
+ .addClass('editing-button')
30
+ .addClass('editing-green')
31
+ .addClass('add-link')
32
+ .html(icon)
33
+
34
+ button
35
+
36
+ # Returns the closest linklist DOM element.
37
+ getCmsField = (element) ->
38
+ element.closest('[data-scrivito-field-type=linklist]')
39
+
40
+ # Saves the entire linklist to the CMS and stores the last successfully saved value.
41
+ save = (cmsField) ->
42
+ value = getAttributes(cmsField)
43
+ lastSaved = getLastSaved(cmsField)
44
+
45
+ unless JSON.stringify(value) == JSON.stringify(lastSaved)
46
+ cmsField.scrivito('save', value).done ->
47
+ storeLastSaved(cmsField, value)
48
+
49
+ # Run when clicking the '...' button inside a li.
50
+ onOpenMediabrowser = (event) ->
51
+ event.preventDefault()
52
+
53
+ linkItem = $(event.currentTarget).closest('li')
54
+ cmsField = getCmsField(linkItem)
55
+ filters = cmsField.data('filters') || cmsField.data('filter')
56
+
57
+ Mediabrowser.open
58
+ selection: []
59
+ filters: filters
60
+ onSave: (selection) =>
61
+ onMediabrowserSaveLinkItem(selection, linkItem)
62
+
63
+ # Media browser callback for saving a single link.
64
+ onMediabrowserSaveLinkItem = (selection, linkItem) ->
65
+ url = buildUrl(selection[0])
66
+ linkItem.find('[name=url]').val(url)
67
+
68
+ # trigger save after inserting the value
69
+ cmsField = getCmsField(linkItem)
70
+ save(cmsField)
71
+
72
+ true
73
+
74
+ # Transforms an obj id into an url that can be parsed by Scrivito
75
+ # to establish an internal link.
76
+ buildUrl = (id) ->
77
+ "/#{id}"
78
+
79
+ # Collects all link attributes for a given linklist.
80
+ getAttributes = (cmsField) ->
81
+ items = $(cmsField).find('li')
82
+
83
+ attributes =
84
+ for item in items
85
+ item = $(item)
86
+ title = item.find('[name=title]').val()
87
+ url = item.find('[name=url]').val()
88
+
89
+ # Make sure the url is not empty.
90
+ if !isEmpty(url)
91
+ 'title': title
92
+ 'url': url
93
+
94
+ # Remove empty array elements.
95
+ removeEmptyElements(attributes)
96
+
97
+ isEmpty = (value) ->
98
+ !value
99
+
100
+ removeEmptyElements = (array) ->
101
+ $.grep(array, (n) -> n)
102
+
103
+ # Adds a new link to the linklist.
104
+ addLink = (event) ->
105
+ event.preventDefault()
106
+
107
+ cmsField = getCmsField($(event.currentTarget))
108
+ content = $('<li>').html(template())
109
+
110
+ cmsField.find('ul').append(content)
111
+
112
+ # Removes a link from the linklist.
113
+ removeLink = (event) ->
114
+ event.preventDefault()
115
+
116
+ target = $(event.currentTarget)
117
+ cmsField = getCmsField(target)
118
+
119
+ target.closest('li').remove()
120
+ save(cmsField)
121
+
122
+ # Turns the server side generated linklist data into the linklist editor using a template.
123
+ transformLinks = (cmsFields) ->
124
+ cmsFields.append(mediabrowserButtonTemplate)
125
+
126
+ items = cmsFields.find('li')
127
+
128
+ for item in items
129
+ item = $(item)
130
+
131
+ content = template
132
+ title: item.data('title')
133
+ url: item.data('url')
134
+
135
+ item.html(content)
136
+
137
+ # Returns the last saved value.
138
+ getLastSaved = (cmsField) ->
139
+ cmsField.data('last-saved')
140
+
141
+ # Stores a given value as last saved.
142
+ storeLastSaved = (cmsField, value) ->
143
+ $(cmsField).data('last-saved', value)
144
+
145
+ # Automatically save when focus is lost.
146
+ onBlur = (event) ->
147
+ cmsField = getCmsField($(event.currentTarget))
148
+
149
+ save(cmsField)
150
+
151
+ initialize = (root) ->
152
+ linklistElements = $(root).find('[data-scrivito-field-type="linklist"]:not([data-editor]), [data-editor="linklist"]')
153
+
154
+ if linklistElements.length
155
+ transformLinks(linklistElements)
156
+
157
+ for linklistElement in linklistElements
158
+ storeLastSaved(linklistElement, getAttributes(linklistElement))
159
+
160
+ linklistElements.on 'blur', 'li input', onBlur
161
+ linklistElements.on 'click', 'li a.delete', removeLink
162
+ linklistElements.on 'click', 'button.add-link', addLink
163
+ linklistElements.on 'click', 'a.mediabrowser-open', onOpenMediabrowser
164
+
165
+ linklistElements.find('ul').sortable
166
+ update: (event) ->
167
+ cmsField = getCmsField($(event.target))
168
+
169
+ save(cmsField)
170
+
171
+ # Initialize linklist editor and setup event callbacks.
172
+ scrivito.on 'new_content', (root) ->
173
+ initialize(root)
174
+
175
+ scrivito.on 'editing', ->
176
+ initialize(document)