From ad6d9c4f927abcaf710ec9b36563c1a6470ddb4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?"J.=20Rene=CC=81e=20Beach"?= Date: Wed, 17 Apr 2013 01:24:54 -0400 Subject: [PATCH] Crazy refactoring to get Create.js out of the mix. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: J. ReneĢe Beach --- core/misc/create/create-editonly.js | 606 ++---------------- core/modules/comment/comment.module | 2 + .../lib/Drupal/comment/CommentRenderController.php | 2 + core/modules/edit/edit.module | 37 +- core/modules/edit/js/app.js | 658 +++++++++----------- core/modules/edit/js/createjs/editable.js | 14 +- core/modules/edit/js/edit.js | 147 ++++- core/modules/edit/js/views/contextuallink-view.js | 11 +- .../edit/js/views/propertyeditordecoration-view.js | 8 +- core/modules/edit/js/views/toolbar-view.js | 35 +- .../edit/Access/EditEntityFieldAccessCheck.php | 5 +- .../edit/lib/Drupal/edit/EditController.php | 2 + .../edit/lib/Drupal/edit/Form/EditFieldForm.php | 3 + core/modules/forum/forum.admin.inc | 31 +- .../lib/Drupal/taxonomy/TermRenderController.php | 1 + core/modules/taxonomy/taxonomy.module | 2 + .../taxonomy/templates/taxonomy-term.tpl.php | 2 + 17 files changed, 556 insertions(+), 1010 deletions(-) diff --git a/core/misc/create/create-editonly.js b/core/misc/create/create-editonly.js index aed84a4..5c002cb 100644 --- a/core/misc/create/create-editonly.js +++ b/core/misc/create/create-editonly.js @@ -1,283 +1,21 @@ -// Create.js - On-site web editing interface -// (c) 2011-2012 Henri Bergius, IKS Consortium -// Create may be freely distributed under the MIT license. -// For all details and documentation: -// http://createjs.org/ -(function (jQuery, undefined) { - // Run JavaScript in strict mode - /*global jQuery:false _:false window:false console:false */ - 'use strict'; - - // # Widget for adding items to a collection - jQuery.widget('Midgard.midgardCollectionAdd', { - options: { - editingWidgets: null, - collection: null, - model: null, - definition: null, - view: null, - disabled: false, - vie: null, - editableOptions: null, - templates: { - button: '' - } - }, - - _create: function () { - this.addButtons = []; - var widget = this; - if (!widget.options.collection.localStorage) { - try { - widget.options.collection.url = widget.options.model.url(); - } catch (e) { - if (window.console) { - console.log(e); - } - } - } - - widget.options.collection.on('add', function (model) { - model.primaryCollection = widget.options.collection; - widget.options.vie.entities.add(model); - model.collection = widget.options.collection; - }); - - // Re-check collection constraints - widget.options.collection.on('add remove reset', widget.checkCollectionConstraints, widget); - - widget._bindCollectionView(widget.options.view); - }, - - _bindCollectionView: function (view) { - var widget = this; - view.on('add', function (itemView) { - itemView.$el.effect('slide', function () { - widget._makeEditable(itemView); - }); - }); - }, - - _makeEditable: function (itemView) { - this.options.editableOptions.disabled = this.options.disabled; - this.options.editableOptions.model = itemView.model; - itemView.$el.midgardEditable(this.options.editableOptions); - }, - - _init: function () { - if (this.options.disabled) { - this.disable(); - return; - } - this.enable(); - }, - - hideButtons: function () { - _.each(this.addButtons, function (button) { - button.hide(); - }); - }, - - showButtons: function () { - _.each(this.addButtons, function (button) { - button.show(); - }); - }, - - checkCollectionConstraints: function () { - if (this.options.disabled) { - return; - } - - if (!this.options.view.canAdd()) { - this.hideButtons(); - return; - } - - if (!this.options.definition) { - // We have now information on the constraints applying to this collection - this.showButtons(); - return; - } - - if (!this.options.definition.max || this.options.definition.max === -1) { - // No maximum constraint - this.showButtons(); - return; - } - - if (this.options.collection.length < this.options.definition.max) { - this.showButtons(); - return; - } - // Collection is already full by its definition - this.hideButtons(); - }, - - enable: function () { - var widget = this; - - var addButton = jQuery(_.template(this.options.templates.button, { - icon: 'plus', - label: this.options.editableOptions.localize('Add', this.options.editableOptions.language) - })).button(); - addButton.addClass('midgard-create-add'); - addButton.click(function () { - widget.addItem(addButton); - }); - jQuery(widget.options.view.el).after(addButton); - - widget.addButtons.push(addButton); - widget.checkCollectionConstraints(); - }, - - disable: function () { - _.each(this.addButtons, function (button) { - button.remove(); - }); - this.addButtons = []; - }, - _getTypeActions: function (options) { - var widget = this; - var actions = []; - _.each(this.options.definition.range, function (type) { - var nsType = widget.options.collection.vie.namespaces.uri(type); - if (!widget.options.view.canAdd(nsType)) { - return; - } - actions.push({ - name: type, - label: type, - cb: function () { - widget.options.collection.add({ - '@type': type - }, options); - }, - className: 'create-ui-btn' - }); - }); - return actions; - }, - - addItem: function (button, options) { - if (options === undefined) { - options = {}; - } - var addOptions = _.extend({}, options, { validate: false }); - - var itemData = {}; - if (this.options.definition && this.options.definition.range) { - if (this.options.definition.range.length === 1) { - // Items can be of single type, add that - itemData['@type'] = this.options.definition.range[0]; - } else { - // Ask user which type to add - jQuery('body').midgardNotifications('create', { - bindTo: button, - gravity: 'L', - body: this.options.editableOptions.localize('Choose type to add', this.options.editableOptions.language), - timeout: 0, - actions: this._getTypeActions(addOptions) - }); - return; - } - } else { - // Check the view templates for possible non-Thing type to use - var keys = _.keys(this.options.view.templates); - if (keys.length == 2) { - itemData['@type'] = keys[0]; - } - } - this.options.collection.add(itemData, addOptions); - } - }); -})(jQuery); // Create.js - On-site web editing interface // (c) 2011-2012 Henri Bergius, IKS Consortium // Create may be freely distributed under the MIT license. // For all details and documentation: // http://createjs.org/ (function (jQuery, undefined) { - // Run JavaScript in strict mode - /*global jQuery:false _:false window:false console:false */ - 'use strict'; - - // # Widget for adding items anywhere inside a collection - jQuery.widget('Midgard.midgardCollectionAddBetween', jQuery.Midgard.midgardCollectionAdd, { - _bindCollectionView: function (view) { - var widget = this; - view.on('add', function (itemView) { - //itemView.el.effect('slide'); - widget._makeEditable(itemView); - widget._refreshButtons(); - }); - view.on('remove', function () { - widget._refreshButtons(); - }); - }, - - _refreshButtons: function () { - var widget = this; - window.setTimeout(function () { - widget.disable(); - widget.enable(); - }, 1); - }, - - prepareButton: function (index) { - var widget = this; - var addButton = jQuery(_.template(this.options.templates.button, { - icon: 'plus', - label: '' - })).button(); - addButton.addClass('midgard-create-add'); - addButton.click(function () { - widget.addItem(addButton, { - at: index - }); - }); - return addButton; - }, - - enable: function () { - var widget = this; - var firstAddButton = widget.prepareButton(0); - jQuery(widget.options.view.el).prepend(firstAddButton); - widget.addButtons.push(firstAddButton); - jQuery.each(widget.options.view.entityViews, function (cid, view) { - var index = widget.options.collection.indexOf(view.model); - var addButton = widget.prepareButton(index + 1); - jQuery(view.el).append(addButton); - widget.addButtons.push(addButton); - }); - - this.checkCollectionConstraints(); - }, - - disable: function () { - var widget = this; - jQuery.each(widget.addButtons, function (idx, button) { - button.remove(); - }); - widget.addButtons = []; - } - }); -})(jQuery); -// Create.js - On-site web editing interface -// (c) 2011-2012 Henri Bergius, IKS Consortium -// Create may be freely distributed under the MIT license. -// For all details and documentation: -// http://createjs.org/ -(function (jQuery, undefined) { // Run JavaScript in strict mode /*global jQuery:false _:false window:false VIE:false */ 'use strict'; + Drupal.edit = Drupal.edit || {}; + // Define Create's EditableEntity widget. - jQuery.widget('Midgard.midgardEditable', { - options: { + Drupal.edit.Editable = function (options) { + this.options = jQuery.extend({ + element: null, propertyEditors: {}, collections: [], model: null, @@ -320,8 +58,12 @@ editables: [], // Now `propertyEditors`. editors: {}, // Now `propertyEditorWidgetsConfiguration`. widgets: {} // Now `propertyEditorW - }, + }, options); + this._create(); + }; + + jQuery.extend(Drupal.edit.Editable.prototype, { // Aids in consistently passing parameters to events and callbacks. _params: function(predicate, extended) { var entityParams = { @@ -348,6 +90,19 @@ }, _create: function () { + + + this.vie = this.options.vie; + + this.options.domService = 'edit'; + this.options.predicateSelector = '*'; //'.edit-field.edit-allowed'; + + // The Create.js PropertyEditor widget configuration is not hardcoded; it + // is generated by the server. + this.options.propertyEditorWidgetsConfiguration = drupalSettings.edit.editors; + + + // Backwards compatibility: // - this.options.propertyEditorWidgets used to be this.options.widgets // - this.options.propertyEditorWidgetsConfiguration used to be @@ -405,6 +160,17 @@ this.setState('candidate'); }, + invoke: function(method) { + var args = Array.prototype.slice.call(arguments, 1); + if (method in this && typeof this[method] === 'function') { + return this[method].apply(this, args); + } + }, + + get: function (property) { + return this.options[property]; + }, + // Method used for cycling between the different states of the Editable widget: // // * Inactive: editable is loaded but disabled @@ -463,7 +229,8 @@ this.enable(); } - this._trigger('statechange', null, this._params(predicate, { + // Huge hack. + this.options.stateChange.fire(this._params(predicate, { previous: previous, current: current, context: context @@ -488,7 +255,8 @@ editableEntity._enablePropertyEditor(jQuery(this)); }); - this._trigger('enable', null, this._params()); + // @jessebeach: Probably just a hook; not needed. + // this.options.enable.fire(this._params()); if (!this.vie.view || !this.vie.view.Collection) { return; @@ -541,7 +309,7 @@ }, this); this.options.collections = []; - this._trigger('disable', null, this._params()); + this.options.disable.fire(this._params()); }, _enablePropertyEditor: function (element) { @@ -572,18 +340,18 @@ silent: true }); - widget._trigger('changed', null, widget._params(predicate)); + widget.options.changed.fire(widget._params(predicate)); }, activating: function () { widget.setState('activating', predicate); }, activated: function () { widget.setState('active', predicate); - widget._trigger('activated', null, widget._params(predicate)); + jQuery(document).trigger('editStateChange', 'active', predicate); }, deactivated: function () { widget.setState('candidate', predicate); - widget._trigger('deactivated', null, widget._params(predicate)); + widget.options.deactivated.fire(widget._params(predicate)); } }); @@ -592,11 +360,6 @@ } var widgetType = propertyElement.data('createWidgetName'); this.options.propertyEditors[predicate] = propertyElement.data('Midgard-' + widgetType); - - // Deprecated. - this.options.editables.push(propertyElement); - - this._trigger('enableproperty', null, this._params(predicate)); }, // returns the name of the property editor widget to use for the given property @@ -826,291 +589,6 @@ }); })(jQuery); // Create.js - On-site web editing interface -// (c) 2012 Tobias Herrmann, IKS Consortium -// (c) 2011 Rene Kapusta, Evo42 -// Create may be freely distributed under the MIT license. -// For all details and documentation: -// http://createjs.org/ -(function (jQuery, undefined) { - // Run JavaScript in strict mode - /*global jQuery:false _:false document:false Aloha:false */ - 'use strict'; - - // # Aloha editing widget - // - // This widget allows editing textual contents using the - // [Aloha](http://aloha-editor.org) rich text editor. - // - // Due to licensing incompatibilities, Aloha Editor needs to be installed - // and configured separately. - jQuery.widget('Midgard.alohaWidget', jQuery.Midgard.editWidget, { - _initialize: function () {}, - enable: function () { - var options = this.options; - var editable; - var currentElement = Aloha.jQuery(options.element.get(0)).aloha(); - _.each(Aloha.editables, function (aloha) { - // Find the actual editable instance so we can hook to the events - // correctly - if (aloha.obj.get(0) === currentElement.get(0)) { - editable = aloha; - } - }); - if (!editable) { - return; - } - editable.vieEntity = options.entity; - - // Subscribe to activation and deactivation events - Aloha.bind('aloha-editable-activated', function (event, data) { - if (data.editable !== editable) { - return; - } - options.activated(); - }); - Aloha.bind('aloha-editable-deactivated', function (event, data) { - if (data.editable !== editable) { - return; - } - options.deactivated(); - }); - - Aloha.bind('aloha-smart-content-changed', function (event, data) { - if (data.editable !== editable) { - return; - } - if (!data.editable.isModified()) { - return true; - } - options.changed(data.editable.getContents()); - data.editable.setUnmodified(); - }); - this.options.disabled = false; - }, - disable: function () { - Aloha.jQuery(this.options.element.get(0)).mahalo(); - this.options.disabled = true; - } - }); -})(jQuery); -// Create.js - On-site web editing interface -// (c) 2012 Tobias Herrmann, IKS Consortium -// Create may be freely distributed under the MIT license. -// For all details and documentation: -(function (jQuery, undefined) { - // Run JavaScript in strict mode - /*global jQuery:false _:false document:false CKEDITOR:false */ - 'use strict'; - - // # CKEditor editing widget - // - // This widget allows editing textual content areas with the - // [CKEditor](http://ckeditor.com/) rich text editor. - jQuery.widget('Midgard.ckeditorWidget', jQuery.Midgard.editWidget, { - enable: function () { - this.element.attr('contentEditable', 'true'); - this.editor = CKEDITOR.inline(this.element.get(0)); - this.options.disabled = false; - - var widget = this; - this.editor.on('focus', function () { - widget.options.activated(); - }); - this.editor.on('blur', function () { - widget.options.activated(); - widget.options.changed(widget.editor.getData()); - }); - this.editor.on('key', function () { - widget.options.changed(widget.editor.getData()); - }); - this.editor.on('paste', function () { - widget.options.changed(widget.editor.getData()); - }); - this.editor.on('afterCommandExec', function () { - widget.options.changed(widget.editor.getData()); - }); - this.editor.on('configLoaded', function() { - jQuery.each(widget.options.editorOptions, function(optionName, option) { - widget.editor.config[optionName] = option; - }); - }); - }, - - disable: function () { - if (!this.editor) { - return; - } - this.element.attr('contentEditable', 'false'); - this.editor.destroy(); - this.editor = null; - }, - - _initialize: function () { - CKEDITOR.disableAutoInline = true; - } - }); -})(jQuery); -// Create.js - On-site web editing interface -// (c) 2012 Tobias Herrmann, IKS Consortium -// Create may be freely distributed under the MIT license. -// For all details and documentation: -// http://createjs.org/ -(function (jQuery, undefined) { - // Run JavaScript in strict mode - /*global jQuery:false _:false document:false */ - 'use strict'; - - // # Hallo editing widget - // - // This widget allows editing textual content areas with the - // [Hallo](http://hallojs.org) rich text editor. - jQuery.widget('Midgard.halloWidget', jQuery.Midgard.editWidget, { - options: { - editorOptions: {}, - disabled: true, - toolbarState: 'full', - vie: null, - entity: null - }, - enable: function () { - jQuery(this.element).hallo({ - editable: true - }); - this.options.disabled = false; - }, - - disable: function () { - jQuery(this.element).hallo({ - editable: false - }); - this.options.disabled = true; - }, - - _initialize: function () { - jQuery(this.element).hallo(this.getHalloOptions()); - var self = this; - jQuery(this.element).on('halloactivated', function (event, data) { - self.options.activated(); - }); - jQuery(this.element).on('hallodeactivated', function (event, data) { - self.options.deactivated(); - }); - jQuery(this.element).on('hallomodified', function (event, data) { - self.options.changed(data.content); - data.editable.setUnmodified(); - }); - - jQuery(document).on('midgardtoolbarstatechange', function(event, data) { - // Switch between Hallo configurations when toolbar state changes - if (data.display === self.options.toolbarState) { - return; - } - self.options.toolbarState = data.display; - if (!self.element.data('IKS-hallo')) { - // Hallo not yet instantiated - return; - } - var newOptions = self.getHalloOptions(); - self.element.hallo('changeToolbar', newOptions.parentElement, newOptions.toolbar, true); - }); - }, - - getHalloOptions: function() { - var defaults = { - plugins: { - halloformat: {}, - halloblock: {}, - hallolists: {}, - hallolink: {}, - halloimage: { - entity: this.options.entity - } - }, - buttonCssClass: 'create-ui-btn-small', - placeholder: '[' + this.options.property + ']' - }; - - if (typeof this.element.annotate === 'function' && this.options.vie.services.stanbol) { - // Enable Hallo Annotate plugin by default if user has annotate.js - // loaded and VIE has Stanbol enabled - defaults.plugins.halloannotate = { - vie: this.options.vie - }; - } - - if (this.options.toolbarState === 'full') { - // Use fixed toolbar in the Create tools area - defaults.parentElement = jQuery('.create-ui-toolbar-dynamictoolarea .create-ui-tool-freearea'); - defaults.toolbar = 'halloToolbarFixed'; - } else { - // Tools area minimized, use floating toolbar - defaults.parentElement = 'body'; - defaults.toolbar = 'halloToolbarContextual'; - } - return _.extend(defaults, this.options.editorOptions); - } - }); -})(jQuery); -// Create.js - On-site web editing interface -// (c) 2012 Henri Bergius, IKS Consortium -// Create may be freely distributed under the MIT license. -// For all details and documentation: -// http://createjs.org/ -(function (jQuery, undefined) { - // Run JavaScript in strict mode - /*global jQuery:false _:false document:false */ - 'use strict'; - - // # Redactor editing widget - // - // This widget allows editing textual content areas with the - // [Redactor](http://redactorjs.com/) rich text editor. - jQuery.widget('Midgard.redactorWidget', jQuery.Midgard.editWidget, { - editor: null, - - options: { - editorOptions: {}, - disabled: true - }, - - enable: function () { - jQuery(this.element).redactor(this.getRedactorOptions()); - this.options.disabled = false; - }, - - disable: function () { - jQuery(this.element).destroyEditor(); - this.options.disabled = true; - }, - - _initialize: function () { - var self = this; - jQuery(this.element).on('focus', function (event) { - self.options.activated(); - }); - /* - jQuery(this.element).on('blur', function (event) { - self.options.deactivated(); - }); - */ - }, - - getRedactorOptions: function () { - var self = this; - var overrides = { - keyupCallback: function (obj, event) { - self.options.changed(jQuery(self.element).getCode()); - }, - execCommandCallback: function (obj, command) { - self.options.changed(jQuery(self.element).getCode()); - } - }; - - return _.extend(self.options.editorOptions, overrides); - } - }); -})(jQuery); -// Create.js - On-site web editing interface // (c) 2011-2012 Henri Bergius, IKS Consortium // Create may be freely distributed under the MIT license. // For all details and documentation: diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index 5690c72..4b4acfe 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -260,6 +260,7 @@ function comment_menu() { 'access callback' => 'entity_page_access', 'access arguments' => array(1, 'update'), 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, ); $items['comment/%comment/approve'] = array( 'title' => 'Approve', @@ -277,6 +278,7 @@ function comment_menu() { 'access callback' => 'entity_page_access', 'access arguments' => array(1, 'delete'), 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_INLINE, 'file' => 'comment.admin.inc', 'weight' => 20, ); diff --git a/core/modules/comment/lib/Drupal/comment/CommentRenderController.php b/core/modules/comment/lib/Drupal/comment/CommentRenderController.php index dab8dc4..cb77687 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentRenderController.php +++ b/core/modules/comment/lib/Drupal/comment/CommentRenderController.php @@ -98,6 +98,8 @@ protected function alterBuild(array &$build, EntityInterface $comment, EntityDis if ($is_threaded && !empty($comment->divs_final)) { $build['#suffix'] = str_repeat('', $comment->divs_final); } + + $build['#contextual_links']['comment'] = array('comment', array($comment->id())); } } diff --git a/core/modules/edit/edit.module b/core/modules/edit/edit.module index a6ee046..0aea458 100644 --- a/core/modules/edit/edit.module +++ b/core/modules/edit/edit.module @@ -14,6 +14,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\edit\Form\EditFieldForm; use Drupal\Component\Utility\NestedArray; +use Drupal\custom_block\Plugin\block\block\CustomBlockBlock; /** * Implements hook_custom_theme(). @@ -153,7 +154,9 @@ function edit_preprocess_field(&$variables) { /** * Implements hook_preprocess_HOOK() for node.tpl.php. * - * @todo Move towards hook_preprocess_entity() once that's available. + * @todo Move towards hook_preprocess_entity() once that's available. Or, alter- + * natively: ensure #attributes on entities (added via hook_entity_view_alter) + * are actually printed, instead of ignored. */ function edit_preprocess_node(&$variables) { $node = $variables['elements']['#node']; @@ -161,6 +164,38 @@ function edit_preprocess_node(&$variables) { } /** + * Implements hook_preprocess_HOOK() for comment.tpl.php. + * + * @todo See edit_preprocess_node()'s todo. + */ +function edit_preprocess_comment(&$variables) { + $comment = $variables['elements']['#comment']; + $variables['attributes']['data-edit-entity'] = 'comment/' . $comment->id(); +} + +/** + * Implements hook_preprocess_HOOK() for taxonomy-term.tpl.php. + * + * @todo See edit_preprocess_node()'s todo. + */ +function edit_preprocess_taxonomy_term(&$variables) { + $term = $variables['elements']['#term']; + $variables['attributes']['data-edit-entity'] = 'taxonomy_term/' . $term->id(); +} + +/** + * Implements hook_preprocess_HOOK() for block.tpl.php. + * + * @todo See edit_preprocess_node()'s todo. + */ +function edit_preprocess_block(&$variables) { + if (isset($variables['elements']['content']['#custom_block'])) { + $custom_block = $variables['elements']['content']['#custom_block']; + $variables['attributes']['data-edit-entity'] = 'custom_block/' . $custom_block->id(); + } +} + +/** * Form constructor for the field editing form. * * @ingroup forms diff --git a/core/modules/edit/js/app.js b/core/modules/edit/js/app.js index 14d76a0..137cba1 100644 --- a/core/modules/edit/js/app.js +++ b/core/modules/edit/js/app.js @@ -7,385 +7,333 @@ "use strict"; Drupal.edit = Drupal.edit || {}; - Drupal.edit.EditAppView = Backbone.View.extend({ - vie: null, - domService: null, - - // Configuration for state handling. - states: [], - activeEditorStates: [], - singleEditorStates: [], - - // State. - $entityElements: null, - - /** - * Implements Backbone Views' initialize() function. - */ - initialize: function() { - _.bindAll(this, 'appStateChange', 'acceptEditorStateChange', 'editorStateChange'); - - // VIE instance for Edit. - this.vie = new VIE(); - // Use our custom DOM parsing service until RDFa is available. - this.vie.use(new this.vie.EditService()); - this.domService = this.vie.service('edit'); - - // Instantiate configuration for state handling. - this.states = [ - null, 'inactive', 'candidate', 'highlighted', - 'activating', 'active', 'changed', 'saving', 'saved', 'invalid' - ]; - this.activeEditorStates = ['activating', 'active']; - this.singleEditorStates = _.union(['highlighted'], this.activeEditorStates); - - this.$entityElements = $([]); - - // Use Create's Storage widget. - this.$el.createStorage({ - vie: this.vie, - editableNs: 'createeditable' - }); - - // When view/edit mode is toggled in the menu, update the editor widgets. - this.model.on('change:activeEntity', this.appStateChange); - }, - - /** - * Finds editable properties within a given context. - * - * Finds editable properties, registers them with the app, updates their - * state to match the current app state. - * - * @param $context - * A jQuery-wrapped context DOM element within which will be searched. - */ - findEditableProperties: function($context) { - var that = this; - var activeEntity = this.model.get('activeEntity'); - - this.domService.findSubjectElements($context).each(function() { - var $element = $(this); - - // Ignore editable properties for which we've already set up Create.js. - if (that.$entityElements.index($element) !== -1) { - return; - } - - $element - // Instantiate an EditableEntity widget. - .createEditable({ - vie: that.vie, - disabled: true, - state: 'inactive', - acceptStateChange: that.acceptEditorStateChange, - statechange: function(event, data) { - that.editorStateChange(data.previous, data.current, data.propertyEditor); - }, - decoratePropertyEditor: function(data) { - that.decorateEditor(data.propertyEditor); - } - }) - // This event is triggered just before Edit removes an EditableEntity - // widget, so that we can do proper clean-up. - .on('destroyedPropertyEditor.edit', function(event, editor) { - that.undecorateEditor(editor); - that.$entityElements = that.$entityElements.not($(this)); - }) - // Transition the new PropertyEditor into the default state. - .createEditable('setState', 'inactive'); - - // If the new PropertyEditor is for the entity that's currently being - // edited, then transition it to the 'candidate' state. - // (This happens when a field was modified and is re-rendered.) - var entityOfProperty = $element.createEditable('option', 'model'); - if (entityOfProperty.getSubjectUri() === activeEntity) { - $element.createEditable('setState', 'candidate'); - } - - // Add this new EditableEntity widget element to the list. - that.$entityElements = that.$entityElements.add($element); - }); - }, - - /** - * Sets the state of PropertyEditor widgets when edit mode begins or ends. - * - * Should be called whenever EditAppModel's "activeEntity" changes. - */ - appStateChange: function() { - // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133, https://github.com/bergie/create/issues/140) - // We're currently setting the state on EditableEntity widgets instead of - // PropertyEditor widgets, because of - // https://github.com/bergie/create/issues/133. - - var activeEntity = this.model.get('activeEntity'); - var $editableFieldsForEntity = $('[data-edit-id^="' + activeEntity + '/"]'); - - // First, change the status of all PropertyEditor widgets to 'inactive'. - this.$entityElements.each(function() { - $(this).createEditable('setState', 'inactive', null, {reason: 'stop'}); - }); - - // Then, change the status of PropertyEditor widgets of the currently - // active entity to 'candidate'. - $editableFieldsForEntity.each(function() { - $(this).createEditable('setState', 'candidate'); - }); - - // Manage the page's tab indexes. - }, - - /** - * Accepts or reject editor (PropertyEditor) state changes. - * - * This is what ensures that the app is in control of what happens. - * - * @param from - * The previous state. - * @param to - * The new state. - * @param predicate - * The predicate of the property for which the state change is happening. - * @param context - * The context that is trying to trigger the state change. - * @param callback - * The callback function that should receive the state acceptance result. - */ - acceptEditorStateChange: function(from, to, predicate, context, callback) { - var accept = true; + $.extend(Drupal.edit, { + EditAppView: Backbone.View.extend({ + vie: null, + domService: null, + + // Configuration for state handling. + states: [], + activeEditorStates: [], + singleEditorStates: [], + + // State. + $entityElements: null, + editables: [], + + /** + * Implements Backbone Views' initialize() function. + */ + initialize: function() { + _.bindAll(this, 'appStateChange', 'acceptEditorStateChange', 'editorStateChange'); + + // VIE instance for Edit. + this.vie = new VIE(); + // Use our custom DOM parsing service until RDFa is available. + this.vie.use(new this.vie.EditService()); + this.domService = this.vie.service('edit'); + + // Instantiate configuration for state handling. + this.states = [ + null, 'inactive', 'candidate', 'highlighted', + 'activating', 'active', 'changed', 'saving', 'saved', 'invalid' + ]; + this.activeEditorStates = ['activating', 'active']; + this.singleEditorStates = _.union(['highlighted'], this.activeEditorStates); + + this.$entityElements = $([]); + + // Use Create's Storage widget. + this.$el.createStorage({ + vie: this.vie, + editableNs: 'createeditable' + }); - // If the app is in view mode, then reject all state changes except for - // those to 'inactive'. - if (context && context.reason === 'stop') { - if (from === 'candidate' && to === 'inactive') { - accept = true; - } - } - // Handling of edit mode state changes is more granular. - else { - // In general, enforce the states sequence. Disallow going back from a - // "later" state to an "earlier" state, except in explicitly allowed - // cases. - if (_.indexOf(this.states, from) > _.indexOf(this.states, to)) { - accept = false; - // Allow: activating/active -> candidate. - // Necessary to stop editing a property. - if (_.indexOf(this.activeEditorStates, from) !== -1 && to === 'candidate') { - accept = true; + // When view/edit mode is toggled in the menu, update the editor widgets. + this.model.on('change:activeEntity', this.appStateChange); + }, + + /** + * Sets the state of PropertyEditor widgets when edit mode begins or ends. + * + * Should be called whenever EditAppModel's "activeEntity" changes. + */ + appStateChange: function() { + // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133, https://github.com/bergie/create/issues/140) + // We're currently setting the state on EditableEntity widgets instead of + // PropertyEditor widgets, because of + // https://github.com/bergie/create/issues/133. + + var editables = this.editables; + var activeEntity = this.model.get('activeEntity'); + + // First, change the status of all PropertyEditor widgets to 'inactive'. + for (var i = 0, il = editables.length; i < il; i++) { + var editable = editables[i]; + editable.invoke('setState', 'inactive', null, {reason: 'stop'}); + // Then, change the status of PropertyEditor widgets of the currently + // active entity to 'candidate'. + var entityOfProperty = editable.invoke('get', 'model'); + // If the new PropertyEditor is for the entity that's currently being + // edited, then transition it to the 'candidate' state. + // (This happens when a field was modified and is re-rendered.) + if (entityOfProperty.getSubjectUri() === activeEntity) { + editable.invoke('setState', 'candidate'); } - // Allow: changed/invalid -> candidate. - // Necessary to stop editing a property when it is changed or invalid. - else if ((from === 'changed' || from === 'invalid') && to === 'candidate') { - accept = true; - } - // Allow: highlighted -> candidate. - // Necessary to stop highlighting a property. - else if (from === 'highlighted' && to === 'candidate') { - accept = true; - } - // Allow: saved -> candidate. - // Necessary when successfully saved a property. - else if (from === 'saved' && to === 'candidate') { - accept = true; - } - // Allow: invalid -> saving. - // Necessary to be able to save a corrected, invalid property. - else if (from === 'invalid' && to === 'saving') { + } + }, + + /** + * Accepts or reject editor (PropertyEditor) state changes. + * + * This is what ensures that the app is in control of what happens. + * + * @param from + * The previous state. + * @param to + * The new state. + * @param predicate + * The predicate of the property for which the state change is happening. + * @param context + * The context that is trying to trigger the state change. + * @param callback + * The callback function that should receive the state acceptance result. + */ + acceptEditorStateChange: function(from, to, predicate, context, callback) { + var accept = true; + + // If the app is in view mode, then reject all state changes except for + // those to 'inactive'. + if (context && context.reason === 'stop') { + if (from === 'candidate' && to === 'inactive') { accept = true; } } - - // If it's not against the general principle, then here are more - // disallowed cases to check. - if (accept) { - // Ensure only one editor (field) at a time may be higlighted or active. - if (from === 'candidate' && _.indexOf(this.singleEditorStates, to) !== -1) { - if (this.model.get('highlightedEditor') || this.model.get('activeEditor')) { - accept = false; + // Handling of edit mode state changes is more granular. + else { + // In general, enforce the states sequence. Disallow going back from a + // "later" state to an "earlier" state, except in explicitly allowed + // cases. + if (_.indexOf(this.states, from) > _.indexOf(this.states, to)) { + accept = false; + // Allow: activating/active -> candidate. + // Necessary to stop editing a property. + if (_.indexOf(this.activeEditorStates, from) !== -1 && to === 'candidate') { + accept = true; } - } - // Reject going from activating/active to candidate because of a - // mouseleave. - else if (_.indexOf(this.activeEditorStates, from) !== -1 && to === 'candidate') { - if (context && context.reason === 'mouseleave') { - accept = false; + // Allow: changed/invalid -> candidate. + // Necessary to stop editing a property when it is changed or invalid. + else if ((from === 'changed' || from === 'invalid') && to === 'candidate') { + accept = true; + } + // Allow: highlighted -> candidate. + // Necessary to stop highlighting a property. + else if (from === 'highlighted' && to === 'candidate') { + accept = true; + } + // Allow: saved -> candidate. + // Necessary when successfully saved a property. + else if (from === 'saved' && to === 'candidate') { + accept = true; + } + // Allow: invalid -> saving. + // Necessary to be able to save a corrected, invalid property. + else if (from === 'invalid' && to === 'saving') { + accept = true; } } - // When attempting to stop editing a changed/invalid property, ask for - // confirmation. - else if ((from === 'changed' || from === 'invalid') && to === 'candidate') { - if (context && context.reason === 'mouseleave') { - accept = false; + + // If it's not against the general principle, then here are more + // disallowed cases to check. + if (accept) { + // Ensure only one editor (field) at a time may be higlighted or active. + if (from === 'candidate' && _.indexOf(this.singleEditorStates, to) !== -1) { + if (this.model.get('highlightedEditor') || this.model.get('activeEditor')) { + accept = false; + } + } + // Reject going from activating/active to candidate because of a + // mouseleave. + else if (_.indexOf(this.activeEditorStates, from) !== -1 && to === 'candidate') { + if (context && context.reason === 'mouseleave') { + accept = false; + } } - else { - // Check whether the transition has been confirmed? - if (context && context.confirmed) { - accept = true; + // When attempting to stop editing a changed/invalid property, ask for + // confirmation. + else if ((from === 'changed' || from === 'invalid') && to === 'candidate') { + if (context && context.reason === 'mouseleave') { + accept = false; } - // Confirm this transition. else { - // The callback will be called from the helper function. - this._confirmStopEditing(callback); - return; + // Check whether the transition has been confirmed? + if (context && context.confirmed) { + accept = true; + } + // Confirm this transition. + else { + // The callback will be called from the helper function. + this._confirmStopEditing(callback); + return; + } } } } } - } - - callback(accept); - }, - /** - * Asks the user to confirm whether he wants to stop editing via a modal. - * - * @param acceptCallback - * The callback function as passed to acceptEditorStateChange(). This - * callback function will be called with the user's choice. - * - * @see acceptEditorStateChange() - */ - _confirmStopEditing: function(acceptCallback) { - // Only instantiate if there isn't a modal instance visible yet. - if (!this.model.get('activeModal')) { - var that = this; - var modal = new Drupal.edit.views.ModalView({ - model: this.model, - message: Drupal.t('You have unsaved changes'), - buttons: [ - { action: 'discard', classes: 'gray-button', label: Drupal.t('Discard changes') }, - { action: 'save', type: 'submit', classes: 'blue-button', label: Drupal.t('Save') } - ], - callback: function(action) { - // The active modal has been removed. - that.model.set('activeModal', null); - if (action === 'discard') { - acceptCallback(true); - } - else { - acceptCallback(false); - var editor = that.model.get('activeEditor'); - editor.options.widget.setState('saving', editor.options.property); + callback(accept); + }, + + /** + * Asks the user to confirm whether he wants to stop editing via a modal. + * + * @param acceptCallback + * The callback function as passed to acceptEditorStateChange(). This + * callback function will be called with the user's choice. + * + * @see acceptEditorStateChange() + */ + _confirmStopEditing: function(acceptCallback) { + // Only instantiate if there isn't a modal instance visible yet. + if (!this.model.get('activeModal')) { + var that = this; + var modal = new Drupal.edit.views.ModalView({ + model: this.model, + message: Drupal.t('You have unsaved changes'), + buttons: [ + { action: 'discard', classes: 'gray-button', label: Drupal.t('Discard changes') }, + { action: 'save', type: 'submit', classes: 'blue-button', label: Drupal.t('Save') } + ], + callback: function(action) { + // The active modal has been removed. + that.model.set('activeModal', null); + if (action === 'discard') { + acceptCallback(true); + } + else { + acceptCallback(false); + var editor = that.model.get('activeEditor'); + editor.options.widget.setState('saving', editor.options.property); + } } - } - }); - this.model.set('activeModal', modal); - // The modal will set the activeModal property on the model when rendering - // to prevent multiple modals from being instantiated. - modal.render(); - } - else { - // Reject as there is still an open transition waiting for confirmation. - acceptCallback(false); - } - }, - - /** - * Reacts to editor (PropertyEditor) state changes; tracks global state. - * - * @param from - * The previous state. - * @param to - * The new state. - * @param editor - * The PropertyEditor widget object. - */ - editorStateChange: function(from, to, editor) { - // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133) - // Get rid of this once that issue is solved. - if (!editor) { - return; - } - else { - editor.stateChange(from, to); - } - - // Keep track of the highlighted editor in the global state. - if (_.indexOf(this.singleEditorStates, to) !== -1 && this.model.get('highlightedEditor') !== editor) { - this.model.set('highlightedEditor', editor); - } - else if (this.model.get('highlightedEditor') === editor && to === 'candidate') { - this.model.set('highlightedEditor', null); - } - - // Keep track of the active editor in the global state. - if (_.indexOf(this.activeEditorStates, to) !== -1 && this.model.get('activeEditor') !== editor) { - this.model.set('activeEditor', editor); - } - else if (this.model.get('activeEditor') === editor && to === 'candidate') { - // Discarded if it transitions from a changed state to 'candidate'. - if (from === 'changed' || from === 'invalid') { - // Retrieve the storage widget from DOM. - var createStorageWidget = this.$el.data('DrupalCreateStorage'); - // Revert changes in the model, this will trigger the direct editable - // content to be reset and redrawn. - createStorageWidget.revertChanges(editor.options.entity); + }); + this.model.set('activeModal', modal); + // The modal will set the activeModal property on the model when rendering + // to prevent multiple modals from being instantiated. + modal.render(); + } + else { + // Reject as there is still an open transition waiting for confirmation. + acceptCallback(false); + } + }, + + /** + * Reacts to editor (PropertyEditor) state changes; tracks global state. + * + * @param from + * The previous state. + * @param to + * The new state. + * @param editor + * The PropertyEditor widget object. + */ + editorStateChange: function(from, to, editor) { + // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133) + // Get rid of this once that issue is solved. + if (!editor) { + return; + } + else { + editor.stateChange(from, to); } - this.model.set('activeEditor', null); - } - // Propagate the state change to the decoration and toolbar views. - // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133) - // Uncomment this once that issue is solved. - // editor.decorationView.stateChange(from, to); - // editor.toolbarView.stateChange(from, to); - }, + // Keep track of the highlighted editor in the global state. + if (_.indexOf(this.singleEditorStates, to) !== -1 && this.model.get('highlightedEditor') !== editor) { + this.model.set('highlightedEditor', editor); + } + else if (this.model.get('highlightedEditor') === editor && to === 'candidate') { + this.model.set('highlightedEditor', null); + } - /** - * Decorates an editor (PropertyEditor). - * - * Upon the page load, all appropriate editors are initialized and decorated - * (i.e. even before anything of the editing UI becomes visible; even before - * edit mode is enabled). - * - * @param editor - * The PropertyEditor widget object. - */ - decorateEditor: function(editor) { - // Toolbars are rendered "on-demand" (highlighting or activating). - // They are a sibling element before the editor's DOM element. - editor.toolbarView = new Drupal.edit.views.ToolbarView({ - editor: editor, - $storageWidgetEl: this.$el - }); + // Keep track of the active editor in the global state. + if (_.indexOf(this.activeEditorStates, to) !== -1 && this.model.get('activeEditor') !== editor) { + this.model.set('activeEditor', editor); + } + else if (this.model.get('activeEditor') === editor && to === 'candidate') { + // Discarded if it transitions from a changed state to 'candidate'. + if (from === 'changed' || from === 'invalid') { + // Retrieve the storage widget from DOM. + var createStorageWidget = this.$el.data('DrupalCreateStorage'); + // Revert changes in the model, this will trigger the direct editable + // content to be reset and redrawn. + createStorageWidget.revertChanges(editor.options.entity); + } + this.model.set('activeEditor', null); + } - // Decorate the editor's DOM element depending on its state. - editor.decorationView = new Drupal.edit.views.PropertyEditorDecorationView({ - el: editor.element, - editor: editor, - toolbarId: editor.toolbarView.getId() - }); + // Propagate the state change to the decoration and toolbar views. + // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133) + // Uncomment this once that issue is solved. + // editor.decorationView.stateChange(from, to); + // editor.toolbarView.stateChange(from, to); + }, + + /** + * Decorates an editor (PropertyEditor). + * + * Upon the page load, all appropriate editors are initialized and decorated + * (i.e. even before anything of the editing UI becomes visible; even before + * edit mode is enabled). + * + * @param editor + * The PropertyEditor widget object. + */ + decorateEditor: function(editor) { + // Toolbars are rendered "on-demand" (highlighting or activating). + // They are a sibling element before the editor's DOM element. + editor.toolbarView = new Drupal.edit.views.ToolbarView({ + editor: editor, + $storageWidgetEl: this.$el + }); - // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133) - // Get rid of this once that issue is solved. - editor.options.widget.element.on('createeditablestatechange', function(event, data) { - editor.decorationView.stateChange(data.previous, data.current); - editor.toolbarView.stateChange(data.previous, data.current); - }); - }, + // Decorate the editor's DOM element depending on its state. + editor.decorationView = new Drupal.edit.views.PropertyEditorDecorationView({ + el: editor.element, + editor: editor, + toolbarId: editor.toolbarView.getId() + }); - /** - * Undecorates an editor (PropertyEditor). - * - * Whenever a property has been updated, the old HTML will be replaced by - * the new (re-rendered) HTML. The EditableEntity widget will be destroyed, - * as will be the PropertyEditor widget. This method ensures Edit's editor - * views also are removed properly. - * - * @param editor - * The PropertyEditor widget object. - */ - undecorateEditor: function(editor) { - editor.toolbarView.undelegateEvents(); - editor.toolbarView.remove(); - delete editor.toolbarView; - editor.decorationView.undelegateEvents(); - // Don't call .remove() on the decoration view, because that would remove - // a potentially rerendered field. - delete editor.decorationView; - } + // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133) + // Get rid of this once that issue is solved. + editor.options.widget.options.stateChange.add(function(data) { + editor.decorationView.stateChange(data.previous, data.current); + editor.toolbarView.stateChange(data.previous, data.current); + }); + }, + + /** + * Undecorates an editor (PropertyEditor). + * + * Whenever a property has been updated, the old HTML will be replaced by + * the new (re-rendered) HTML. The EditableEntity widget will be destroyed, + * as will be the PropertyEditor widget. This method ensures Edit's editor + * views also are removed properly. + * + * @param editor + * The PropertyEditor widget object. + */ + undecorateEditor: function(editor) { + editor.toolbarView.undelegateEvents(); + editor.toolbarView.remove(); + delete editor.toolbarView; + editor.decorationView.undelegateEvents(); + // Don't call .remove() on the decoration view, because that would remove + // a potentially rerendered field. + delete editor.decorationView; + } + }) }); })(jQuery, _, Backbone, Drupal, VIE); diff --git a/core/modules/edit/js/createjs/editable.js b/core/modules/edit/js/createjs/editable.js index 1316023..47564e3 100644 --- a/core/modules/edit/js/createjs/editable.js +++ b/core/modules/edit/js/createjs/editable.js @@ -6,19 +6,7 @@ "use strict"; - jQuery.widget('Drupal.createEditable', jQuery.Midgard.midgardEditable, { - _create: function() { - this.vie = this.options.vie; - - this.options.domService = 'edit'; - this.options.predicateSelector = '*'; //'.edit-field.edit-allowed'; - - // The Create.js PropertyEditor widget configuration is not hardcoded; it - // is generated by the server. - this.options.propertyEditorWidgetsConfiguration = drupalSettings.edit.editors; - - jQuery.Midgard.midgardEditable.prototype._create.call(this); - }, + jQuery.extend(Drupal.edit.Editable.prototype, { _propertyEditorName: function(data) { // Pick a PropertyEditor widget for a property depending on its metadata. diff --git a/core/modules/edit/js/edit.js b/core/modules/edit/js/edit.js index f924e7b..2e8918a 100644 --- a/core/modules/edit/js/edit.js +++ b/core/modules/edit/js/edit.js @@ -13,12 +13,20 @@ Drupal.edit.metadataCache = Drupal.edit.metadataCache || {}; * Attach toggling behavior and in-place editing. */ Drupal.behaviors.edit = { + + views: { + contextualLinks: [] + }, + + models: {}, + attach: function(context) { var $context = $(context); var $fields = $context.find('[data-edit-id]'); + var options = $.extend({}, this.defaults, (Drupal.edit || {})); // Initialize the Edit app. - $('body').once('edit-init', Drupal.edit.init); + $('body').once('edit-init', $.proxy(this.init, this, options)); var annotateField = function(field) { if (_.has(Drupal.edit.metadataCache, field.editID)) { @@ -53,7 +61,9 @@ Drupal.behaviors.edit = { }, []); // Make fields that could be annotated immediately available for editing. - Drupal.edit.app.findEditableProperties($context); + this.findEditableProperties($context); + + if (remainingFieldsToAnnotate.length) { $(window).ready(function() { @@ -77,37 +87,112 @@ Drupal.behaviors.edit = { }); }); } - } -}; - -Drupal.edit.init = function() { - // Instantiate EditAppView, which is the controller of it all. EditAppModel - // instance tracks global state (viewing/editing in-place). - var appModel = new Drupal.edit.models.EditAppModel(); - var app = new Drupal.edit.EditAppView({ - el: $('body'), - model: appModel - }); - - // Add "Quick edit" links to all contextual menus where editing the full - // node is possible. - // @todo Generalize this to work for all entities. - $('ul.contextual-links li.node-edit') - .before('
  • ') - .each(function() { - // Instantiate ContextualLinkView. - var $editContextualLink = $(this).prev(); - var editContextualLinkView = new Drupal.edit.views.ContextualLinkView({ - el: $editContextualLink.get(0), - model: appModel, - entity: $editContextualLink.parents('[data-edit-entity]').attr('data-edit-entity') + }, + + init: function (options) { + var that = this; + // Instantiate EditAppView, which is the controller of it all. EditAppModel + // instance tracks global state (viewing/editing in-place). + var appModel = new Drupal.edit.models.EditAppModel(); + var app = new Drupal.edit.EditAppView({ + el: $('body').get(0), + model: appModel }); - }); - // For now, we work with a singleton app, because for Drupal.behaviors to be - // able to discover new editable properties that get AJAXed in, it must know - // with which app instance they should be associated. - Drupal.edit.app = app; + // Add "Quick edit" links to all contextual menus where editing the full + // node is possible. + // @todo Generalize this to work for all entities. + $('ul.contextual-links') + .each(function() { + // Instantiate ContextualLinkView. + that.views.contextualLinks.push(new Drupal.edit.views.ContextualLinkView($.extend({ + el: this, + model: appModel, + entity: $(this).parents('[data-edit-entity]').attr('data-edit-entity') + }, options))); + }); + + // For now, we work with a singleton app, because for Drupal.behaviors to be + // able to discover new editable properties that get AJAXed in, it must know + // with which app instance they should be associated. + Drupal.edit.app = app; + }, + + defaults: { + strings: { + quickEdit: Drupal.t('Quick edit'), + stopQuickEdit: Drupal.t('Stop quick edit') + } + }, + + /** + * Finds editable properties within a given context. + * + * Finds editable properties, registers them with the app, updates their + * state to match the current app state. + * + * @param $context + * A jQuery-wrapped context DOM element within which will be searched. + */ + findEditableProperties: function($context) { + var that = this; + var activeEntity = this.model.get('activeEntity'); + + this.domService.findSubjectElements($context).each(function() { + var $element = $(this); + + // Ignore editable properties for which we've already set up Create.js. + if (that.$entityElements.index($element) !== -1) { + return; + } + + // Instantiate an Editable. + var editable = new Drupal.edit.Editable({ + element: $element, + vie: that.vie, + disabled: true, + state: 'inactive', + acceptStateChange: that.acceptEditorStateChange, + stateChange: jQuery.Callbacks(), + decoratePropertyEditor: function(data) { + that.decorateEditor(data.propertyEditor); + } + }); + + editable.options.stateChange.add(function(data) { + that.editorStateChange(data.previous, data.current, data.propertyEditor); + }); + + // Add this new Editable widget element to the list. + that.editables.push(editable); + // This event is triggered just before Edit removes an EditableEntity + // widget, so that we can do proper clean-up. + editable.options.element + .on('destroyedPropertyEditor.edit', function(event, editor) { + that.undecorateEditor(editor); + // that.$entityElements = that.$entityElements.not($(this)); + }); + // Transition the new PropertyEditor into the default state. + editable.invoke('setState', 'inactive'); + + // If the new PropertyEditor is for the entity that's currently being + // edited, then transition it to the 'candidate' state. + // (This happens when a field was modified and is re-rendered.) + var entityOfProperty = editable.invoke('get', 'model'); + if (entityOfProperty.getSubjectUri() === activeEntity) { + editable.invoke('setState', 'candidate'); + } + }); + } }; +$.extend(Drupal.edit, { + Ed itableModel: Backbone.Model.extend({ + defaults: { + state: 'inactive', + propertyEditor: null, + } + }) +}); + })(jQuery, _, Backbone, Drupal, drupalSettings); diff --git a/core/modules/edit/js/views/contextuallink-view.js b/core/modules/edit/js/views/contextuallink-view.js index efe8ddd..bf7ffe4 100644 --- a/core/modules/edit/js/views/contextuallink-view.js +++ b/core/modules/edit/js/views/contextuallink-view.js @@ -13,7 +13,7 @@ Drupal.edit.views.ContextualLinkView = Backbone.View.extend({ entity: null, events: { - 'click': 'onClick' + 'click .quick-edit a': 'onClick' }, /** @@ -25,6 +25,12 @@ Drupal.edit.views.ContextualLinkView = Backbone.View.extend({ */ initialize: function (options) { this.entity = options.entity; + this.strings = options.strings; + + // Build the DOM elements. + this.$el + .find('li.node-edit, li.taxonomy-edit, li.comment-edit, li.custom-block-edit') + .before('
  • '); // Initial render. this.render(); @@ -87,8 +93,7 @@ Drupal.edit.views.ContextualLinkView = Backbone.View.extend({ */ render: function () { var activeEntity = this.model.get('activeEntity'); - var string = (activeEntity !== this.entity) ? Drupal.t('Quick edit') : Drupal.t('Stop quick edit'); - this.$el.html('' + string + ''); + this.$el.find('.quick-edit a').text((activeEntity !== this.entity) ? this.strings.quickEdit : this.strings.stopQuickEdit); return this; }, diff --git a/core/modules/edit/js/views/propertyeditordecoration-view.js b/core/modules/edit/js/views/propertyeditordecoration-view.js index bee33d1..1af75cc 100644 --- a/core/modules/edit/js/views/propertyeditordecoration-view.js +++ b/core/modules/edit/js/views/propertyeditordecoration-view.js @@ -17,8 +17,8 @@ Drupal.edit.views.PropertyEditorDecorationView = Backbone.View.extend({ _widthAttributeIsEmpty: null, events: { - 'mouseenter.edit' : 'onMouseEnter', - 'mouseleave.edit' : 'onMouseLeave', + //'mouseenter.edit' : 'onMouseEnter', + //'mouseleave.edit' : 'onMouseLeave', 'click': 'onClick', 'tabIn.edit': 'onMouseEnter', 'tabOut.edit': 'onMouseLeave' @@ -37,6 +37,10 @@ Drupal.edit.views.PropertyEditorDecorationView = Backbone.View.extend({ * - toolbarId: the ID attribute of the toolbar as rendered in the DOM. */ initialize: function(options) { + + $(document) + .on('editStateChange', $.proxy(this.stateChange, this)); + this.editor = options.editor; this.toolbarId = options.toolbarId; diff --git a/core/modules/edit/js/views/toolbar-view.js b/core/modules/edit/js/views/toolbar-view.js index f4b2123..d2ded36 100644 --- a/core/modules/edit/js/views/toolbar-view.js +++ b/core/modules/edit/js/views/toolbar-view.js @@ -46,6 +46,10 @@ Drupal.edit.views.ToolbarView = Backbone.View.extend({ * initialized. */ initialize: function(options) { + + $(document) + .on('editStateChange', $.proxy(this.stateChange, this)); + this.editor = options.editor; this.$storageWidgetEl = options.$storageWidgetEl; @@ -58,6 +62,22 @@ Drupal.edit.views.ToolbarView = Backbone.View.extend({ // Generate a DOM-compatible ID for the toolbar DOM element. this._id = Drupal.edit.util.calcPropertyID(this.entity, this.predicate).replace(/\//g, '_'); + + + // Render toolbar. + this.setElement($(Drupal.theme('editToolbarContainer', { + id: this.getId() + }))); + + // Insert in DOM. + if (this.editor.element.css('display') === 'inline') { + this.$el.prependTo(this.editor.element.offsetParent()); + var pos = this.editor.element.position(); + this.$el.css('left', pos.left).css('top', pos.top); + } + else { + this.$el.insertBefore(this.editor.element); + } }, /** @@ -394,20 +414,7 @@ Drupal.edit.views.ToolbarView = Backbone.View.extend({ * toolbar is being inserted into the DOM, it will be inserted differently. */ render: function () { - // Render toolbar. - this.setElement($(Drupal.theme('editToolbarContainer', { - id: this.getId() - }))); - - // Insert in DOM. - if (this.editor.element.css('display') === 'inline') { - this.$el.prependTo(this.editor.element.offsetParent()); - var pos = this.editor.element.position(); - this.$el.css('left', pos.left).css('top', pos.top); - } - else { - this.$el.insertBefore(this.editor.element); - } + return this; }, /** diff --git a/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php b/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php index 9eac5bc..f7f0c06 100644 --- a/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php +++ b/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php @@ -41,10 +41,7 @@ public function access(Route $route, Request $request) { * Implements EntityFieldAccessCheckInterface::accessEditEntityField(). */ public function accessEditEntityField(EntityInterface $entity, $field_name) { - $entity_type = $entity->entityType(); - // @todo Generalize to all entity types once http://drupal.org/node/1862750 - // is done. - return ($entity_type == 'node' && node_access('update', $entity) && field_access('edit', $field_name, $entity_type, $entity)); + return $entity->access('update') && field_access('edit', $field_name, $entity->entityType(), $entity); } /** diff --git a/core/modules/edit/lib/Drupal/edit/EditController.php b/core/modules/edit/lib/Drupal/edit/EditController.php index a9b051b..2a0bb05 100644 --- a/core/modules/edit/lib/Drupal/edit/EditController.php +++ b/core/modules/edit/lib/Drupal/edit/EditController.php @@ -94,6 +94,8 @@ public function fieldForm(EntityInterface $entity, $field_name, $langcode, $view // The form submission took care of saving the updated entity. Return the // updated view of the field. $entity = entity_load($form_state['entity']->entityType(), $form_state['entity']->id(), TRUE); + // @todo Remove when http://drupal.org/node/1346214 is complete. + $entity = $entity->getBCEntity(); $output = field_view_field($entity, $field_name, $view_mode, $langcode); $response->addCommand(new FieldFormSavedCommand(drupal_render($output))); diff --git a/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php b/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php index 1959ae7..3a0da13 100644 --- a/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php +++ b/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php @@ -47,6 +47,9 @@ public function build(array $form, array &$form_state, EntityInterface $entity, * Initialize the form state and the entity before the first form build. */ protected function init(array &$form_state, EntityInterface $entity, $field_name) { + // @todo Remove when http://drupal.org/node/1346214 is complete. + $entity = $entity->getBCEntity(); + // @todo Rather than special-casing $node->revision, invoke prepareEdit() // once http://drupal.org/node/1863258 lands. if ($entity->entityType() == 'node') { diff --git a/core/modules/forum/forum.admin.inc b/core/modules/forum/forum.admin.inc index 2e014c4..cccb123 100644 --- a/core/modules/forum/forum.admin.inc +++ b/core/modules/forum/forum.admin.inc @@ -21,6 +21,10 @@ * @see forum_menu() */ function forum_form_main($type, Term $term = NULL) { + if ((isset($_POST['op']) && $_POST['op'] == t('Delete')) || !empty($_POST['confirm'])) { + return drupal_get_form('forum_confirm_delete', $term->id()); + } + if (!$term) { $term = entity_create('taxonomy_term', array('vid' => config('forum.settings')->get('vocabulary'))); } @@ -67,19 +71,10 @@ function forum_form_forum($form, &$form_state, Term $term) { $form['vid'] = array('#type' => 'hidden', '#value' => config('forum.settings')->get('vocabulary')); $form['actions'] = array('#type' => 'actions'); - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => t('Save'), - '#button_type' => 'primary', - '#submit' => array('forum_form_submit') - ); + $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save'), '#button_type' => 'primary'); if (!$term->isNew()) { - $form['actions']['delete'] = array( - '#type' => 'submit', - '#value' => t('Delete'), - '#submit' => array('forum_forum_delete'), - ); - $form['tid'] = array('#type' => 'value', '#value' => $term->id()); + $form['actions']['delete'] = array('#type' => 'submit', '#value' => t('Delete')); + $form['tid'] = array('#type' => 'hidden', '#value' => $term->id()); } $form['#theme'] = 'forum_form'; @@ -187,11 +182,7 @@ function forum_form_container($form, &$form_state, Term $term) { '#submit' => array('forum_form_submit'), ); if (!$term->isNew()) { - $form['actions']['delete'] = array( - '#type' => 'submit', - '#value' => t('Delete'), - '#submit' => array('forum_forum_delete'), - ); + $form['actions']['delete'] = array('#type' => 'submit', '#value' => t('Delete')); $form['tid'] = array('#type' => 'value', '#value' => $term->id()); } $form['#theme'] = 'forum_form'; @@ -235,16 +226,10 @@ function forum_overview($form, &$form_state) { if (in_array($term->id(), $config->get('containers'))) { $form[$key]['operations']['#links']['edit']['title'] = t('edit container'); $form[$key]['operations']['#links']['edit']['href'] = 'admin/structure/forum/edit/container/' . $term->id(); - // We don't want the redirect from the link so we can redirect the - // delete action. - unset($form[$key]['operations']['#links']['edit']['query']['destination']); } else { $form[$key]['operations']['#links']['edit']['title'] = t('edit forum'); $form[$key]['operations']['#links']['edit']['href'] = 'admin/structure/forum/edit/forum/' . $term->id(); - // We don't want the redirect from the link so we can redirect the - // delete action. - unset($form[$key]['operations']['#links']['edit']['query']['destination']); } } } diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermRenderController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermRenderController.php index 60728b4..4b6c60f 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/TermRenderController.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermRenderController.php @@ -54,6 +54,7 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco protected function alterBuild(array &$build, EntityInterface $entity, EntityDisplay $display, $view_mode, $langcode = NULL) { parent::alterBuild($build, $entity, $display, $view_mode, $langcode); $build['#attached']['css'][] = drupal_get_path('module', 'taxonomy') . '/taxonomy.css'; + $build['#contextual_links']['taxonomy'] = array('taxonomy/term', array($entity->id())); } } diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module index 591b1c9..681ff0e 100644 --- a/core/modules/taxonomy/taxonomy.module +++ b/core/modules/taxonomy/taxonomy.module @@ -299,6 +299,7 @@ function taxonomy_menu() { 'access callback' => 'entity_page_access', 'access arguments' => array(2, 'update'), 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, 'weight' => 10, 'file' => 'taxonomy.admin.inc', ); @@ -309,6 +310,7 @@ function taxonomy_menu() { 'access callback' => 'entity_page_access', 'access arguments' => array(2, 'delete'), 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_INLINE, 'weight' => 20, 'file' => 'taxonomy.admin.inc', ); diff --git a/core/modules/taxonomy/templates/taxonomy-term.tpl.php b/core/modules/taxonomy/templates/taxonomy-term.tpl.php index 6f6cac1..2ac86b6 100644 --- a/core/modules/taxonomy/templates/taxonomy-term.tpl.php +++ b/core/modules/taxonomy/templates/taxonomy-term.tpl.php @@ -37,8 +37,10 @@
    > +

    +
    -- 1.7.10.4