core/misc/create/create-editonly.js | 1651 --------------------
core/modules/contextual/contextual-old.js | 243 +++
core/modules/edit/css/edit.css | 4 +
core/modules/edit/edit.module | 18 +-
core/modules/edit/js/app.js | 391 -----
core/modules/edit/js/backbone.drupalform.js | 2 +-
core/modules/edit/js/createjs/editable.js | 30 -
.../editingWidgets/drupalcontenteditablewidget.js | 83 -
.../edit/js/createjs/editingWidgets/formwidget.js | 152 --
core/modules/edit/js/createjs/storage.js | 11 -
core/modules/edit/js/edit.js | 739 ++++++++-
core/modules/edit/js/editors/directEditor.js | 84 +
core/modules/edit/js/editors/editor.js | 81 +
core/modules/edit/js/editors/formEditor.js | 216 +++
core/modules/edit/js/models/edit-app-model.js | 21 -
core/modules/edit/js/storage.js | 518 ++++++
core/modules/edit/js/viejs/EditService.js | 289 ----
core/modules/edit/js/views/contextuallink-view.js | 64 +-
core/modules/edit/js/views/modal-view.js | 4 +-
.../edit/js/views/propertyeditordecoration-view.js | 27 +-
core/modules/edit/js/views/toolbar-view.js | 177 +--
core/modules/editor/js/editor.createjs.js | 42 +-
core/modules/system/system.module | 14 -
23 files changed, 1932 insertions(+), 2929 deletions(-)
diff --git a/core/misc/create/create-editonly.js b/core/misc/create/create-editonly.js
deleted file mode 100644
index aed84a4..0000000
--- a/core/misc/create/create-editonly.js
+++ /dev/null
@@ -1,1651 +0,0 @@
-// 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';
-
- // Define Create's EditableEntity widget.
- jQuery.widget('Midgard.midgardEditable', {
- options: {
- propertyEditors: {},
- collections: [],
- model: null,
- // the configuration (mapping and options) of property editor widgets
- propertyEditorWidgetsConfiguration: {
- hallo: {
- widget: 'halloWidget',
- options: {}
- }
- },
- // the available property editor widgets by data type
- propertyEditorWidgets: {
- 'default': 'hallo'
- },
- collectionWidgets: {
- 'default': 'midgardCollectionAdd'
- },
- toolbarState: 'full',
- vie: null,
- domService: 'rdfa',
- predicateSelector: '[property]',
- disabled: false,
- localize: function (id, language) {
- return window.midgardCreate.localize(id, language);
- },
- language: null,
- // Current state of the Editable
- state: null,
- // Callback function for validating changes between states. Receives the previous state, new state, possibly property, and a callback
- acceptStateChange: true,
- // Callback function for listening (and reacting) to state changes.
- stateChange: null,
- // Callback function for decorating the full editable. Will be called on instantiation
- decorateEditableEntity: null,
- // Callback function for decorating a single property editor widget. Will
- // be called on editing widget instantiation.
- decoratePropertyEditor: null,
-
- // Deprecated.
- editables: [], // Now `propertyEditors`.
- editors: {}, // Now `propertyEditorWidgetsConfiguration`.
- widgets: {} // Now `propertyEditorW
- },
-
- // Aids in consistently passing parameters to events and callbacks.
- _params: function(predicate, extended) {
- var entityParams = {
- entity: this.options.model,
- editableEntity: this,
- entityElement: this.element,
-
- // Deprecated.
- editable: this,
- element: this.element,
- instance: this.options.model
- };
- var propertyParams = (predicate) ? {
- predicate: predicate,
- propertyEditor: this.options.propertyEditors[predicate],
- propertyElement: this.options.propertyEditors[predicate].element,
-
- // Deprecated.
- property: predicate,
- element: this.options.propertyEditors[predicate].element
- } : {};
-
- return _.extend(entityParams, propertyParams, extended);
- },
-
- _create: function () {
- // Backwards compatibility:
- // - this.options.propertyEditorWidgets used to be this.options.widgets
- // - this.options.propertyEditorWidgetsConfiguration used to be
- // this.options.editors
- if (this.options.widgets) {
- this.options.propertyEditorWidgets = _.extend(this.options.propertyEditorWidgets, this.options.widgets);
- }
- if (this.options.editors) {
- this.options.propertyEditorWidgetsConfiguration = _.extend(this.options.propertyEditorWidgetsConfiguration, this.options.editors);
- }
-
- this.vie = this.options.vie;
- this.domService = this.vie.service(this.options.domService);
- if (!this.options.model) {
- var widget = this;
- this.vie.load({
- element: this.element
- }).from(this.options.domService).execute().done(function (entities) {
- widget.options.model = entities[0];
- });
- }
- if (_.isFunction(this.options.decorateEditableEntity)) {
- this.options.decorateEditableEntity(this._params());
- }
- },
-
- _init: function () {
- // Backwards compatibility:
- // - this.options.propertyEditorWidgets used to be this.options.widgets
- // - this.options.propertyEditorWidgetsConfiguration used to be
- // this.options.editors
- if (this.options.widgets) {
- this.options.propertyEditorWidgets = _.extend(this.options.propertyEditorWidgets, this.options.widgets);
- }
- if (this.options.editors) {
- this.options.propertyEditorWidgetsConfiguration = _.extend(this.options.propertyEditorWidgetsConfiguration, this.options.editors);
- }
-
- // Old way of setting the widget inactive
- if (this.options.disabled === true) {
- this.setState('inactive');
- return;
- }
-
- if (this.options.disabled === false && this.options.state === 'inactive') {
- this.setState('candidate');
- return;
- }
- this.options.disabled = false;
-
- if (this.options.state) {
- this.setState(this.options.state);
- return;
- }
- this.setState('candidate');
- },
-
- // Method used for cycling between the different states of the Editable widget:
- //
- // * Inactive: editable is loaded but disabled
- // * Candidate: editable is enabled but not activated
- // * Highlight: user is hovering over the editable (not set by Editable widget directly)
- // * Activating: an editor widget is being activated for user to edit with it (skipped for editors that activate instantly)
- // * Active: user is actually editing something inside the editable
- // * Changed: user has made changes to the editable
- // * Invalid: the contents of the editable have validation errors
- //
- // In situations where state changes are triggered for a particular property editor, the `predicate`
- // argument will provide the name of that property.
- //
- // State changes may carry optional context information in a JavaScript object. The payload of these context objects is not
- // standardized, and is meant to be set and used by the application controller
- //
- // The callback parameter is optional and will be invoked after a state change has been accepted (after the 'statechange'
- // event) or rejected.
- setState: function (state, predicate, context, callback) {
- var previous = this.options.state;
- var current = state;
- if (current === previous) {
- return;
- }
-
- if (this.options.acceptStateChange === undefined || !_.isFunction(this.options.acceptStateChange)) {
- // Skip state transition validation
- this._doSetState(previous, current, predicate, context);
- if (_.isFunction(callback)) {
- callback(true);
- }
- return;
- }
-
- var widget = this;
- this.options.acceptStateChange(previous, current, predicate, context, function (accepted) {
- if (accepted) {
- widget._doSetState(previous, current, predicate, context);
- }
- if (_.isFunction(callback)) {
- callback(accepted);
- }
- return;
- });
- },
-
- getState: function () {
- return this.options.state;
- },
-
- _doSetState: function (previous, current, predicate, context) {
- this.options.state = current;
- if (current === 'inactive') {
- this.disable();
- } else if ((previous === null || previous === 'inactive') && current !== 'inactive') {
- this.enable();
- }
-
- this._trigger('statechange', null, this._params(predicate, {
- previous: previous,
- current: current,
- context: context
- }));
- },
-
- findEditablePredicateElements: function (callback) {
- this.domService.findPredicateElements(this.options.model.id, jQuery(this.options.predicateSelector, this.element), false).each(callback);
- },
-
- getElementPredicate: function (element) {
- return this.domService.getElementPredicate(element);
- },
-
- enable: function () {
- var editableEntity = this;
- if (!this.options.model) {
- return;
- }
-
- this.findEditablePredicateElements(function () {
- editableEntity._enablePropertyEditor(jQuery(this));
- });
-
- this._trigger('enable', null, this._params());
-
- if (!this.vie.view || !this.vie.view.Collection) {
- return;
- }
-
- _.each(this.domService.views, function (view) {
- if (view instanceof this.vie.view.Collection && this.options.model === view.owner) {
- var predicate = view.collection.predicate;
- var editableOptions = _.clone(this.options);
- editableOptions.state = null;
- var collection = this.enableCollection({
- model: this.options.model,
- collection: view.collection,
- property: predicate,
- definition: this.getAttributeDefinition(predicate),
- view: view,
- element: view.el,
- vie: editableEntity.vie,
- editableOptions: editableOptions
- });
- editableEntity.options.collections.push(collection);
- }
- }, this);
- },
-
- disable: function () {
- _.each(this.options.propertyEditors, function (editable) {
- this.disablePropertyEditor({
- widget: this,
- editable: editable,
- entity: this.options.model,
- element: editable.element
- });
- }, this);
- this.options.propertyEditors = {};
-
- // Deprecated.
- this.options.editables = [];
-
- _.each(this.options.collections, function (collectionWidget) {
- var editableOptions = _.clone(this.options);
- editableOptions.state = 'inactive';
- this.disableCollection({
- widget: this,
- model: this.options.model,
- element: collectionWidget,
- vie: this.vie,
- editableOptions: editableOptions
- });
- }, this);
- this.options.collections = [];
-
- this._trigger('disable', null, this._params());
- },
-
- _enablePropertyEditor: function (element) {
- var widget = this;
- var predicate = this.getElementPredicate(element);
- if (!predicate) {
- return true;
- }
- if (this.options.model.get(predicate) instanceof Array) {
- // For now we don't deal with multivalued properties in the editable
- return true;
- }
-
- var propertyElement = this.enablePropertyEditor({
- widget: this,
- element: element,
- entity: this.options.model,
- property: predicate,
- vie: this.vie,
- decorate: this.options.decoratePropertyEditor,
- decorateParams: _.bind(this._params, this),
- changed: function (content) {
- widget.setState('changed', predicate);
-
- var changedProperties = {};
- changedProperties[predicate] = content;
- widget.options.model.set(changedProperties, {
- silent: true
- });
-
- widget._trigger('changed', null, widget._params(predicate));
- },
- activating: function () {
- widget.setState('activating', predicate);
- },
- activated: function () {
- widget.setState('active', predicate);
- widget._trigger('activated', null, widget._params(predicate));
- },
- deactivated: function () {
- widget.setState('candidate', predicate);
- widget._trigger('deactivated', null, widget._params(predicate));
- }
- });
-
- if (!propertyElement) {
- return;
- }
- 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
- _propertyEditorName: function (data) {
- if (this.options.propertyEditorWidgets[data.property] !== undefined) {
- // Property editor widget configuration set for specific RDF predicate
- return this.options.propertyEditorWidgets[data.property];
- }
-
- // Load the property editor widget configuration for the data type
- var propertyType = 'default';
- var attributeDefinition = this.getAttributeDefinition(data.property);
- if (attributeDefinition) {
- propertyType = attributeDefinition.range[0];
- }
- if (this.options.propertyEditorWidgets[propertyType] !== undefined) {
- return this.options.propertyEditorWidgets[propertyType];
- }
- return this.options.propertyEditorWidgets['default'];
- },
-
- _propertyEditorWidget: function (editor) {
- return this.options.propertyEditorWidgetsConfiguration[editor].widget;
- },
-
- _propertyEditorOptions: function (editor) {
- return this.options.propertyEditorWidgetsConfiguration[editor].options;
- },
-
- getAttributeDefinition: function (property) {
- var type = this.options.model.get('@type');
- if (!type) {
- return;
- }
- if (!type.attributes) {
- return;
- }
- return type.attributes.get(property);
- },
-
- // Deprecated.
- enableEditor: function (data) {
- return this.enablePropertyEditor(data);
- },
-
- enablePropertyEditor: function (data) {
- var editorName = this._propertyEditorName(data);
- if (editorName === null) {
- return;
- }
-
- var editorWidget = this._propertyEditorWidget(editorName);
-
- data.editorOptions = this._propertyEditorOptions(editorName);
- data.toolbarState = this.options.toolbarState;
- data.disabled = false;
- // Pass metadata that could be useful for some implementations.
- data.editorName = editorName;
- data.editorWidget = editorWidget;
-
- if (typeof jQuery(data.element)[editorWidget] !== 'function') {
- throw new Error(editorWidget + ' widget is not available');
- }
-
- jQuery(data.element)[editorWidget](data);
- jQuery(data.element).data('createWidgetName', editorWidget);
- return jQuery(data.element);
- },
-
- // Deprecated.
- disableEditor: function (data) {
- return this.disablePropertyEditor(data);
- },
-
- disablePropertyEditor: function (data) {
- data.element[data.editable.widgetName]({
- disabled: true
- });
- jQuery(data.element).removeClass('ui-state-disabled');
-
- if (data.element.is(':focus')) {
- data.element.blur();
- }
- },
-
- collectionWidgetName: function (data) {
- if (this.options.collectionWidgets[data.property] !== undefined) {
- // Widget configuration set for specific RDF predicate
- return this.options.collectionWidgets[data.property];
- }
-
- var propertyType = 'default';
- var attributeDefinition = this.getAttributeDefinition(data.property);
- if (attributeDefinition) {
- propertyType = attributeDefinition.range[0];
- }
- if (this.options.collectionWidgets[propertyType] !== undefined) {
- return this.options.collectionWidgets[propertyType];
- }
- return this.options.collectionWidgets['default'];
- },
-
- enableCollection: function (data) {
- var widgetName = this.collectionWidgetName(data);
- if (widgetName === null) {
- return;
- }
- data.disabled = false;
- if (typeof jQuery(data.element)[widgetName] !== 'function') {
- throw new Error(widgetName + ' widget is not available');
- }
- jQuery(data.element)[widgetName](data);
- jQuery(data.element).data('createCollectionWidgetName', widgetName);
- return jQuery(data.element);
- },
-
- disableCollection: function (data) {
- var widgetName = jQuery(data.element).data('createCollectionWidgetName');
- if (widgetName === null) {
- return;
- }
- data.disabled = true;
- if (widgetName) {
- // only if there has been an editing widget registered
- jQuery(data.element)[widgetName](data);
- jQuery(data.element).removeClass('ui-state-disabled');
- }
- }
- });
-})(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';
-
- // # Base property editor widget
- //
- // This property editor widget provides a very simplistic `contentEditable`
- // property editor that can be used as standalone, but should more usually be
- // used as the base class for other property editor widgets.
- // This property editor widget is only useful for textual properties!
- //
- // Subclassing this base property editor widget is easy:
- //
- // jQuery.widget('Namespace.MyWidget', jQuery.Create.editWidget, {
- // // override any properties
- // });
- jQuery.widget('Midgard.editWidget', {
- options: {
- disabled: false,
- vie: null
- },
- // override to enable the widget
- enable: function () {
- this.element.attr('contenteditable', 'true');
- },
- // override to disable the widget
- disable: function (disable) {
- this.element.attr('contenteditable', 'false');
- },
- // called by the jQuery UI plugin factory when creating the property editor
- // widget instance
- _create: function () {
- this._registerWidget();
- this._initialize();
-
- if (_.isFunction(this.options.decorate) && _.isFunction(this.options.decorateParams)) {
- // TRICKY: we can't use this.options.decorateParams()'s 'propertyName'
- // parameter just yet, because it will only be available after this
- // object has been created, but we're currently in the constructor!
- // Hence we have to duplicate part of its logic here.
- this.options.decorate(this.options.decorateParams(null, {
- propertyName: this.options.property,
- propertyEditor: this,
- propertyElement: this.element,
- // Deprecated.
- editor: this,
- predicate: this.options.property,
- element: this.element
- }));
- }
- },
- // called every time the property editor widget is called
- _init: function () {
- if (this.options.disabled) {
- this.disable();
- return;
- }
- this.enable();
- },
- // override this function to initialize the property editor widget functions
- _initialize: function () {
- var self = this;
- this.element.on('focus', function () {
- if (self.options.disabled) {
- return;
- }
- self.options.activated();
- });
- this.element.on('blur', function () {
- if (self.options.disabled) {
- return;
- }
- self.options.deactivated();
- });
- var before = this.element.html();
- this.element.on('keyup paste', function (event) {
- if (self.options.disabled) {
- return;
- }
- var current = jQuery(this).html();
- if (before !== current) {
- before = current;
- self.options.changed(current);
- }
- });
- },
- // used to register the property editor widget name with the DOM element
- _registerWidget: function () {
- this.element.data("createWidgetName", this.widgetName);
- }
- });
-})(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:
-// http://createjs.org/
-(function (jQuery, undefined) {
- // Run JavaScript in strict mode
- /*global jQuery:false _:false window:false */
- 'use strict';
-
- jQuery.widget('Midgard.midgardStorage', {
- saveEnabled: true,
- options: {
- // Whether to use localstorage
- localStorage: false,
- removeLocalstorageOnIgnore: true,
- // VIE instance to use for storage handling
- vie: null,
- // URL callback for Backbone.sync
- url: '',
- // Whether to enable automatic saving
- autoSave: false,
- // How often to autosave in milliseconds
- autoSaveInterval: 5000,
- // Whether to save entities that are referenced by entities
- // we're saving to the server.
- saveReferencedNew: false,
- saveReferencedChanged: false,
- // Namespace used for events from midgardEditable-derived widget
- editableNs: 'midgardeditable',
- // CSS selector for the Edit button, leave to null to not bind
- // notifications to any element
- editSelector: '#midgardcreate-edit a',
- localize: function (id, language) {
- return window.midgardCreate.localize(id, language);
- },
- language: null
- },
-
- _create: function () {
- var widget = this;
- this.changedModels = [];
-
- if (window.localStorage) {
- this.options.localStorage = true;
- }
-
- this.vie = this.options.vie;
-
- this.vie.entities.on('add', function (model) {
- // Add the back-end URL used by Backbone.sync
- model.url = widget.options.url;
- model.toJSON = model.toJSONLD;
- });
-
- widget._bindEditables();
- if (widget.options.autoSave) {
- widget._autoSave();
- }
- },
-
- _autoSave: function () {
- var widget = this;
- widget.saveEnabled = true;
-
- var doAutoSave = function () {
- if (!widget.saveEnabled) {
- return;
- }
-
- if (widget.changedModels.length === 0) {
- return;
- }
-
- widget.saveRemoteAll({
- // We make autosaves silent so that potential changes from server
- // don't disrupt user while writing.
- silent: true
- });
- };
-
- var timeout = window.setInterval(doAutoSave, widget.options.autoSaveInterval);
-
- this.element.on('startPreventSave', function () {
- if (timeout) {
- window.clearInterval(timeout);
- timeout = null;
- }
- widget.disableAutoSave();
- });
- this.element.on('stopPreventSave', function () {
- if (!timeout) {
- timeout = window.setInterval(doAutoSave, widget.options.autoSaveInterval);
- }
- widget.enableAutoSave();
- });
-
- },
-
- enableAutoSave: function () {
- this.saveEnabled = true;
- },
-
- disableAutoSave: function () {
- this.saveEnabled = false;
- },
-
- _bindEditables: function () {
- var widget = this;
- this.restorables = [];
- var restorer;
-
- widget.element.on(widget.options.editableNs + 'changed', function (event, options) {
- if (_.indexOf(widget.changedModels, options.instance) === -1) {
- widget.changedModels.push(options.instance);
- }
- widget._saveLocal(options.instance);
- });
-
- widget.element.on(widget.options.editableNs + 'disable', function (event, options) {
- widget.revertChanges(options.instance);
- });
-
- widget.element.on(widget.options.editableNs + 'enable', function (event, options) {
- if (!options.instance._originalAttributes) {
- options.instance._originalAttributes = _.clone(options.instance.attributes);
- }
-
- if (!options.instance.isNew() && widget._checkLocal(options.instance)) {
- // We have locally-stored modifications, user needs to be asked
- widget.restorables.push(options.instance);
- }
-
- /*_.each(options.instance.attributes, function (attributeValue, property) {
- if (attributeValue instanceof widget.vie.Collection) {
- widget._readLocalReferences(options.instance, property, attributeValue);
- }
- });*/
- });
-
- widget.element.on('midgardcreatestatechange', function (event, options) {
- if (options.state === 'browse' || widget.restorables.length === 0) {
- widget.restorables = [];
- if (restorer) {
- restorer.close();
- }
- return;
- }
- restorer = widget.checkRestore();
- });
-
- widget.element.on('midgardstorageloaded', function (event, options) {
- if (_.indexOf(widget.changedModels, options.instance) === -1) {
- widget.changedModels.push(options.instance);
- }
- });
- },
-
- checkRestore: function () {
- var widget = this;
- if (widget.restorables.length === 0) {
- return;
- }
-
- var message;
- var restorer;
- if (widget.restorables.length === 1) {
- message = _.template(widget.options.localize('localModification', widget.options.language), {
- label: widget.restorables[0].getSubjectUri()
- });
- } else {
- message = _.template(widget.options.localize('localModifications', widget.options.language), {
- number: widget.restorables.length
- });
- }
-
- var doRestore = function (event, notification) {
- widget.restoreLocalAll();
- restorer.close();
- };
-
- var doIgnore = function (event, notification) {
- widget.ignoreLocal();
- restorer.close();
- };
-
- restorer = jQuery('body').midgardNotifications('create', {
- bindTo: widget.options.editSelector,
- gravity: 'TR',
- body: message,
- timeout: 0,
- actions: [
- {
- name: 'restore',
- label: widget.options.localize('Restore', widget.options.language),
- cb: doRestore,
- className: 'create-ui-btn'
- },
- {
- name: 'ignore',
- label: widget.options.localize('Ignore', widget.options.language),
- cb: doIgnore,
- className: 'create-ui-btn'
- }
- ],
- callbacks: {
- beforeShow: function () {
- if (!window.Mousetrap) {
- return;
- }
- window.Mousetrap.bind(['command+shift+r', 'ctrl+shift+r'], function (event) {
- event.preventDefault();
- doRestore();
- });
- window.Mousetrap.bind(['command+shift+i', 'ctrl+shift+i'], function (event) {
- event.preventDefault();
- doIgnore();
- });
- },
- afterClose: function () {
- if (!window.Mousetrap) {
- return;
- }
- window.Mousetrap.unbind(['command+shift+r', 'ctrl+shift+r']);
- window.Mousetrap.unbind(['command+shift+i', 'ctrl+shift+i']);
- }
- }
- });
- return restorer;
- },
-
- restoreLocalAll: function () {
- _.each(this.restorables, function (instance) {
- this.readLocal(instance);
- }, this);
- this.restorables = [];
- },
-
- ignoreLocal: function () {
- if (this.options.removeLocalstorageOnIgnore) {
- _.each(this.restorables, function (instance) {
- this._removeLocal(instance);
- }, this);
- }
- this.restorables = [];
- },
-
- saveReferences: function (model) {
- _.each(model.attributes, function (value, property) {
- if (!value || !value.isCollection) {
- return;
- }
-
- value.each(function (referencedModel) {
- if (this.changedModels.indexOf(referencedModel) !== -1) {
- // The referenced model is already in the save queue
- return;
- }
-
- if (referencedModel.isNew() && this.options.saveReferencedNew) {
- return referencedModel.save();
- }
-
- if (referencedModel.hasChanged() && this.options.saveReferencedChanged) {
- return referencedModel.save();
- }
- }, this);
- }, this);
- },
-
- saveRemote: function (model, options) {
- // Optionally handle entities referenced in this model first
- this.saveReferences(model);
-
- this._trigger('saveentity', null, {
- entity: model,
- options: options
- });
-
- var widget = this;
- model.save(null, _.extend({}, options, {
- success: function (m, response) {
- // From now on we're going with the values we have on server
- model._originalAttributes = _.clone(model.attributes);
- widget._removeLocal(model);
- window.setTimeout(function () {
- // Remove the model from the list of changed models after saving
- widget.changedModels.splice(widget.changedModels.indexOf(model), 1);
- }, 0);
- if (_.isFunction(options.success)) {
- options.success(m, response);
- }
- widget._trigger('savedentity', null, {
- entity: model,
- options: options
- });
- },
- error: function (m, response) {
- if (_.isFunction(options.error)) {
- options.error(m, response);
- }
- }
- }));
- },
-
- saveRemoteAll: function (options) {
- var widget = this;
- if (widget.changedModels.length === 0) {
- return;
- }
-
- widget._trigger('save', null, {
- entities: widget.changedModels,
- options: options,
- // Deprecated
- models: widget.changedModels
- });
-
- var notification_msg;
- var needed = widget.changedModels.length;
- if (needed > 1) {
- notification_msg = _.template(widget.options.localize('saveSuccessMultiple', widget.options.language), {
- number: needed
- });
- } else {
- notification_msg = _.template(widget.options.localize('saveSuccess', widget.options.language), {
- label: widget.changedModels[0].getSubjectUri()
- });
- }
-
- widget.disableAutoSave();
- _.each(widget.changedModels, function (model) {
- this.saveRemote(model, {
- success: function (m, response) {
- needed--;
- if (needed <= 0) {
- // All models were happily saved
- widget._trigger('saved', null, {
- options: options
- });
- if (options && _.isFunction(options.success)) {
- options.success(m, response);
- }
- jQuery('body').midgardNotifications('create', {
- body: notification_msg
- });
- widget.enableAutoSave();
- }
- },
- error: function (m, err) {
- if (options && _.isFunction(options.error)) {
- options.error(m, err);
- }
- jQuery('body').midgardNotifications('create', {
- body: _.template(widget.options.localize('saveError', widget.options.language), {
- error: err.responseText || ''
- }),
- timeout: 0
- });
-
- widget._trigger('error', null, {
- instance: model
- });
- }
- });
- }, this);
- },
-
- _saveLocal: function (model) {
- if (!this.options.localStorage) {
- return;
- }
-
- if (model.isNew()) {
- // Anonymous object, save as refs instead
- if (!model.primaryCollection) {
- return;
- }
- return this._saveLocalReferences(model.primaryCollection.subject, model.primaryCollection.predicate, model);
- }
- window.localStorage.setItem(model.getSubjectUri(), JSON.stringify(model.toJSONLD()));
- },
-
- _getReferenceId: function (model, property) {
- return model.id + ':' + property;
- },
-
- _saveLocalReferences: function (subject, predicate, model) {
- if (!this.options.localStorage) {
- return;
- }
-
- if (!subject || !predicate) {
- return;
- }
-
- var widget = this;
- var identifier = subject + ':' + predicate;
- var json = model.toJSONLD();
- if (window.localStorage.getItem(identifier)) {
- var referenceList = JSON.parse(window.localStorage.getItem(identifier));
- var index = _.pluck(referenceList, '@').indexOf(json['@']);
- if (index !== -1) {
- referenceList[index] = json;
- } else {
- referenceList.push(json);
- }
- window.localStorage.setItem(identifier, JSON.stringify(referenceList));
- return;
- }
- window.localStorage.setItem(identifier, JSON.stringify([json]));
- },
-
- _checkLocal: function (model) {
- if (!this.options.localStorage) {
- return false;
- }
-
- var local = window.localStorage.getItem(model.getSubjectUri());
- if (!local) {
- return false;
- }
-
- return true;
- },
-
- hasLocal: function (model) {
- if (!this.options.localStorage) {
- return false;
- }
-
- if (!window.localStorage.getItem(model.getSubjectUri())) {
- return false;
- }
- return true;
- },
-
- readLocal: function (model) {
- if (!this.options.localStorage) {
- return;
- }
-
- var local = window.localStorage.getItem(model.getSubjectUri());
- if (!local) {
- return;
- }
- if (!model._originalAttributes) {
- model._originalAttributes = _.clone(model.attributes);
- }
- var parsed = JSON.parse(local);
- var entity = this.vie.entities.addOrUpdate(parsed, {
- overrideAttributes: true
- });
-
- this._trigger('loaded', null, {
- instance: entity
- });
- },
-
- _readLocalReferences: function (model, property, collection) {
- if (!this.options.localStorage) {
- return;
- }
-
- var identifier = this._getReferenceId(model, property);
- var local = window.localStorage.getItem(identifier);
- if (!local) {
- return;
- }
- collection.add(JSON.parse(local));
- },
-
- revertChanges: function (model) {
- var widget = this;
-
- // Remove unsaved collection members
- if (!model) { return; }
- _.each(model.attributes, function (attributeValue, property) {
- if (attributeValue instanceof widget.vie.Collection) {
- var removables = [];
- attributeValue.forEach(function (model) {
- if (model.isNew()) {
- removables.push(model);
- }
- });
- attributeValue.remove(removables);
- }
- });
-
- // Restore original object properties
- if (!model.changedAttributes()) {
- if (model._originalAttributes) {
- model.set(model._originalAttributes);
- }
- return;
- }
-
- model.set(model.previousAttributes());
- },
-
- _removeLocal: function (model) {
- if (!this.options.localStorage) {
- return;
- }
-
- window.localStorage.removeItem(model.getSubjectUri());
- }
- });
-})(jQuery);
-if (window.midgardCreate === undefined) {
- window.midgardCreate = {};
-}
-if (window.midgardCreate.locale === undefined) {
- window.midgardCreate.locale = {};
-}
-
-window.midgardCreate.locale.en = {
- // Session-state buttons for the main toolbar
- 'Save': 'Save',
- 'Saving': 'Saving',
- 'Cancel': 'Cancel',
- 'Edit': 'Edit',
- // Storage status messages
- 'localModification': 'Item "<%= label %>" has local modifications',
- 'localModifications': '<%= number %> items on this page have local modifications',
- 'Restore': 'Restore',
- 'Ignore': 'Ignore',
- 'saveSuccess': 'Item "<%= label %>" saved successfully',
- 'saveSuccessMultiple': '<%= number %> items saved successfully',
- 'saveError': 'Error occurred while saving
<%= error %>',
- // Tagging
- 'Item tags': 'Item tags',
- 'Suggested tags': 'Suggested tags',
- 'Tags': 'Tags',
- 'add a tag': 'add a tag',
- // Collection widgets
- 'Add': 'Add',
- 'Choose type to add': 'Choose type to add'
-};
diff --git a/core/modules/contextual/contextual-old.js b/core/modules/contextual/contextual-old.js
new file mode 100644
index 0000000..ea51af2
--- /dev/null
+++ b/core/modules/contextual/contextual-old.js
@@ -0,0 +1,243 @@
+/**
+ * @file
+ * Attaches behaviors for the Contextual module.
+ */
+
+(function ($, Drupal) {
+
+"use strict";
+
+var contextuals = [];
+
+/**
+ * Attaches outline behavior for regions associated with contextual links.
+ */
+Drupal.behaviors.contextual = {
+ attach: function (context) {
+ var that = this;
+ $('ul.contextual-links', context).once('contextual', function () {
+ var $this = $(this);
+ var contextual = new Drupal.contextual($this, $this.closest('.contextual-region'));
+ contextuals.push(contextual);
+ $this.data('drupal-contextual', contextual);
+ that._adjustIfNestedAndOverlapping(this);
+ });
+
+ // Bind to edit mode changes.
+ $('body').once('contextual', function () {
+ $(document).on('drupalEditModeChanged.contextual', toggleEditMode);
+ });
+ },
+
+ /**
+ * Determines if a contextual link is nested & overlapping, if so: adjusts it.
+ *
+ * This only deals with two levels of nesting; deeper levels are not touched.
+ *
+ * @param DOM contextualLink
+ * A contextual link DOM element.
+ */
+ _adjustIfNestedAndOverlapping: function (contextualLink) {
+ var $contextuals = $(contextualLink)
+ .parents('.contextual-region').eq(-1)
+ .find('.contextual');
+
+ // Early-return when there's no nesting.
+ if ($contextuals.length === 1) {
+ return;
+ }
+
+ // If the two contextual links overlap, then we move the second one.
+ var firstTop = $contextuals.eq(0).offset().top;
+ var secondTop = $contextuals.eq(1).offset().top;
+ if (firstTop === secondTop) {
+ var $nestedContextual = $contextuals.eq(1);
+
+ // Retrieve height of nested contextual link.
+ var height = 0;
+ var $trigger = $nestedContextual.find('.trigger');
+ // Elements with the .element-invisible class have no dimensions, so this
+ // class must be temporarily removed to the calculate the height.
+ $trigger.removeClass('element-invisible');
+ height = $nestedContextual.height();
+ $trigger.addClass('element-invisible');
+
+ // Adjust nested contextual link's position.
+ $nestedContextual.css({ top: $nestedContextual.position().top + height });
+ }
+ }
+};
+
+/**
+ * Contextual links object.
+ */
+Drupal.contextual = function($links, $region) {
+ this.$links = $links;
+ this.$region = $region;
+
+ this.init();
+};
+
+/**
+ * Initiates a contextual links object.
+ */
+Drupal.contextual.prototype.init = function() {
+ // Wrap the links to provide positioning and behavior attachment context.
+ this.$wrapper = $(Drupal.theme.contextualWrapper())
+ .insertBefore(this.$links)
+ .append(this.$links);
+
+ // Mark the links as hidden. Use aria-role form so that the number of items
+ // in the list is spoken.
+ this.$links
+ .prop('hidden', true)
+ .attr('role', 'form');
+
+ // Create and append the contextual links trigger.
+ var action = Drupal.t('Open');
+
+ var parentBlock = this.$region.find('h2').first().text();
+ this.$trigger = $(Drupal.theme.contextualTrigger())
+ .text(Drupal.t('@action @parent configuration options', {'@action': action, '@parent': parentBlock}))
+ // Set the aria-pressed state.
+ .prop('aria-pressed', false)
+ .prependTo(this.$wrapper);
+
+ // The trigger behaviors are never detached or mutated.
+ this.$region
+ .on('click.contextual', '.contextual .trigger:first', $.proxy(this.triggerClickHandler, this))
+ .on('mouseleave.contextual', '.contextual', {show: false}, $.proxy(this.triggerLeaveHandler, this));
+ // Attach highlight behaviors.
+ this.attachHighlightBehaviors();
+};
+
+/**
+ * Attaches highlight-on-mouseenter behaviors.
+ */
+Drupal.contextual.prototype.attachHighlightBehaviors = function () {
+ // Bind behaviors through delegation.
+ var highlightRegion = $.proxy(this.highlightRegion, this);
+ this.$region
+ .on('mouseenter.contextual.highlight', {highlight: true}, highlightRegion)
+ .on('mouseleave.contextual.highlight', {highlight: false}, highlightRegion)
+ .on('click.contextual.highlight', '.contextual-links a', {highlight: false}, highlightRegion)
+ .on('focus.contextual.highlight', '.contextual-links a, .contextual .trigger', {highlight: true}, highlightRegion)
+ .on('blur.contextual.highlight', '.contextual-links a, .contextual .trigger', {highlight: false}, highlightRegion);
+};
+
+/**
+ * Detaches unhighlight-on-mouseleave behaviors.
+ */
+Drupal.contextual.prototype.detachHighlightBehaviors = function () {
+ this.$region.off('.contextual.highlight');
+};
+
+/**
+ * Toggles the highlighting of a contextual region.
+ *
+ * @param {Object} event
+ * jQuery Event object.
+ */
+Drupal.contextual.prototype.highlightRegion = function(event) {
+ // Set up a timeout to delay the dismissal of the region highlight state.
+ if (!event.data.highlight && this.timer === undefined) {
+ return this.timer = window.setTimeout($.proxy($.fn.trigger, $(event.target), 'mouseleave.contextual'), 100);
+ }
+ // Clear the timeout to prevent an infinite loop of mouseleave being
+ // triggered.
+ if (this.timer) {
+ window.clearTimeout(this.timer);
+ delete this.timer;
+ }
+ // Toggle active state of the contextual region based on the highlight value.
+ this.$region.toggleClass('contextual-region-active', event.data.highlight);
+ // Hide the links if the contextual region is inactive.
+ var state = this.$region.hasClass('contextual-region-active');
+ if (!state) {
+ this.showLinks(state);
+ }
+};
+
+/**
+ * Handles click on the contextual links trigger.
+ *
+ * @param {Object} event
+ * jQuery Event object.
+ */
+Drupal.contextual.prototype.triggerClickHandler = function (event) {
+ event.preventDefault();
+ // Hide all nested contextual triggers while the links are shown for this one.
+ this.$region.find('.contextual .trigger:not(:first)').hide();
+ this.showLinks();
+};
+
+/**
+ * Handles mouseleave on the contextual links trigger.
+ *
+ * @param {Object} event
+ * jQuery Event object.
+ */
+Drupal.contextual.prototype.triggerLeaveHandler = function (event) {
+ var show = event && event.data && event.data.show;
+ // Show all nested contextual triggers when the links are hidden for this one.
+ this.$region.find('.contextual .trigger:not(:first)').show();
+ this.showLinks(show);
+};
+
+/**
+ * Toggles the active state of the contextual links.
+ *
+ * @param {Boolean} show
+ * (optional) True if the links should be shown. False is the links should be
+ * hidden.
+ */
+Drupal.contextual.prototype.showLinks = function(show) {
+ this.$wrapper.toggleClass('contextual-links-active', show);
+ var isOpen = this.$wrapper.hasClass('contextual-links-active');
+ var action = (isOpen) ? Drupal.t('Close') : Drupal.t('Open');
+ var parentBlock = this.$region.find('h2').first().text();
+ this.$trigger
+ .text(Drupal.t('@action @parent configuration options', {'@action': action, '@parent': parentBlock}))
+ // Set the aria-pressed state.
+ .prop('aria-pressed', isOpen);
+ // Mark the links as hidden if they are.
+ if (isOpen) {
+ this.$links.prop('hidden', false);
+ }
+ else {
+ this.$links.prop('hidden', true);
+ }
+
+};
+
+/**
+ * Shows or hides all pencil icons and corresponding contextual regions.
+ */
+function toggleEditMode (event, data) {
+ for (var i = contextuals.length - 1; i >= 0; i--) {
+ contextuals[i][(data.status) ? 'detachHighlightBehaviors' : 'attachHighlightBehaviors']();
+ contextuals[i].$region.toggleClass('contextual-region-active', data.status);
+ }
+}
+
+/**
+ * Wraps contextual links.
+ *
+ * @return {String}
+ * A string representing a DOM fragment.
+ */
+Drupal.theme.contextualWrapper = function () {
+ return '
';
+};
+
+/**
+ * A trigger is an interactive element often bound to a click handler.
+ *
+ * @return {String}
+ * A string representing a DOM fragment.
+ */
+Drupal.theme.contextualTrigger = function () {
+ return '';
+};
+
+})(jQuery, Drupal);
diff --git a/core/modules/edit/css/edit.css b/core/modules/edit/css/edit.css
index 6a5ac83..1ceea82 100644
--- a/core/modules/edit/css/edit.css
+++ b/core/modules/edit/css/edit.css
@@ -121,6 +121,10 @@
*/
}
+[data-edit-entity].edit-active {
+ outline: 2px dotted red;
+}
+
diff --git a/core/modules/edit/edit.module b/core/modules/edit/edit.module
index a6ee046..3cec929 100644
--- a/core/modules/edit/edit.module
+++ b/core/modules/edit/edit.module
@@ -65,7 +65,6 @@ function edit_library_info() {
$path = drupal_get_path('module', 'edit');
$options = array(
'scope' => 'footer',
- 'attributes' => array('defer' => TRUE),
);
$libraries['edit'] = array(
'title' => 'Edit: in-place editing',
@@ -73,10 +72,8 @@ function edit_library_info() {
'version' => VERSION,
'js' => array(
// Core.
+ $path . '/js/editors/editor.js' => $options,
$path . '/js/edit.js' => $options,
- $path . '/js/app.js' => $options,
- // Models.
- $path . '/js/models/edit-app-model.js' => $options,
// Views.
$path . '/js/views/propertyeditordecoration-view.js' => $options,
$path . '/js/views/contextuallink-view.js' => $options,
@@ -84,11 +81,8 @@ function edit_library_info() {
$path . '/js/views/toolbar-view.js' => $options,
// Backbone.sync implementation on top of Drupal forms.
$path . '/js/backbone.drupalform.js' => $options,
- // VIE service.
- $path . '/js/viejs/EditService.js' => $options,
- // Create.js subclasses.
- $path . '/js/createjs/editable.js' => $options,
- $path . '/js/createjs/storage.js' => $options,
+ // Storage manager.
+ $path . '/js/storage.js' => $options,
// Other.
$path . '/js/util.js' => $options,
$path . '/js/theme.js' => $options,
@@ -109,8 +103,6 @@ function edit_library_info() {
array('system', 'jquery'),
array('system', 'underscore'),
array('system', 'backbone'),
- array('system', 'vie.core'),
- array('system', 'create.editonly'),
array('system', 'jquery.form'),
array('system', 'drupal.form'),
array('system', 'drupal.ajax'),
@@ -121,7 +113,7 @@ function edit_library_info() {
'title' => '"Form" Create.js PropertyEditor widget',
'version' => VERSION,
'js' => array(
- $path . '/js/createjs/editingWidgets/formwidget.js' => $options,
+ $path . '/js/editors/formEditor.js' => $options,
),
'dependencies' => array(
array('edit', 'edit'),
@@ -131,7 +123,7 @@ function edit_library_info() {
'title' => '"Direct" Create.js PropertyEditor widget',
'version' => VERSION,
'js' => array(
- $path . '/js/createjs/editingWidgets/drupalcontenteditablewidget.js' => $options,
+ $path . '/js/editors/directEditor.js' => $options,
),
'dependencies' => array(
array('edit', 'edit'),
diff --git a/core/modules/edit/js/app.js b/core/modules/edit/js/app.js
deleted file mode 100644
index 14d76a0..0000000
--- a/core/modules/edit/js/app.js
+++ /dev/null
@@ -1,391 +0,0 @@
-/**
- * @file
- * A Backbone View that is the central app controller.
- */
-(function ($, _, Backbone, Drupal, VIE) {
-
-"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;
-
- // 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;
- }
- // 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;
- }
- }
-
- // 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;
- }
- }
- // 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;
- }
- else {
- // 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);
- }
- }
- });
- 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('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);
- },
-
- /**
- * 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
- });
-
- // 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()
- });
-
- // @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);
- });
- },
-
- /**
- * 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/backbone.drupalform.js b/core/modules/edit/js/backbone.drupalform.js
index 750d16f..170f4a9 100644
--- a/core/modules/edit/js/backbone.drupalform.js
+++ b/core/modules/edit/js/backbone.drupalform.js
@@ -105,7 +105,7 @@ Backbone.syncDirect = function(method, model, options) {
if (jQuery('#edit_backstage form').length === 0) {
var formOptions = {
propertyID: Drupal.edit.util.calcPropertyID(entity, predicate),
- $editorElement: options.editor.element,
+ $editorElement: options.editor.$el,
nocssjs: true
};
Drupal.edit.util.form.load(formOptions, function(form, ajax) {
diff --git a/core/modules/edit/js/createjs/editable.js b/core/modules/edit/js/createjs/editable.js
deleted file mode 100644
index 1316023..0000000
--- a/core/modules/edit/js/createjs/editable.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * @file
- * Determines which editor (Create.js PropertyEditor widget) to use.
- */
-(function (jQuery, Drupal, drupalSettings) {
-
-"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);
- },
-
- _propertyEditorName: function(data) {
- // Pick a PropertyEditor widget for a property depending on its metadata.
- var propertyID = Drupal.edit.util.calcPropertyID(data.entity, data.property);
- return Drupal.edit.metadataCache[propertyID].editor;
- }
- });
-
-})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js b/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js
deleted file mode 100644
index cde6163..0000000
--- a/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * @file
- * Override of Create.js' default "base" (plain contentEditable) widget.
- */
-(function (jQuery, Drupal) {
-
-"use strict";
-
- // @todo D8: use jQuery UI Widget bridging.
- // @see http://drupal.org/node/1874934#comment-7124904
- jQuery.widget('Midgard.direct', jQuery.Midgard.editWidget, {
-
- /**
- * Implements getEditUISettings() method.
- */
- getEditUISettings: function() {
- return { padding: true, unifiedToolbar: false, fullWidthToolbar: false };
- },
-
- /**
- * Implements jQuery UI widget factory's _init() method.
- *
- * @todo: POSTPONED_ON(Create.js, https://github.com/bergie/create/issues/142)
- * Get rid of this once that issue is solved.
- */
- _init: function() {},
-
- /**
- * Implements Create's _initialize() method.
- */
- _initialize: function() {
- var that = this;
-
- // Sets the state to 'changed' whenever the content has changed.
- var before = jQuery.trim(this.element.text());
- this.element.on('keyup paste', function (event) {
- if (that.options.disabled) {
- return;
- }
- var current = jQuery.trim(that.element.text());
- if (before !== current) {
- before = current;
- that.options.changed(current);
- }
- });
- },
-
- /**
- * Makes this PropertyEditor widget react to state changes.
- */
- stateChange: function(from, to) {
- switch (to) {
- case 'inactive':
- break;
- case 'candidate':
- if (from !== 'inactive') {
- // Removes the "contenteditable" attribute.
- this.disable();
- }
- break;
- case 'highlighted':
- break;
- case 'activating':
- this.options.activated();
- break;
- case 'active':
- // Sets the "contenteditable" attribute to "true".
- this.enable();
- break;
- case 'changed':
- break;
- case 'saving':
- break;
- case 'saved':
- break;
- case 'invalid':
- break;
- }
- }
-
- });
-
-})(jQuery, Drupal);
diff --git a/core/modules/edit/js/createjs/editingWidgets/formwidget.js b/core/modules/edit/js/createjs/editingWidgets/formwidget.js
deleted file mode 100644
index aa2dd0a..0000000
--- a/core/modules/edit/js/createjs/editingWidgets/formwidget.js
+++ /dev/null
@@ -1,152 +0,0 @@
-/**
- * @file
- * Form-based Create.js widget for structured content in Drupal.
- */
-(function ($, Drupal) {
-
-"use strict";
-
- // @todo D8: change the name to "form" + use jQuery UI Widget bridging.
- // @see http://drupal.org/node/1874934#comment-7124904
- $.widget('Midgard.formEditEditor', $.Midgard.editWidget, {
-
- id: null,
- $formContainer: null,
-
- /**
- * Implements getEditUISettings() method.
- */
- getEditUISettings: function() {
- return { padding: false, unifiedToolbar: false, fullWidthToolbar: false };
- },
-
- /**
- * Implements jQuery UI widget factory's _init() method.
- *
- * @todo: POSTPONED_ON(Create.js, https://github.com/bergie/create/issues/142)
- * Get rid of this once that issue is solved.
- */
- _init: function() {},
-
- /**
- * Implements Create's _initialize() method.
- */
- _initialize: function() {},
-
- /**
- * Makes this PropertyEditor widget react to state changes.
- */
- stateChange: function(from, to) {
- switch (to) {
- case 'inactive':
- break;
- case 'candidate':
- if (from !== 'inactive') {
- this.disable();
- }
- break;
- case 'highlighted':
- break;
- case 'activating':
- this.enable();
- break;
- case 'active':
- break;
- case 'changed':
- break;
- case 'saving':
- break;
- case 'saved':
- break;
- case 'invalid':
- break;
- }
- },
-
- /**
- * Enables the widget.
- */
- enable: function () {
- var $editorElement = $(this.options.widget.element);
- var propertyID = Drupal.edit.util.calcPropertyID(this.options.entity, this.options.property);
-
- // Generate a DOM-compatible ID for the form container DOM element.
- this.id = 'edit-form-for-' + propertyID.replace(/\//g, '_');
-
- // Render form container.
- this.$formContainer = $(Drupal.theme('editFormContainer', {
- id: this.id,
- loadingMsg: Drupal.t('Loading…')}
- ));
- this.$formContainer
- .find('.edit-form')
- .addClass('edit-editable edit-highlighted edit-editing')
- .attr('role', 'dialog');
-
- // Insert form container in DOM.
- if ($editorElement.css('display') === 'inline') {
- // @todo: POSTPONED_ON(Drupal core, title/author/date as Entity Properties)
- // This is untested in Drupal 8, because in Drupal 8 we don't yet
- // have the ability to edit the node title/author/date, because they
- // haven't been converted into Entity Properties yet, and they're the
- // only examples in core of "display: inline" properties.
- this.$formContainer.prependTo($editorElement.offsetParent());
-
- var pos = $editorElement.position();
- this.$formContainer.css('left', pos.left).css('top', pos.top);
- }
- else {
- this.$formContainer.insertBefore($editorElement);
- }
-
- // Load form, insert it into the form container and attach event handlers.
- var widget = this;
- var formOptions = {
- propertyID: propertyID,
- $editorElement: $editorElement,
- nocssjs: false
- };
- Drupal.edit.util.form.load(formOptions, function(form, ajax) {
- Drupal.ajax.prototype.commands.insert(ajax, {
- data: form,
- selector: '#' + widget.id + ' .placeholder'
- });
-
- var $submit = widget.$formContainer.find('.edit-form-submit');
- Drupal.edit.util.form.ajaxifySaving(formOptions, $submit);
- widget.$formContainer
- .on('formUpdated.edit', ':input', function () {
- // Sets the state to 'changed'.
- widget.options.changed();
- })
- .on('keypress.edit', 'input', function (event) {
- if (event.keyCode === 13) {
- return false;
- }
- });
-
- // Sets the state to 'activated'.
- widget.options.activated();
- });
- },
-
- /**
- * Disables the widget.
- */
- disable: function () {
- if (this.$formContainer === null) {
- return;
- }
-
- Drupal.edit.util.form.unajaxifySaving(this.$formContainer.find('.edit-form-submit'));
- // Allow form widgets to detach properly.
- Drupal.detachBehaviors(this.$formContainer, null, 'unload');
- this.$formContainer
- .off('change.edit', ':input')
- .off('keypress.edit', 'input')
- .remove();
- this.$formContainer = null;
- }
- });
-
-})(jQuery, Drupal);
diff --git a/core/modules/edit/js/createjs/storage.js b/core/modules/edit/js/createjs/storage.js
deleted file mode 100644
index 580ff82..0000000
--- a/core/modules/edit/js/createjs/storage.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * @file
- * Subclasses jQuery.Midgard.midgardStorage to have consistent namespaces.
- */
-(function(jQuery) {
-
-"use strict";
-
- jQuery.widget('Drupal.createStorage', jQuery.Midgard.midgardStorage, {});
-
-})(jQuery);
diff --git a/core/modules/edit/js/edit.js b/core/modules/edit/js/edit.js
index f924e7b..47bc671 100644
--- a/core/modules/edit/js/edit.js
+++ b/core/modules/edit/js/edit.js
@@ -13,29 +13,59 @@ Drupal.edit.metadataCache = Drupal.edit.metadataCache || {};
* Attach toggling behavior and in-place editing.
*/
Drupal.behaviors.edit = {
+
+ views: {
+ contextualLinks: {},
+ decorators: {},
+ editables: {},
+ entities: {},
+ propertyEditors: {},
+ toolbars: {}
+ },
+
+ collections: {
+ entities: null,
+ editables: null
+ },
+
+ controllers: {
+ editables: {}
+ },
+
attach: function(context) {
+ var that = this;
var $context = $(context);
var $fields = $context.find('[data-edit-id]');
+ var options = $.extend({}, this.defaults, (drupalSettings.edit || {}));
+ var Collection;
+
+ // Store a collection of entity models.
+ if (!this.collections.entities) {
+ Collection = Backbone.Collection.extend({
+ model: Drupal.edit.EntityModel
+ });
+ this.collections.entities = new Collection();
+ }
+
+ // Store a collection of editables models.
+ if (!this.collections.editables) {
+ this.collections.editables = new Drupal.edit.EditablesCollection();
+ }
+
+ // Respond to entity model change events.
+ this.collections.entities
+ .on('change:isActive', this.enforceSingleActiveEntity, this);
+
+ this.collections.editables
+ // Respond to editable model HTML representation change events.
+ .on('change:html', this.updateAllKnownInstances, this);
// Initialize the Edit app.
- $('body').once('edit-init', Drupal.edit.init);
-
- var annotateField = function(field) {
- if (_.has(Drupal.edit.metadataCache, field.editID)) {
- var meta = Drupal.edit.metadataCache[field.editID];
-
- field.$el.addClass((meta.access) ? 'edit-allowed' : 'edit-disallowed');
- if (meta.access) {
- field.$el
- .attr('data-edit-field-label', meta.label)
- .attr('aria-label', meta.aria)
- .addClass('edit-field edit-type-' + ((meta.editor === 'form') ? 'form' : 'direct'));
- }
+ $('body').once('edit-init', $.proxy(this.init, this, options));
- return true;
- }
- return false;
- };
+ // @todo currently must be after the call to init, because the app needs to be initialized
+ this.collections.editables
+ .on('change:state', Drupal.edit.app.editorStateChange, Drupal.edit.app);
// Find all fields in the context without metadata.
var fieldsToAnnotate = _.map($fields.not('.edit-allowed, .edit-disallowed'), function(el) {
@@ -46,14 +76,15 @@ Drupal.behaviors.edit = {
// Fields whose metadata is known (typically when they were just modified)
// can be annotated immediately, those remaining must be requested.
var remainingFieldsToAnnotate = _.reduce(fieldsToAnnotate, function(result, field) {
- if (!annotateField(field)) {
+ var isAnnotated = $.proxy(that.annotateField, that, field)();
+ if (!isAnnotated) {
result.push(field);
}
return result;
}, []);
// Make fields that could be annotated immediately available for editing.
- Drupal.edit.app.findEditableProperties($context);
+ this.findEditableProperties($context, options);
if (remainingFieldsToAnnotate.length) {
$(window).ready(function() {
@@ -66,48 +97,658 @@ Drupal.behaviors.edit = {
// Update the metadata cache.
_.each(results, function(metadata, editID) {
Drupal.edit.metadataCache[editID] = metadata;
+ // @temporary HACK: use the 'form' editor for all fields
+ // Drupal.edit.metadataCache[editID].editor = 'form';
});
// Annotate the remaining fields based on the updated access cache.
- _.each(remainingFieldsToAnnotate, annotateField);
+ _.each(remainingFieldsToAnnotate, $.proxy(that.annotateField, that));
- // Find editable fields, make them editable.
- Drupal.edit.app.findEditableProperties($context);
+ // Make fields that could be annotated immediately available for editing.
+ that.findEditableProperties($context, options);
}
});
});
}
- }
-};
+ },
-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),
+ 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.AppModel();
+ var app = new Drupal.edit.AppView({
+ el: $('body').get(0),
model: appModel,
- entity: $editContextualLink.parents('[data-edit-entity]').attr('data-edit-entity')
+ entitiesCollection: this.collections.entities
+ });
+ var entityModel;
+
+ // Create a view for the Entity just once.
+ $('[data-edit-entity]').once('editEntity', function (index) {
+ var $this = $(this);
+ var id = $this.data('edit-entity');
+
+ entityModel = new Drupal.edit.EntityModel({
+ id: id
+ });
+ that.collections.entities.add(entityModel);
+
+ // Create a View for the entity.
+ that.views.entities[id] = new Drupal.edit.EntityView($.extend({
+ el: this,
+ model: entityModel
+ }, options));
+
+ // Create a view for the contextual links.
+ $this.find('.contextual-links')
+ .each(function () {
+ // Instantiate ContextualLinkView.
+ that.views.contextualLinks[id] = new Drupal.edit.ContextualLinkView($.extend({
+ el: this,
+ model: entityModel,
+ appModel: appModel
+ }, options));
+ });
+ });
+
+ // Add "Quick edit" links to all contextual menus where editing the full
+ // node is possible.
+ // @todo Generalize this to work for all entities.
+
+
+ // 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;
+ },
+
+ /**
+ * 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, options) {
+ var that = this;
+ var app = Drupal.edit.app;
+ var model = app.model;
+ var activeEntity = model.get('activeEntity');
+ options = options || {};
+
+ $context.find('[data-edit-id]').each(function() {
+ var $element = $(this);
+ var editID = $element.attr('data-edit-id');
+
+ if (!_.has(Drupal.edit.metadataCache, editID)) {
+ return;
+ }
+
+ var entityId = $element.closest('[data-edit-entity]').data('edit-entity');
+ // @todo Note that 1) not every field that has a data-edit-id also has a
+ // surrounding data-edit-entity, 2) also note that whether the "Quick Edit"
+ // link should appear depends on whether the user has access to edit any
+ // of the entity's fields. So, it is blocked on the metadata callback to
+ // the server. This is not yet implemented in the D8 HEAD Edit, but it is
+ // in http://drupal.org/node/1971108. We should take this into account if
+ // possible. If not, then we'll just have to refactor later.
+ // For now, this assumption is acceptable.
+ var entity = that.collections.entities.where({id: entityId})[0];
+
+ // The EditableModel stores the state of an editable entity field.
+ var editableModel = new Drupal.edit.EditableModel({
+ entity: entity,
+ $el: $element,
+ editID: editID,
+ label: Drupal.edit.metadataCache[editID].label,
+ editor: Drupal.edit.metadataCache[editID].editor,
+ html: $element[0].outerHTML,
+ acceptStateChange: _.bind(Drupal.edit.app.acceptEditorStateChange, Drupal.edit.app)
+ });
+
+ // Store this editable field model in the collection where we track all
+ // fields on the page.
+ that.collections.editables.add(editableModel);
+
+ // @todo make the below work
+ return;
+
+ // 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');
+ }
});
- });
+ },
- // 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;
+ annotateField: function (field) {
+ if (_.has(Drupal.edit.metadataCache, field.editID)) {
+ var meta = Drupal.edit.metadataCache[field.editID];
+
+ field.$el.addClass((meta.access) ? 'edit-allowed' : 'edit-disallowed');
+ if (meta.access) {
+ field.$el
+ .attr('data-edit-field-label', meta.label)
+ .attr('aria-label', meta.aria)
+ .addClass('edit-field edit-type-' + ((meta.editor === 'form') ? 'form' : 'direct'));
+ }
+
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * EntityModel Collection change handler, called on change:isActive, enforces
+ * a single active entity.
+ */
+ enforceSingleActiveEntity: function (changedEntityModel) {
+ // When an entity is deactivated, we don't need to enforce anything.
+ if (changedEntityModel.get('isActive') === false) {
+ return;
+ }
+
+ // This entity was activated; deactivate all other entities.
+ changedEntityModel.collection.chain()
+ .filter(function (entityModel) {
+ return entityModel.get('isActive') === true && entityModel !== changedEntityModel;
+ })
+ .each(function (entityModel) {
+ entityModel.set('isActive', false);
+ });
+ },
+
+ // Updates all known instances of a specific entity field whenever the HTML
+ // representation of one of them has changed.
+ // @todo: this is currently prototype-level code, test this. The principle is
+ // sound, but the tricky thing is that an editID includes the view mode, but
+ // we actually want to update the same field in other view modes too, which
+ // means this method will have to check if there are any such instances, and
+ // if so, go to the server and re-render those too.
+ updateAllKnownInstances: function (changedModel) {
+ changedModel.collection.where({ editID: changedModel.get('editID') })
+ .set('html', changedModel.get('html'));
+ },
+
+ defaults: {
+ strings: {
+ quickEdit: Drupal.t('Quick edit'),
+ stopQuickEdit: Drupal.t('Stop quick edit')
+ }
+ }
};
+$.extend(Drupal.edit, {
+
+ /**
+ *
+ */
+ AppModel: Backbone.Model.extend({
+ defaults: {
+ activeEntity: null,
+ highlightedEditor: null,
+ activeEditor: null,
+ // Reference to a ModalView-instance if a transition requires confirmation.
+ activeModal: null
+ }
+ }),
+
+ /**
+ *
+ */
+ AppView: Backbone.View.extend({
+ vie: null,
+ domService: null,
+
+ // Configuration for state handling.
+ states: [],
+ activeEditorStates: [],
+ singleEditorStates: [],
+
+ // State.
+ $entityElements: null,
+ editables: [],
+ entityViews: [],
+
+ /**
+ * Implements Backbone Views' initialize() function.
+ */
+ initialize: function (options) {
+ this.entitiesCollection = options.entitiesCollection;
+
+ _.bindAll(this, 'appStateChange', 'acceptEditorStateChange', 'editorStateChange');
+
+ // 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.entitiesCollection.on('change:isActive', this.appStateChange, this);
+ },
+
+ /**
+ * Handles setup/teardown and state changes when the active entity changes.
+ */
+ appStateChange: function (entityModel, isActive) {
+ var app = this;
+ if (isActive) {
+ // Move all fields of this entity from the 'inactive' state to the
+ // 'candidate' state.
+ entityModel.get('fields').each(function (fieldModel) {
+ // First, set up decoration views.
+ app.decorate(fieldModel);
+ // Second, change the field's state.
+ fieldModel.set('state', 'candidate');
+ });
+ }
+ else {
+ // Move all fields of this entity from whatever state they are in to
+ // the 'inactive' state.
+ entityModel.get('fields').each(function (fieldModel) {
+ // First, change the field's state.
+ fieldModel.set('state', 'inactive', { reason: 'stop' });
+ // Second, tear down decoration views.
+ app.undecorate(fieldModel);
+ });
+ }
+ },
+
+ /**
+ * Accepts or reject editor (PropertyEditor) state changes.
+ *
+ * This is what ensures that the app is in control of what happens.
+ *
+ * @param String from
+ * The previous state.
+ * @param String to
+ * The new state.
+ * @param null|Object context
+ * The context that is trying to trigger the state change.
+ * @param Function callback
+ * The callback function that should receive the state acceptance result.
+ */
+ acceptEditorStateChange: function(from, to, context, callback) {
+ var accept = true;
+
+ console.log("accept? %s → %s (reason: %s)", from, to, (context && context.reason) ? context.reason : 'NONE');
+
+ // 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;
+ }
+ // 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;
+ }
+ }
+
+ // 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;
+ }
+ }
+ // 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;
+ }
+ else {
+ // Check whether the transition has been confirmed?
+ if (context && context.confirmed) {
+ accept = true;
+ }
+ // Confirm this transition.
+ else {
+ // Do not accept this change right now, instead open a modal
+ // that will ask the user to confirm his choice.
+ accept = false;
+ // The callback will be called from the helper function.
+ this._confirmStopEditing(callback);
+ }
+ }
+ }
+ }
+ }
+
+ return accept;
+ },
+
+ // @todo rename to decorateField
+ decorate: function (fieldModel) {
+ var editID = fieldModel.get('editID');
+ var $el = fieldModel.get('$el');
+
+ // Create a new Editor.
+ var editorName = fieldModel.get('editor');
+ var editorView = new Drupal.edit.editors[editorName]({
+ el: $el,
+ model: fieldModel
+ });
+
+ // Toolbars are rendered "on-demand" (highlighting or activating).
+ // They are a sibling element before the editor's DOM element.
+ var toolbarView = new Drupal.edit.ToolbarView({
+ model: fieldModel,
+ editor: editorView
+ });
+
+ // Decorate the editor's DOM element depending on its state.
+ var decorationView = new Drupal.edit.PropertyEditorDecorationView({
+ el: $el,
+ model: fieldModel,
+ editor: editorView,
+ toolbarId: toolbarView.getId()
+ });
+
+ // Create references; necessary for undecorate().
+ fieldModel.set('decorationViews', {
+ editorView: editorView,
+ toolbarView: toolbarView,
+ decorationView: decorationView
+ });
+ },
+
+ // @todo rename to undecorateField
+ undecorate: function (fieldModel) {
+ var decorationViews = fieldModel.get('decorationViews');
+
+ // Unbind event handlers; remove toolbar element; delete toolbar view.
+ decorationViews.toolbarView.undelegateEvents();
+ decorationViews.toolbarView.remove();
+ delete decorationViews.toolbarView;
+
+ // Unbind event handlers; delete decoration view. Don't remove the element
+ // because that would remove the field itself.
+ decorationViews.decorationView.undelegateEvents();
+ delete decorationViews.decorationView;
+
+ // Unbind event handlers; delete editor view. Don't remove the element
+ // because that would remove the field itself.
+ decorationViews.editorView.undelegateEvents();
+ delete decorationViews.editorView;
+ },
+
+ /**
+ * Asks the user to confirm whether he wants to stop editing via a modal.
+ *
+ * @see acceptEditorStateChange()
+ */
+ _confirmStopEditing: function () {
+ // Only instantiate if there isn't a modal instance visible yet.
+ if (!this.model.get('activeModal')) {
+ var that = this;
+ var modal = new Drupal.edit.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);
+ // Set the state that matches the user's action.
+ var targetState = (action === 'discard') ? 'candidate' : 'saving';
+ that.model.get('activeEditor').set('state', 'candidate', { confirmed: true });
+ }
+ });
+ 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();
+ }
+ },
+
+ /**
+ * 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(fieldModel, state) {
+ var from = fieldModel.previous('state');
+ var to = state;
+
+ console.log("%c %s → %s %s", "background-color: black; color: white", from, to, fieldModel.get('editID'));
+
+ // Keep track of the highlighted editor in the global state.
+ if (_.indexOf(this.singleEditorStates, to) !== -1 && this.model.get('highlightedEditor') !== fieldModel) {
+ this.model.set('highlightedEditor', fieldModel);
+ }
+ else if (this.model.get('highlightedEditor') === fieldModel && 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') !== fieldModel) {
+ this.model.set('activeEditor', fieldModel);
+ }
+ else if (this.model.get('activeEditor') === fieldModel && to === 'candidate') {
+ // Discarded if it transitions from a changed state to 'candidate'.
+ if (from === 'changed' || from === 'invalid') {
+ // @todo FIX THIS!
+ // 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(fieldModel.options.entity);
+ }
+ this.model.set('activeEditor', null);
+ }
+ }
+ }),
+
+ /**
+ *
+ */
+ EntityModel: Backbone.Model.extend({
+ defaults: {
+ // An entity ID, of the form "/", e.g. "node/1".
+ id: null,
+ // Indicates whether this instance of this entity is currently being
+ // edited.
+ isActive: false,
+ // A Drupal.edit.EditablesCollection for all fields of this entity.
+ fields: null
+ },
+ initialize: function () {
+ this.set('fields', new Drupal.edit.EditablesCollection());
+ }
+ }),
+
+ /**
+ *
+ */
+ EntityView: Backbone.View.extend({
+
+ events: {},
+
+ /**
+ * Implements Backbone Views' initialize() function.
+ */
+ initialize: function (options) {
+ this.strings = this.options.strings;
+ this.model.on('change:isActive', this.render, this);
+ },
+
+ /**
+ * Implements Backbone.View.prototype.render().
+ */
+ render: function (model, value, options) {
+ var isActive = this.model.get('isActive');
+ this.$el.toggleClass('edit-active', isActive);
+
+ return this;
+ }
+ }),
+
+ /**
+ *
+ */
+ // @todo rename to FieldModel?
+ EditableModel: Backbone.Model.extend({
+ defaults: {
+ // @todo Try to get rid of this, but it's hard; see appStateChange()
+ $el: null,
+
+ // Possible states:
+ // - inactive
+ // - candidate
+ // - highlighted
+ // - activating
+ // - active
+ // - changed
+ // - saving
+ // - saved
+ // - saved
+ // - invalid
+ // @see http://createjs.org/guide/#states
+ state: 'inactive',
+ // A Drupal.edit.EntityModel. Its "properties" attribute, which is an
+ // EditablesCollection, is automatically updated to include this
+ // EditableModel.
+ entity: null,
+ // A place to store any decoration views.
+ decorationViews: {},
+
+
+ //
+ // Data set by the metadata callback.
+ //
+ // The ID of the in-place editor to use.
+ editor: null,
+ // The label to use.
+ label: null,
+
+ //
+ // Data derived from the information in the DOM.
+ //
+
+ // The edit ID, format: `::::`.
+ editID: null,
+ // The full HTML representation of this field (with the element that has
+ // the data-edit-id as the outer element). Used to propagate changes from
+ // this field instance to other instances of the same field.
+ html: null,
+
+ //
+ // Callbacks.
+ //
+
+ // Callback function for validating changes between states. Receives the
+ // previous state, new state, context, and a callback
+ acceptStateChange: null
+ },
+ initialize: function () {
+ this.get('entity').get('fields').add(this);
+ this.on('error', function(model, error) {
+ console.log('%c' + model.get("editID") + " " + error, 'color: orange');
+ });
+ },
+ validate: function (attrs, options) {
+ // We only care about validating the 'state' attribute.
+ if (!_.has(attrs, 'state')) {
+ return;
+ }
+
+ var current = this.get('state');
+ var next = attrs.state;
+ if (current !== next) {
+ // Ensure it's a valid state.
+ if (_.indexOf(this.constructor.states, next) === -1) {
+ return '"' + next + '" is an invalid state';
+ }
+ // Check if the acceptStateChange callback accepts it.
+ if (!this.get('acceptStateChange')(current, next, options)) {
+ return 'state change not accepted';
+ }
+ }
+ },
+ // @see VIE.prototype.EditService.prototype.getElementSubject()
+ // Parses the `/` part from the edit ID.
+ getEntityID: function() {
+ return this.get('editID').split('/').slice(0, 2).join('/');
+ },
+ // @see VIE.prototype.EditService.prototype.getElementPredicate()
+ // Parses the `//` part from the edit ID.
+ getFieldID: function() {
+ return this.get('editID').split('/').slice(2, 5).join('/');
+ }
+ }, {
+ states: [
+ 'inactive', 'candidate', 'highlighted',
+ 'activating', 'active', 'changed', 'saving', 'saved', 'invalid'
+ ],
+ activeEditorStates: ['activating', 'active'],
+ singleEditorStates: ['highlighted', 'activating', 'active']
+ }),
+
+ // A collection of EditableModels.
+ // @todo link back to the entity at the collection level (not the collection element level)?
+ EditablesCollection: Backbone.Collection.extend({
+ model: Drupal.edit.EditableModel
+ }),
+
+ // @todo provide a base class for formEditor/directEditor/…, migrate them away
+ // from editor.js
+ EditorView: Backbone.View.extend({
+
+ })
+});
+
})(jQuery, _, Backbone, Drupal, drupalSettings);
diff --git a/core/modules/edit/js/editors/directEditor.js b/core/modules/edit/js/editors/directEditor.js
new file mode 100644
index 0000000..ebf1650
--- /dev/null
+++ b/core/modules/edit/js/editors/directEditor.js
@@ -0,0 +1,84 @@
+/**
+ * @file
+ * Override of Create.js' default "base" (plain contentEditable) widget.
+ */
+(function ($, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+Drupal.edit.editors = Drupal.edit.editors || {};
+
+Drupal.edit.editors.direct = Backbone.View.extend({
+
+ /**
+ * Implements getEditUISettings() method.
+ */
+ getEditUISettings: function() {
+ return { padding: true, unifiedToolbar: false, fullWidthToolbar: false };
+ },
+
+ /**
+ * Implements jQuery UI widget factory's _init() method.
+ *
+ * @todo: POSTPONED_ON(Create.js, https://github.com/bergie/create/issues/142)
+ * Get rid of this once that issue is solved.
+ */
+ _init: function() {},
+
+ /**
+ * Implements Create's _initialize() method.
+ */
+ _initialize: function() {
+ var that = this;
+
+ // Sets the state to 'changed' whenever the content has changed.
+ var before = jQuery.trim(this.element.text());
+ this.element.on('keyup paste', function (event) {
+ if (that.options.disabled) {
+ return;
+ }
+ var current = jQuery.trim(that.element.text());
+ if (before !== current) {
+ before = current;
+ that.options.changed(current);
+ }
+ });
+ },
+
+ /**
+ * Makes this PropertyEditor widget react to state changes.
+ */
+ stateChange: function(model, value) {
+ switch (value) {
+ case 'inactive':
+ break;
+ case 'candidate':
+ if (from !== 'inactive') {
+ // Removes the "contenteditable" attribute.
+ this.disable();
+ }
+ break;
+ case 'highlighted':
+ break;
+ case 'activating':
+ this.options.activated();
+ break;
+ case 'active':
+ // Sets the "contenteditable" attribute to "true".
+ this.enable();
+ break;
+ case 'changed':
+ break;
+ case 'saving':
+ this.save();
+ break;
+ case 'saved':
+ break;
+ case 'invalid':
+ break;
+ }
+ }
+});
+
+})(jQuery, Drupal);
diff --git a/core/modules/edit/js/editors/editor.js b/core/modules/edit/js/editors/editor.js
new file mode 100644
index 0000000..e9a3696
--- /dev/null
+++ b/core/modules/edit/js/editors/editor.js
@@ -0,0 +1,81 @@
+(function ($, Drupal, drupalSettings) {
+
+ 'use strict';
+
+ Drupal.edit = Drupal.edit || {};
+ Drupal.edit.editors = Drupal.edit.editors || {};
+
+ Drupal.edit.editors.editor = function () {};
+
+ $.extend(Drupal.edit.editors.editor.prototype, {
+ // override to enable the widget
+ enable: function () {
+ this.element.attr('contenteditable', 'true');
+ },
+ // override to disable the widget
+ disable: function (disable) {
+ this.element.attr('contenteditable', 'false');
+ },
+ // called by the jQuery UI plugin factory when creating the property editor
+ // widget instance
+ _create: function () {
+ this._registerWidget();
+ this._initialize();
+
+ if (_.isFunction(this.options.decorate) && _.isFunction(this.options.decorateParams)) {
+ // TRICKY: we can't use this.options.decorateParams()'s 'propertyName'
+ // parameter just yet, because it will only be available after this
+ // object has been created, but we're currently in the constructor!
+ // Hence we have to duplicate part of its logic here.
+ this.options.decorate(this.options.decorateParams(null, {
+ propertyName: this.options.property,
+ propertyEditor: this,
+ propertyElement: this.element,
+ // Deprecated.
+ editor: this,
+ predicate: this.options.property,
+ element: this.element
+ }));
+ }
+ },
+ // called every time the property editor widget is called
+ _init: function () {
+ if (this.options.disabled) {
+ this.disable();
+ return;
+ }
+ this.enable();
+ },
+ // override this function to initialize the property editor widget functions
+ _initialize: function () {
+ var self = this;
+ this.element.on('focus', function () {
+ if (self.options.disabled) {
+ return;
+ }
+ self.options.activated();
+ });
+ this.element.on('blur', function () {
+ if (self.options.disabled) {
+ return;
+ }
+ self.options.deactivated();
+ });
+ var before = this.element.html();
+ this.element.on('keyup paste', function (event) {
+ if (self.options.disabled) {
+ return;
+ }
+ var current = jQuery(this).html();
+ if (before !== current) {
+ before = current;
+ self.options.changed(current);
+ }
+ });
+ },
+ // used to register the property editor widget name with the DOM element
+ _registerWidget: function () {
+ this.element.data("createWidgetName", this.widgetName);
+ }
+ });
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/edit/js/editors/formEditor.js b/core/modules/edit/js/editors/formEditor.js
new file mode 100644
index 0000000..b62bfe3
--- /dev/null
+++ b/core/modules/edit/js/editors/formEditor.js
@@ -0,0 +1,216 @@
+/**
+ * @file
+ * Form-based Create.js widget for structured content in Drupal.
+ */
+(function ($, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+Drupal.edit.editors = Drupal.edit.editors || {};
+
+Drupal.edit.editors.form = Backbone.View.extend({
+ id: null,
+ $formContainer: null,
+
+ /**
+ * Implements getEditUISettings() method.
+ */
+ getEditUISettings: function() {
+ return { padding: false, unifiedToolbar: false, fullWidthToolbar: false };
+ },
+
+ /**
+ *
+ */
+ initialize: function (options) {
+ this.model.on('change:state', this.stateChange, this);
+
+ // Generate a DOM-compatible ID for the form container DOM element.
+ this.elementId = 'edit-form-for-' + this.model.get('editID').replace(/\//g, '_');
+ },
+
+ /**
+ * Enables the widget.
+ */
+ enable: function () {
+ // Render form container.
+ this.$formContainer = $(Drupal.theme('editFormContainer', {
+ id: this.elementId,
+ loadingMsg: Drupal.t('Loading…')}
+ ));
+ this.$formContainer
+ .find('.edit-form')
+ .addClass('edit-editable edit-highlighted edit-editing')
+ .attr('role', 'dialog');
+
+ // Insert form container in DOM.
+ if (this.$el.css('display') === 'inline') {
+ this.$formContainer.prependTo(this.$el.offsetParent());
+ // Position the form container to render on top of the field's element.
+ var pos = this.$el.position();
+ this.$formContainer.css('left', pos.left).css('top', pos.top);
+ }
+ else {
+ this.$formContainer.insertBefore(this.$el);
+ }
+
+ // Load form, insert it into the form container and attach event handlers.
+ var that = this;
+ var formOptions = {
+ propertyID: this.model.get('editID'),
+ $editorElement: this.$el,
+ nocssjs: false
+ };
+ Drupal.edit.util.form.load(formOptions, function(form, ajax) {
+ Drupal.ajax.prototype.commands.insert(ajax, {
+ data: form,
+ selector: '#' + that.elementId + ' .placeholder'
+ });
+
+ var $submit = that.$formContainer.find('.edit-form-submit');
+ Drupal.edit.util.form.ajaxifySaving(formOptions, $submit);
+ that.$formContainer
+ .on('formUpdated.edit', ':input', function () {
+ // Sets the state to 'changed'.
+ that.model.set('state', 'changed');
+ })
+ .on('keypress.edit', 'input', function (event) {
+ if (event.keyCode === 13) {
+ return false;
+ }
+ });
+
+ // The in-place editor has loaded; change state to 'active'.
+ that.model.set('state', 'active');
+ });
+ },
+
+ /**
+ * Disables the widget.
+ */
+ disable: function () {
+ if (this.$formContainer === null) {
+ return;
+ }
+
+ Drupal.edit.util.form.unajaxifySaving(this.$formContainer.find('.edit-form-submit'));
+ // Allow form widgets to detach properly.
+ Drupal.detachBehaviors(this.$formContainer, null, 'unload');
+ this.$formContainer
+ .off('change.edit', ':input')
+ .off('keypress.edit', 'input')
+ .remove();
+ this.$formContainer = null;
+ },
+
+ /**
+ * Saves a property.
+ *
+ * This method deals with the complexity of the editor-dependent ways of
+ * inserting updated content and showing validation error messages.
+ *
+ * One might argue that this does not belong in a view. However, there is no
+ * actual "save" logic here, that lives in Backbone.sync. This is just some
+ * glue code, along with the logic for inserting updated content as well as
+ * showing validation error messages, the latter of which is certainly okay.
+ */
+ save: function() {
+ var that = this;
+ var editor = this;
+ var editableEntity = this.el;
+ var entity = this.entity;
+ var predicate = this.predicate;
+
+ // Use Create.js' Storage widget to handle saving. (Uses Backbone.sync.)
+ var storage = new Drupal.edit.Storage({
+ vie: that.vie,
+ editableNs: 'createeditable',
+ element: this.$el
+ });
+ storage.invoke('saveRemote', entity, {
+ editor: that,
+
+ // Successfully saved without validation errors.
+ success: function (model) {
+ //editableEntity.setState('saved', predicate);
+
+ // Now that the changes to this property have been saved, the saved
+ // attributes are now the "original" attributes.
+ entity._originalAttributes = entity._previousAttributes = _.clone(entity.attributes);
+
+ // Get data necessary to rerender property before it is unavailable.
+ var updatedProperty = entity.get(predicate + '/rendered');
+ var $propertyWrapper = editor.$el.closest('.edit-field');
+ var $context = $propertyWrapper.parent();
+
+ //editableEntity.setState('candidate', predicate);
+ // Unset the property, because it will be parsed again from the DOM, iff
+ // its new value causes it to still be rendered.
+ entity.unset(predicate, { silent: true });
+ entity.unset(predicate + '/rendered', { silent: true });
+ // Trigger event to allow for proper clean-up of editor-specific views.
+ editor.$el.trigger('destroyedPropertyEditor.edit', editor);
+
+ // Replace the old content with the new content.
+ $propertyWrapper.replaceWith(updatedProperty);
+ Drupal.attachBehaviors($context);
+ },
+
+ // Save attempted but failed due to validation errors.
+ error: function (validationErrorMessages) {
+ editableEntity.setState('invalid', predicate);
+
+ if (that.editorName === 'form') {
+ editor.$formContainer
+ .find('.edit-form')
+ .addClass('edit-validation-error')
+ .find('form')
+ .prepend(validationErrorMessages);
+ }
+ else {
+ var $errors = $('')
+ .append(validationErrorMessages);
+ editor.element
+ .addClass('edit-validation-error')
+ .after($errors);
+ }
+ }
+ });
+ },
+
+ /**
+ * Makes this PropertyEditor widget react to state changes.
+ */
+ stateChange: function(model, state) {
+ var from = model.previous('state');
+ var to = state;
+ switch (to) {
+ case 'inactive':
+ break;
+ case 'candidate':
+ if (from !== 'inactive') {
+ this.disable();
+ }
+ break;
+ case 'highlighted':
+ break;
+ case 'activating':
+ this.enable();
+ break;
+ case 'active':
+ break;
+ case 'changed':
+ break;
+ case 'saving':
+ this.save();
+ break;
+ case 'saved':
+ break;
+ case 'invalid':
+ break;
+ }
+ }
+});
+
+})(jQuery, Drupal);
diff --git a/core/modules/edit/js/models/edit-app-model.js b/core/modules/edit/js/models/edit-app-model.js
deleted file mode 100644
index 0c90fd0..0000000
--- a/core/modules/edit/js/models/edit-app-model.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * @file
- * A Backbone Model that models the current Edit application state.
- */
-(function(Backbone, Drupal) {
-
-"use strict";
-
-Drupal.edit = Drupal.edit || {};
-Drupal.edit.models = Drupal.edit.models || {};
-Drupal.edit.models.EditAppModel = Backbone.Model.extend({
- defaults: {
- activeEntity: null,
- highlightedEditor: null,
- activeEditor: null,
- // Reference to a ModalView-instance if a transition requires confirmation.
- activeModal: null
- }
-});
-
-})(Backbone, Drupal);
diff --git a/core/modules/edit/js/storage.js b/core/modules/edit/js/storage.js
new file mode 100644
index 0000000..196337e
--- /dev/null
+++ b/core/modules/edit/js/storage.js
@@ -0,0 +1,518 @@
+/**
+ * @file
+ * Subclasses jQuery.Midgard.midgardStorage to have consistent namespaces.
+ */
+(function($, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+
+Drupal.edit.Storage = function (options) {
+ $.extend(this, {
+ saveEnabled: true,
+ // VIE instance to use for storage handling
+ vie: null,
+ // Whether to use localstorage
+ localStorage: false,
+ removeLocalstorageOnIgnore: true,
+ // URL callback for Backbone.sync
+ url: '',
+ // Whether to enable automatic saving
+ autoSave: false,
+ // How often to autosave in milliseconds
+ autoSaveInterval: 5000,
+ // Whether to save entities that are referenced by entities
+ // we're saving to the server.
+ saveReferencedNew: false,
+ saveReferencedChanged: false,
+ // Namespace used for events from midgardEditable-derived widget
+ editableNs: 'midgardeditable',
+ // CSS selector for the Edit button, leave to null to not bind
+ // notifications to any element
+ editSelector: '#midgardcreate-edit a',
+ localize: function (id, language) {
+ return window.midgardCreate.localize(id, language);
+ },
+ language: null
+ }, options);
+ this._create();
+};
+
+$.extend(Drupal.edit.Storage.prototype, {
+
+ 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);
+ }
+ },
+ _create: function () {
+ var widget = this;
+ this.changedModels = [];
+
+ if (window.localStorage) {
+ this.localStorage = true;
+ }
+
+ this.vie.entities.on('add', function (model) {
+ // Add the back-end URL used by Backbone.sync
+ model.url = widget.url;
+ model.toJSON = model.toJSONLD;
+ });
+
+ widget._bindEditables();
+ if (widget.autoSave) {
+ widget._autoSave();
+ }
+ },
+
+ _autoSave: function () {
+ var widget = this;
+ widget.saveEnabled = true;
+
+ var doAutoSave = function () {
+ if (!widget.saveEnabled) {
+ return;
+ }
+
+ if (widget.changedModels.length === 0) {
+ return;
+ }
+
+ widget.saveRemoteAll({
+ // We make autosaves silent so that potential changes from server
+ // don't disrupt user while writing.
+ silent: true
+ });
+ };
+
+ var timeout = window.setInterval(doAutoSave, widget.options.autoSaveInterval);
+
+ this.element.on('startPreventSave', function () {
+ if (timeout) {
+ window.clearInterval(timeout);
+ timeout = null;
+ }
+ widget.disableAutoSave();
+ });
+ this.element.on('stopPreventSave', function () {
+ if (!timeout) {
+ timeout = window.setInterval(doAutoSave, widget.options.autoSaveInterval);
+ }
+ widget.enableAutoSave();
+ });
+
+ },
+
+ enableAutoSave: function () {
+ this.saveEnabled = true;
+ },
+
+ disableAutoSave: function () {
+ this.saveEnabled = false;
+ },
+
+ _bindEditables: function () {
+ var widget = this;
+ this.restorables = [];
+ var restorer;
+
+ widget.element.on(widget.editableNs + 'changed', function (event, options) {
+ if (_.indexOf(widget.changedModels, options.instance) === -1) {
+ widget.changedModels.push(options.instance);
+ }
+ widget._saveLocal(options.instance);
+ });
+
+ widget.element.on(widget.editableNs + 'disable', function (event, options) {
+ widget.revertChanges(options.instance);
+ });
+
+ widget.element.on(widget.editableNs + 'enable', function (event, options) {
+ if (!options.instance._originalAttributes) {
+ options.instance._originalAttributes = _.clone(options.instance.attributes);
+ }
+
+ if (!options.instance.isNew() && widget._checkLocal(options.instance)) {
+ // We have locally-stored modifications, user needs to be asked
+ widget.restorables.push(options.instance);
+ }
+
+ /*_.each(options.instance.attributes, function (attributeValue, property) {
+ if (attributeValue instanceof widget.vie.Collection) {
+ widget._readLocalReferences(options.instance, property, attributeValue);
+ }
+ });*/
+ });
+
+ widget.element.on('midgardcreatestatechange', function (event, options) {
+ if (options.state === 'browse' || widget.restorables.length === 0) {
+ widget.restorables = [];
+ if (restorer) {
+ restorer.close();
+ }
+ return;
+ }
+ restorer = widget.checkRestore();
+ });
+
+ widget.element.on('midgardstorageloaded', function (event, options) {
+ if (_.indexOf(widget.changedModels, options.instance) === -1) {
+ widget.changedModels.push(options.instance);
+ }
+ });
+ },
+
+ checkRestore: function () {
+ var widget = this;
+ if (widget.restorables.length === 0) {
+ return;
+ }
+
+ var message;
+ var restorer;
+ if (widget.restorables.length === 1) {
+ message = _.template(widget.options.localize('localModification', widget.options.language), {
+ label: widget.restorables[0].getSubjectUri()
+ });
+ } else {
+ message = _.template(widget.options.localize('localModifications', widget.options.language), {
+ number: widget.restorables.length
+ });
+ }
+
+ var doRestore = function (event, notification) {
+ widget.restoreLocalAll();
+ restorer.close();
+ };
+
+ var doIgnore = function (event, notification) {
+ widget.ignoreLocal();
+ restorer.close();
+ };
+
+ restorer = jQuery('body').midgardNotifications('create', {
+ bindTo: widget.options.editSelector,
+ gravity: 'TR',
+ body: message,
+ timeout: 0,
+ actions: [
+ {
+ name: 'restore',
+ label: widget.options.localize('Restore', widget.options.language),
+ cb: doRestore,
+ className: 'create-ui-btn'
+ },
+ {
+ name: 'ignore',
+ label: widget.options.localize('Ignore', widget.options.language),
+ cb: doIgnore,
+ className: 'create-ui-btn'
+ }
+ ],
+ callbacks: {
+ beforeShow: function () {
+ if (!window.Mousetrap) {
+ return;
+ }
+ window.Mousetrap.bind(['command+shift+r', 'ctrl+shift+r'], function (event) {
+ event.preventDefault();
+ doRestore();
+ });
+ window.Mousetrap.bind(['command+shift+i', 'ctrl+shift+i'], function (event) {
+ event.preventDefault();
+ doIgnore();
+ });
+ },
+ afterClose: function () {
+ if (!window.Mousetrap) {
+ return;
+ }
+ window.Mousetrap.unbind(['command+shift+r', 'ctrl+shift+r']);
+ window.Mousetrap.unbind(['command+shift+i', 'ctrl+shift+i']);
+ }
+ }
+ });
+ return restorer;
+ },
+
+ restoreLocalAll: function () {
+ _.each(this.restorables, function (instance) {
+ this.readLocal(instance);
+ }, this);
+ this.restorables = [];
+ },
+
+ ignoreLocal: function () {
+ if (this.options.removeLocalstorageOnIgnore) {
+ _.each(this.restorables, function (instance) {
+ this._removeLocal(instance);
+ }, this);
+ }
+ this.restorables = [];
+ },
+
+ saveReferences: function (model) {
+ _.each(model.attributes, function (value, property) {
+ if (!value || !value.isCollection) {
+ return;
+ }
+
+ value.each(function (referencedModel) {
+ if (this.changedModels.indexOf(referencedModel) !== -1) {
+ // The referenced model is already in the save queue
+ return;
+ }
+
+ if (referencedModel.isNew() && this.options.saveReferencedNew) {
+ return referencedModel.save();
+ }
+
+ if (referencedModel.hasChanged() && this.options.saveReferencedChanged) {
+ return referencedModel.save();
+ }
+ }, this);
+ }, this);
+ },
+
+ saveRemote: function (model, options) {
+ // Optionally handle entities referenced in this model first
+ this.saveReferences(model);
+
+ // this._trigger('saveentity', null, {
+ // entity: model,
+ // options: options
+ // });
+
+ var widget = this;
+ model.save(null, _.extend({}, options, {
+ success: function (m, response) {
+ // From now on we're going with the values we have on server
+ model._originalAttributes = _.clone(model.attributes);
+ widget._removeLocal(model);
+ window.setTimeout(function () {
+ // Remove the model from the list of changed models after saving
+ widget.changedModels.splice(widget.changedModels.indexOf(model), 1);
+ }, 0);
+ if (_.isFunction(options.success)) {
+ options.success(m, response);
+ }
+ // widget._trigger('savedentity', null, {
+ // entity: model,
+ // options: options
+ // });
+ },
+ error: function (m, response) {
+ if (_.isFunction(options.error)) {
+ options.error(m, response);
+ }
+ }
+ }));
+ },
+
+ saveRemoteAll: function (options) {
+ var widget = this;
+ if (widget.changedModels.length === 0) {
+ return;
+ }
+
+ widget._trigger('save', null, {
+ entities: widget.changedModels,
+ options: options,
+ // Deprecated
+ models: widget.changedModels
+ });
+
+ var notification_msg;
+ var needed = widget.changedModels.length;
+ if (needed > 1) {
+ notification_msg = _.template(widget.options.localize('saveSuccessMultiple', widget.options.language), {
+ number: needed
+ });
+ } else {
+ notification_msg = _.template(widget.options.localize('saveSuccess', widget.options.language), {
+ label: widget.changedModels[0].getSubjectUri()
+ });
+ }
+
+ widget.disableAutoSave();
+ _.each(widget.changedModels, function (model) {
+ this.saveRemote(model, {
+ success: function (m, response) {
+ needed--;
+ if (needed <= 0) {
+ // All models were happily saved
+ widget._trigger('saved', null, {
+ options: options
+ });
+ if (options && _.isFunction(options.success)) {
+ options.success(m, response);
+ }
+ jQuery('body').midgardNotifications('create', {
+ body: notification_msg
+ });
+ widget.enableAutoSave();
+ }
+ },
+ error: function (m, err) {
+ if (options && _.isFunction(options.error)) {
+ options.error(m, err);
+ }
+ jQuery('body').midgardNotifications('create', {
+ body: _.template(widget.options.localize('saveError', widget.options.language), {
+ error: err.responseText || ''
+ }),
+ timeout: 0
+ });
+
+ widget._trigger('error', null, {
+ instance: model
+ });
+ }
+ });
+ }, this);
+ },
+
+ _saveLocal: function (model) {
+ if (!this.options.localStorage) {
+ return;
+ }
+
+ if (model.isNew()) {
+ // Anonymous object, save as refs instead
+ if (!model.primaryCollection) {
+ return;
+ }
+ return this._saveLocalReferences(model.primaryCollection.subject, model.primaryCollection.predicate, model);
+ }
+ window.localStorage.setItem(model.getSubjectUri(), JSON.stringify(model.toJSONLD()));
+ },
+
+ _getReferenceId: function (model, property) {
+ return model.id + ':' + property;
+ },
+
+ _saveLocalReferences: function (subject, predicate, model) {
+ if (!this.options.localStorage) {
+ return;
+ }
+
+ if (!subject || !predicate) {
+ return;
+ }
+
+ var widget = this;
+ var identifier = subject + ':' + predicate;
+ var json = model.toJSONLD();
+ if (window.localStorage.getItem(identifier)) {
+ var referenceList = JSON.parse(window.localStorage.getItem(identifier));
+ var index = _.pluck(referenceList, '@').indexOf(json['@']);
+ if (index !== -1) {
+ referenceList[index] = json;
+ } else {
+ referenceList.push(json);
+ }
+ window.localStorage.setItem(identifier, JSON.stringify(referenceList));
+ return;
+ }
+ window.localStorage.setItem(identifier, JSON.stringify([json]));
+ },
+
+ _checkLocal: function (model) {
+ if (!this.options.localStorage) {
+ return false;
+ }
+
+ var local = window.localStorage.getItem(model.getSubjectUri());
+ if (!local) {
+ return false;
+ }
+
+ return true;
+ },
+
+ hasLocal: function (model) {
+ if (!this.options.localStorage) {
+ return false;
+ }
+
+ if (!window.localStorage.getItem(model.getSubjectUri())) {
+ return false;
+ }
+ return true;
+ },
+
+ readLocal: function (model) {
+ if (!this.options.localStorage) {
+ return;
+ }
+
+ var local = window.localStorage.getItem(model.getSubjectUri());
+ if (!local) {
+ return;
+ }
+ if (!model._originalAttributes) {
+ model._originalAttributes = _.clone(model.attributes);
+ }
+ var parsed = JSON.parse(local);
+ var entity = this.vie.entities.addOrUpdate(parsed, {
+ overrideAttributes: true
+ });
+
+ this._trigger('loaded', null, {
+ instance: entity
+ });
+ },
+
+ _readLocalReferences: function (model, property, collection) {
+ if (!this.options.localStorage) {
+ return;
+ }
+
+ var identifier = this._getReferenceId(model, property);
+ var local = window.localStorage.getItem(identifier);
+ if (!local) {
+ return;
+ }
+ collection.add(JSON.parse(local));
+ },
+
+ revertChanges: function (model) {
+ var widget = this;
+
+ // Remove unsaved collection members
+ if (!model) { return; }
+ _.each(model.attributes, function (attributeValue, property) {
+ if (attributeValue instanceof widget.vie.Collection) {
+ var removables = [];
+ attributeValue.forEach(function (model) {
+ if (model.isNew()) {
+ removables.push(model);
+ }
+ });
+ attributeValue.remove(removables);
+ }
+ });
+
+ // Restore original object properties
+ if (!model.changedAttributes()) {
+ if (model._originalAttributes) {
+ model.set(model._originalAttributes);
+ }
+ return;
+ }
+
+ model.set(model.previousAttributes());
+ },
+
+ _removeLocal: function (model) {
+ if (!this.localStorage) {
+ return;
+ }
+
+ window.localStorage.removeItem(model.getSubjectUri());
+ }
+});
+
+})(jQuery, Drupal);
diff --git a/core/modules/edit/js/viejs/EditService.js b/core/modules/edit/js/viejs/EditService.js
deleted file mode 100644
index 00cb04b..0000000
--- a/core/modules/edit/js/viejs/EditService.js
+++ /dev/null
@@ -1,289 +0,0 @@
-/**
- * @file
- * VIE DOM parsing service for Edit.
- */
-(function(jQuery, _, VIE, Drupal, drupalSettings) {
-
-"use strict";
-
- VIE.prototype.EditService = function (options) {
- var defaults = {
- name: 'edit',
- subjectSelector: '.edit-field.edit-allowed'
- };
- this.options = _.extend({}, defaults, options);
-
- this.views = [];
- this.vie = null;
- this.name = this.options.name;
- };
-
- VIE.prototype.EditService.prototype = {
- load: function (loadable) {
- var correct = loadable instanceof this.vie.Loadable;
- if (!correct) {
- throw new Error('Invalid Loadable passed');
- }
-
- var element;
- if (!loadable.options.element) {
- if (typeof document === 'undefined') {
- return loadable.resolve([]);
- } else {
- element = drupalSettings.edit.context;
- }
- } else {
- element = loadable.options.element;
- }
-
- var entities = this.readEntities(element);
- loadable.resolve(entities);
- },
-
- _getViewForElement:function (element, collectionView) {
- var viewInstance;
-
- jQuery.each(this.views, function () {
- if (jQuery(this.el).get(0) === element.get(0)) {
- if (collectionView && !this.template) {
- return true;
- }
- viewInstance = this;
- return false;
- }
- });
- return viewInstance;
- },
-
- _registerEntityView:function (entity, element, isNew) {
- if (!element.length) {
- return;
- }
-
- // Let's only have this overhead for direct types. Form-based editors are
- // handled in backbone.drupalform.js and the PropertyEditor instance.
- if (jQuery(element).hasClass('edit-type-form')) {
- return;
- }
-
- var service = this;
- var viewInstance = this._getViewForElement(element);
- if (viewInstance) {
- return viewInstance;
- }
-
- viewInstance = new this.vie.view.Entity({
- model:entity,
- el:element,
- tagName:element.get(0).nodeName,
- vie:this.vie,
- service:this.name
- });
-
- this.views.push(viewInstance);
-
- return viewInstance;
- },
-
- save: function(saveable) {
- var correct = saveable instanceof this.vie.Savable;
- if (!correct) {
- throw "Invalid Savable passed";
- }
-
- if (!saveable.options.element) {
- // FIXME: we could find element based on subject
- throw "Unable to write entity to edit.module-markup, no element given";
- }
-
- if (!saveable.options.entity) {
- throw "Unable to write to edit.module-markup, no entity given";
- }
-
- var $element = jQuery(saveable.options.element);
- this._writeEntity(saveable.options.entity, saveable.options.element);
- saveable.resolve();
- },
-
- _writeEntity:function (entity, element) {
- var service = this;
- this.findPredicateElements(this.getElementSubject(element), element, true).each(function () {
- var predicateElement = jQuery(this);
- var predicate = service.getElementPredicate(predicateElement);
- if (!entity.has(predicate)) {
- return true;
- }
-
- var value = entity.get(predicate);
- if (value && value.isCollection) {
- // Handled by CollectionViews separately
- return true;
- }
- if (value === service.readElementValue(predicate, predicateElement)) {
- return true;
- }
- // Unlike in the VIE's RdfaService no (re-)mapping needed here.
- predicateElement.html(value);
- });
- return true;
- },
-
- // The edit-id data attribute contains the full identifier of
- // each entity element in the format
- // `::::`.
- _getID: function (element) {
- var id = jQuery(element).attr('data-edit-id');
- if (!id) {
- id = jQuery(element).closest('[data-edit-id]').attr('data-edit-id');
- }
- return id;
- },
-
- // Returns the "URI" of an entity of an element in format
- // `/`.
- getElementSubject: function (element) {
- return this._getID(element).split('/').slice(0, 2).join('/');
- },
-
- // Returns the field name for an element in format
- // `//`.
- // (Slashes instead of colons because the field name is no namespace.)
- getElementPredicate: function (element) {
- if (!this._getID(element)) {
- throw new Error('Could not find predicate for element');
- }
- return this._getID(element).split('/').slice(2, 5).join('/');
- },
-
- getElementType: function (element) {
- return this._getID(element).split('/').slice(0, 1)[0];
- },
-
- // Reads all editable entities (currently each Drupal field is considered an
- // entity, in the future Drupal entities should be mapped to VIE entities)
- // from DOM and returns the VIE enties it found.
- readEntities: function (element) {
- var service = this;
- var entities = [];
- var entityElements = jQuery(this.options.subjectSelector, element);
- entityElements = entityElements.add(jQuery(element).filter(this.options.subjectSelector));
- entityElements.each(function () {
- var entity = service._readEntity(jQuery(this));
- if (entity) {
- entities.push(entity);
- }
- });
- return entities;
- },
-
- // Returns a filled VIE Entity instance for a DOM element. The Entity
- // is also registered in the VIE entities collection.
- _readEntity: function (element) {
- var subject = this.getElementSubject(element);
- var type = this.getElementType(element);
- var entity = this._readEntityPredicates(subject, element, false);
- if (jQuery.isEmptyObject(entity)) {
- return null;
- }
- entity['@subject'] = subject;
- if (type) {
- entity['@type'] = this._registerType(type, element);
- }
-
- var entityInstance = new this.vie.Entity(entity);
- entityInstance = this.vie.entities.addOrUpdate(entityInstance, {
- updateOptions: {
- silent: true,
- ignoreChanges: true
- }
- });
-
- this._registerEntityView(entityInstance, element);
- return entityInstance;
- },
-
- _registerType: function (typeId, element) {
- typeId = '';
- var type = this.vie.types.get(typeId);
- if (!type) {
- this.vie.types.add(typeId, []);
- type = this.vie.types.get(typeId);
- }
-
- var predicate = this.getElementPredicate(element);
- if (type.attributes.get(predicate)) {
- return type;
- }
- var range = predicate.split('/')[0];
- type.attributes.add(predicate, [range], 0, 1, {
- label: element.data('edit-field-label')
- });
-
- return type;
- },
-
- _readEntityPredicates: function (subject, element, emptyValues) {
- var entityPredicates = {};
- var service = this;
- this.findPredicateElements(subject, element, true).each(function () {
- var predicateElement = jQuery(this);
- var predicate = service.getElementPredicate(predicateElement);
- if (!predicate) {
- return;
- }
- var value = service.readElementValue(predicate, predicateElement);
- if (value === null && !emptyValues) {
- return;
- }
-
- entityPredicates[predicate] = value;
- entityPredicates[predicate + '/rendered'] = predicateElement[0].outerHTML;
- });
- return entityPredicates;
- },
-
- readElementValue : function(predicate, element) {
- // Unlike in RdfaService there is parsing needed here.
- if (element.hasClass('edit-type-form')) {
- return undefined;
- }
- else {
- return jQuery.trim(element.html());
- }
- },
-
- // Subject elements are the DOM elements containing a single or multiple
- // editable fields.
- findSubjectElements: function (element) {
- if (!element) {
- element = drupalSettings.edit.context;
- }
- return jQuery(this.options.subjectSelector, element);
- },
-
- // Predicate Elements are the actual DOM elements that users will be able
- // to edit.
- findPredicateElements: function (subject, element, allowNestedPredicates, stop) {
- var predicates = jQuery();
- // Make sure that element is wrapped by jQuery.
- var $element = jQuery(element);
-
- // Form-type predicates
- predicates = predicates.add($element.filter('.edit-type-form'));
-
- // Direct-type predicates
- var direct = $element.filter('.edit-type-direct');
- predicates = predicates.add(direct.find('.field-item'));
-
- if (!predicates.length && !stop) {
- var parentElement = $element.parent(this.options.subjectSelector);
- if (parentElement.length) {
- return this.findPredicateElements(subject, parentElement, allowNestedPredicates, true);
- }
- }
-
- return predicates;
- }
- };
-
-})(jQuery, _, VIE, Drupal, drupalSettings);
diff --git a/core/modules/edit/js/views/contextuallink-view.js b/core/modules/edit/js/views/contextuallink-view.js
index efe8ddd..76ef876 100644
--- a/core/modules/edit/js/views/contextuallink-view.js
+++ b/core/modules/edit/js/views/contextuallink-view.js
@@ -6,14 +6,12 @@
"use strict";
-Drupal.edit = Drupal.edit || {};
-Drupal.edit.views = Drupal.edit.views || {};
-Drupal.edit.views.ContextualLinkView = Backbone.View.extend({
+Drupal.edit.ContextualLinkView = Backbone.View.extend({
entity: null,
events: {
- 'click': 'onClick'
+ 'click .quick-edit a': 'onClick'
},
/**
@@ -21,19 +19,26 @@ Drupal.edit.views.ContextualLinkView = Backbone.View.extend({
*
* @param options
* An object with the following keys:
- * - entity: the entity ID (e.g. node/1) of the entity
+ * - appModel: the application state model
+ * - strings: the strings for the "Quick edit" link
*/
initialize: function (options) {
- this.entity = options.entity;
+ this.appModel = options.appModel;
+ 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();
- // Re-render whenever the app state's active entity changes.
- this.model.on('change:activeEntity', this.render, this);
+ // Re-render whenever this entity's isActive attribute changes.
+ this.model.on('change:isActive', this.render, this);
// Hide the contextual links whenever an in-place editor is active.
- this.model.on('change:activeEditor', this.toggleContextualLinksVisibility, this);
+ this.appModel.on('change:activeEditor', this.toggleContextualLinksVisibility, this);
},
/**
@@ -45,50 +50,15 @@ Drupal.edit.views.ContextualLinkView = Backbone.View.extend({
onClick: function (event) {
event.preventDefault();
- var that = this;
- var updateActiveEntity = function() {
- // The active entity is the current entity, i.e. stop editing the current
- // entity.
- if (that.model.get('activeEntity') === that.entity) {
- that.model.set('activeEntity', null);
- }
- // The active entity is different from the current entity, i.e. start
- // editing this entity instead of the previous one.
- else {
- that.model.set('activeEntity', that.entity);
- }
- };
-
- // If there's an active editor, attempt to set its state to 'candidate', and
- // only then do what the user asked.
- // (Only when all PropertyEditor widgets of an entity are in the 'candidate'
- // state, it is possible to stop editing it.)
- var activeEditor = this.model.get('activeEditor');
- if (activeEditor) {
- var editableEntity = activeEditor.options.widget;
- var predicate = activeEditor.options.property;
- editableEntity.setState('candidate', predicate, { reason: 'stop or switch' }, function(accepted) {
- if (accepted) {
- updateActiveEntity();
- }
- else {
- // No change.
- }
- });
- }
- // Otherwise, we can immediately do what the user asked.
- else {
- updateActiveEntity();
- }
+ this.model.set('isActive', !this.model.get('isActive'));
},
/**
* Render the "Quick edit" contextual link.
*/
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 + '');
+ var isActive = this.model.get('isActive');
+ this.$el.find('.quick-edit a').text((!isActive) ? this.strings.quickEdit : this.strings.stopQuickEdit);
return this;
},
diff --git a/core/modules/edit/js/views/modal-view.js b/core/modules/edit/js/views/modal-view.js
index b98c876..51ec5ce 100644
--- a/core/modules/edit/js/views/modal-view.js
+++ b/core/modules/edit/js/views/modal-view.js
@@ -6,9 +6,7 @@
"use strict";
-Drupal.edit = Drupal.edit || {};
-Drupal.edit.views = Drupal.edit.views || {};
-Drupal.edit.views.ModalView = Backbone.View.extend({
+Drupal.edit.ModalView = Backbone.View.extend({
message: null,
buttons: null,
diff --git a/core/modules/edit/js/views/propertyeditordecoration-view.js b/core/modules/edit/js/views/propertyeditordecoration-view.js
index bee33d1..91d544e 100644
--- a/core/modules/edit/js/views/propertyeditordecoration-view.js
+++ b/core/modules/edit/js/views/propertyeditordecoration-view.js
@@ -8,10 +8,7 @@
"use strict";
-Drupal.edit = Drupal.edit || {};
-Drupal.edit.views = Drupal.edit.views || {};
-Drupal.edit.views.PropertyEditorDecorationView = Backbone.View.extend({
-
+Drupal.edit.PropertyEditorDecorationView = Backbone.View.extend({
toolbarId: null,
_widthAttributeIsEmpty: null,
@@ -37,20 +34,18 @@ Drupal.edit.views.PropertyEditorDecorationView = Backbone.View.extend({
* - toolbarId: the ID attribute of the toolbar as rendered in the DOM.
*/
initialize: function(options) {
+ this.model.on('change:state', this.stateChange, this);
+
this.editor = options.editor;
this.toolbarId = options.toolbarId;
-
- this.predicate = this.editor.options.property;
- this.editorName = this.editor.options.editorName;
-
- // Only start listening to events as soon as we're no longer in the 'inactive' state.
- this.undelegateEvents();
},
/**
* Listens to editor state changes.
*/
- stateChange: function(from, to) {
+ stateChange: function(model, state) {
+ var from = model.previous('state');
+ var to = state;
switch (to) {
case 'inactive':
if (from !== null) {
@@ -108,8 +103,7 @@ Drupal.edit.views.PropertyEditorDecorationView = Backbone.View.extend({
onMouseEnter: function(event) {
var that = this;
this._ignoreHoveringVia(event, '#' + this.toolbarId, function () {
- var editableEntity = that.editor.options.widget;
- editableEntity.setState('highlighted', that.predicate);
+ that.model.set('state', 'highlighted');
event.stopPropagation();
});
},
@@ -122,8 +116,7 @@ Drupal.edit.views.PropertyEditorDecorationView = Backbone.View.extend({
onMouseLeave: function(event) {
var that = this;
this._ignoreHoveringVia(event, '#' + this.toolbarId, function () {
- var editableEntity = that.editor.options.widget;
- editableEntity.setState('candidate', that.predicate, { reason: 'mouseleave' });
+ that.model.set('state', 'candidate', { reason: 'mouseleave' });
event.stopPropagation();
});
},
@@ -134,8 +127,7 @@ Drupal.edit.views.PropertyEditorDecorationView = Backbone.View.extend({
* @param event
*/
onClick: function(event) {
- var editableEntity = this.editor.options.widget;
- editableEntity.setState('activating', this.predicate);
+ this.model.set('state', 'activating');
event.preventDefault();
event.stopPropagation();
},
@@ -148,7 +140,6 @@ Drupal.edit.views.PropertyEditorDecorationView = Backbone.View.extend({
undecorate: function () {
this.$el
.removeClass('edit-candidate edit-editable edit-highlighted edit-editing');
- this.undelegateEvents();
},
startHighlight: function () {
diff --git a/core/modules/edit/js/views/toolbar-view.js b/core/modules/edit/js/views/toolbar-view.js
index f4b2123..359ad81 100644
--- a/core/modules/edit/js/views/toolbar-view.js
+++ b/core/modules/edit/js/views/toolbar-view.js
@@ -9,16 +9,8 @@
"use strict";
-Drupal.edit = Drupal.edit || {};
-Drupal.edit.views = Drupal.edit.views || {};
-Drupal.edit.views.ToolbarView = Backbone.View.extend({
-
+Drupal.edit.ToolbarView = Backbone.View.extend({
editor: null,
- $storageWidgetEl: null,
-
- entity: null,
- predicate : null,
- editorName: null,
_loader: null,
_loaderVisibleStart: 0,
@@ -34,41 +26,56 @@ Drupal.edit.views.ToolbarView = Backbone.View.extend({
/**
* Implements Backbone Views' initialize() function.
- *
- * @param options
- * An object with the following keys:
- * - editor: the editor object with an 'options' object that has these keys:
- * * entity: the VIE entity for the property.
- * * property: the predicate of the property.
- * * editorName: the editor name.
- * * element: the jQuery-wrapped editor DOM element
- * - $storageWidgetEl: the DOM element on which the Create Storage widget is
- * initialized.
*/
initialize: function(options) {
this.editor = options.editor;
- this.$storageWidgetEl = options.$storageWidgetEl;
-
- this.entity = this.editor.options.entity;
- this.predicate = this.editor.options.property;
- this.editorName = this.editor.options.editorName;
this._loader = null;
this._loaderVisibleStart = 0;
- // Generate a DOM-compatible ID for the toolbar DOM element.
- this._id = Drupal.edit.util.calcPropertyID(this.entity, this.predicate).replace(/\//g, '_');
+ // Generate a DOM-compatible ID for the form container DOM element.
+ this.elementId = 'edit-toolbar-for-' + this.model.get('editID').replace(/\//g, '_');
+
+ this.model.on('change:state', this.stateChange, this);
+ },
+
+ /**
+ * Renders the Toolbar's markup into the DOM.
+ *
+ * Note: depending on whether the 'display' property of the $el for which a
+ * toolbar is being inserted into the DOM, it will be inserted differently.
+ */
+ render: function () {
+ // Render toolbar.
+ this.setElement($(Drupal.theme('editToolbarContainer', {
+ id: this.elementId
+ })));
+
+ // Insert in DOM.
+ var $fieldElement = this.editor.$el;
+ if ($fieldElement.css('display') === 'inline') {
+ this.$el.prependTo($fieldElement.offsetParent());
+ var pos = $fieldElement.position();
+ this.$el.css('left', pos.left).css('top', pos.top);
+ }
+ else {
+ this.$el.insertBefore($fieldElement);
+ }
+
+ return this;
},
/**
* Listens to editor state changes.
*/
- stateChange: function(from, to) {
+ stateChange: function(model, state) {
+ var from = model.previous('state');
+ var to = state;
switch (to) {
case 'inactive':
if (from) {
this.remove();
- if (this.editorName !== 'form') {
+ if (this.model.get('editor') !== 'form') {
Backbone.syncDirectCleanUp();
}
}
@@ -78,7 +85,7 @@ Drupal.edit.views.ToolbarView = Backbone.View.extend({
this.render();
}
else {
- if (this.editorName !== 'form') {
+ if (this.model.get('editor') !== 'form') {
Backbone.syncDirectCleanUp();
}
// Remove all toolgroups; they're no longer necessary.
@@ -119,7 +126,6 @@ Drupal.edit.views.ToolbarView = Backbone.View.extend({
break;
case 'saving':
this.setLoadingIndicator(true);
- this.save();
break;
case 'saved':
this.setLoadingIndicator(false);
@@ -131,76 +137,6 @@ Drupal.edit.views.ToolbarView = Backbone.View.extend({
},
/**
- * Saves a property.
- *
- * This method deals with the complexity of the editor-dependent ways of
- * inserting updated content and showing validation error messages.
- *
- * One might argue that this does not belong in a view. However, there is no
- * actual "save" logic here, that lives in Backbone.sync. This is just some
- * glue code, along with the logic for inserting updated content as well as
- * showing validation error messages, the latter of which is certainly okay.
- */
- save: function() {
- var that = this;
- var editor = this.editor;
- var editableEntity = editor.options.widget;
- var entity = editor.options.entity;
- var predicate = editor.options.property;
-
- // Use Create.js' Storage widget to handle saving. (Uses Backbone.sync.)
- this.$storageWidgetEl.createStorage('saveRemote', entity, {
- editor: editor,
-
- // Successfully saved without validation errors.
- success: function (model) {
- editableEntity.setState('saved', predicate);
-
- // Now that the changes to this property have been saved, the saved
- // attributes are now the "original" attributes.
- entity._originalAttributes = entity._previousAttributes = _.clone(entity.attributes);
-
- // Get data necessary to rerender property before it is unavailable.
- var updatedProperty = entity.get(predicate + '/rendered');
- var $propertyWrapper = editor.element.closest('.edit-field');
- var $context = $propertyWrapper.parent();
-
- editableEntity.setState('candidate', predicate);
- // Unset the property, because it will be parsed again from the DOM, iff
- // its new value causes it to still be rendered.
- entity.unset(predicate, { silent: true });
- entity.unset(predicate + '/rendered', { silent: true });
- // Trigger event to allow for proper clean-up of editor-specific views.
- editor.element.trigger('destroyedPropertyEditor.edit', editor);
-
- // Replace the old content with the new content.
- $propertyWrapper.replaceWith(updatedProperty);
- Drupal.attachBehaviors($context);
- },
-
- // Save attempted but failed due to validation errors.
- error: function (validationErrorMessages) {
- editableEntity.setState('invalid', predicate);
-
- if (that.editorName === 'form') {
- editor.$formContainer
- .find('.edit-form')
- .addClass('edit-validation-error')
- .find('form')
- .prepend(validationErrorMessages);
- }
- else {
- var $errors = $('')
- .append(validationErrorMessages);
- editor.element
- .addClass('edit-validation-error')
- .after($errors);
- }
- }
- });
- },
-
- /**
* When the user clicks the info label, nothing should happen.
* @note currently redirects the click.edit-event to the editor DOM element.
*
@@ -220,9 +156,9 @@ Drupal.edit.views.ToolbarView = Backbone.View.extend({
* @param event
*/
onMouseLeave: function(event) {
- var el = this.editor.element[0];
- if (event.relatedTarget != el && !$.contains(el, event.relatedTarget)) {
- this.editor.element.trigger('mouseleave.edit');
+ var el = this.editor.el;
+ if (event.relatedTarget !== el && !$.contains(el, event.relatedTarget)) {
+ this.editor.$el.trigger('mouseleave.edit');
}
event.stopPropagation();
},
@@ -235,7 +171,7 @@ Drupal.edit.views.ToolbarView = Backbone.View.extend({
onClickSave: function(event) {
event.stopPropagation();
event.preventDefault();
- this.editor.options.widget.setState('saving', this.predicate);
+ this.model.set('state', 'saving');
},
/**
@@ -246,7 +182,7 @@ Drupal.edit.views.ToolbarView = Backbone.View.extend({
onClickClose: function(event) {
event.stopPropagation();
event.preventDefault();
- this.editor.options.widget.setState('candidate', this.predicate, { reason: 'cancel' });
+ this.model.set('state', 'candidate', { reason: 'cancel' });
},
/**
@@ -280,12 +216,8 @@ Drupal.edit.views.ToolbarView = Backbone.View.extend({
},
startHighlight: function() {
- // We get the label to show for this property from VIE's type system.
- var label = this.predicate;
- var attributeDef = this.entity.get('@type').attributes.get(this.predicate);
- if (attributeDef && attributeDef.metadata) {
- label = attributeDef.metadata.label;
- }
+ // Retrieve the lavel to show for this field.
+ var label = this.model.get('label');
this.$el
.addClass('edit-highlighted')
@@ -388,29 +320,6 @@ Drupal.edit.views.ToolbarView = Backbone.View.extend({
},
/**
- * Renders the Toolbar's markup into the DOM.
- *
- * Note: depending on whether the 'display' property of the $el for which a
- * 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);
- }
- },
-
- /**
* Retrieves the ID for this toolbar's container.
*
* Only used to make sane hovering behavior possible.
diff --git a/core/modules/editor/js/editor.createjs.js b/core/modules/editor/js/editor.createjs.js
index de15c77..752e0b2 100644
--- a/core/modules/editor/js/editor.createjs.js
+++ b/core/modules/editor/js/editor.createjs.js
@@ -12,13 +12,11 @@
* - Drupal.editors.magical.onChange()
* - Drupal.editors.magical.detach()
*/
-(function (jQuery, Drupal, drupalSettings) {
+(function ($, Drupal, drupalSettings) {
"use strict";
-// @todo D8: use jQuery UI Widget bridging.
-// @see http://drupal.org/node/1874934#comment-7124904
-jQuery.widget('Midgard.editor', jQuery.Midgard.direct, {
+Drupal.edit.editors.editor = Backbone.View.extend({
textFormat: null,
textFormatHasTransformations: null,
@@ -32,29 +30,26 @@ jQuery.widget('Midgard.editor', jQuery.Midgard.direct, {
},
/**
- * Implements jQuery.widget._init.
- *
- * @todo D8: Remove this.
- * @see http://drupal.org/node/1874934
- */
- _init: function () {},
-
- /**
* Implements Create.editWidget._initialize.
*/
- _initialize: function () {
- var propertyID = Drupal.edit.util.calcPropertyID(this.options.entity, this.options.property);
- var metadata = Drupal.edit.metadataCache[propertyID].custom;
+ initialize: function () {
+ // this.toolbarView = options.toolbarView;
+ var editID = this.model.get('editID');
+ var metadata = Drupal.edit.metadataCache[editID].custom;
this.textFormat = drupalSettings.editor.formats[metadata.format];
this.textFormatHasTransformations = metadata.formatHasTransformations;
this.textEditor = Drupal.editors[this.textFormat.editor];
+
+ this.model.on('change:state', this.stateChange, this);
},
/**
* Implements Create.editWidget.stateChange.
*/
- stateChange: function (from, to) {
+ stateChange: function(model, state) {
+ var from = model.previous('state');
+ var to = state;
var that = this;
switch (to) {
case 'inactive':
@@ -76,29 +71,32 @@ jQuery.widget('Midgard.editor', jQuery.Midgard.direct, {
// text of this field, then we'll need to load a re-processed version of
// it without the transformation filters.
if (this.textFormatHasTransformations) {
- var propertyID = Drupal.edit.util.calcPropertyID(this.options.entity, this.options.property);
- this._getUntransformedText(propertyID, this.element, function (untransformedText) {
+ var editID = this.model.get('editID');
+ this._getUntransformedText(editID, this.$el, function (untransformedText) {
+ debugger;
that.element.html(untransformedText);
- that.options.activated();
+ that.model.set('state', 'active');
});
}
// When no transformation filters have been applied: start WYSIWYG
// editing immediately!
else {
- this.options.activated();
+ this.model.set('state', 'active');
}
break;
case 'active':
this.textEditor.attachInlineEditor(
- this.element.get(0),
+ this.$el,
this.textFormat,
this.toolbarView.getMainWysiwygToolgroupId(),
this.toolbarView.getFloatedWysiwygToolgroupId()
);
// Set the state to 'changed' whenever the content has changed.
this.textEditor.onChange(this.element.get(0), function (html) {
- that.options.changed(html);
+ that.setState('changed');
+
+ // @todo pass modified HTML somewhere?!
});
break;
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index eee53b1..4185d43 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -2108,20 +2108,6 @@ function system_library_info() {
),
);
- // Create.
- $libraries['create.editonly'] = array(
- 'title' => 'Create.js edit-only (editing features only)',
- 'website' => 'http://backbonejs.org/',
- 'version' => '1.0.0-dev',
- 'js' => array(
- 'core/misc/create/create-editonly.js' => array('group' => JS_LIBRARY),
- ),
- 'dependencies' => array(
- array('system', 'vie.core'),
- array('system', 'jquery.ui.widget'),
- ),
- );
-
// Cookie.
$libraries['jquery.cookie'] = array(
'title' => 'Cookie',