Skip to content

Commit

Permalink
fix(editor3): enable multiple editors (#1522)
Browse files Browse the repository at this point in the history
* fix(editor3): documented attributes and added find-replace-target

When the Editor3 directive is being used, it registers itself with a
service that allows external components to run find & replace operations
on it. Only one editor can be registered at a time, and this flag
enables that operation.

The `data-find-replace-target` attribute registers the editor with the
find & replace service and de-registers it when its scope is destroyed.
Only one editor can be registered at a time.

* feat(editor3): allow editor3 services to override editor2

- Added missing interface methods to the editor3 service, so that it can
replace editor2 when needed. Assures SendItem & Macros work correctly.

- Added config `features.onlyEditor3` which completely disables editor2.

- Added single-line option to the editor, enabled by adding
  `data-single-line` as an attribute (no value needed).

- Added editor3 version for all fields on items.

- Fixed problem with hovering toolbar, so that when an editor goes out
  of view, the toolbar goes away (instead of staying sticky when no
editor is in view anymore).

- Added several CSS fixes to acommodate editor3.

- Made formatting options for special content work. Editor formats
  supported: picture, anchor, embed, table

* feat(editor3): new set of icons

Updates the new set of icons provided by the design team.

* feat(editor3): added editorResolver service
  • Loading branch information
gbbr authored Apr 20, 2017
1 parent 618f229 commit bff38c4
Show file tree
Hide file tree
Showing 33 changed files with 416 additions and 119 deletions.
Binary file modified fonts/sd_icons.eot
Binary file not shown.
61 changes: 38 additions & 23 deletions fonts/sd_icons.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified fonts/sd_icons.ttf
Binary file not shown.
Binary file modified fonts/sd_icons.woff
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export function ArticleEditDirective(
scope.editSignOff = false;
scope.mediaLoading = false;
scope.validator = config.validatorMediaMetadata;
scope.features = config.features;

var mainEditScope = scope.$parent.$parent;
var autopopulateByline = config.features && config.features.autopopulateByline;
Expand Down
6 changes: 4 additions & 2 deletions scripts/apps/authoring/authoring/directives/SendItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import _ from 'lodash';

SendItem.$inject = ['$q', 'api', 'desks', 'notify', 'authoringWorkspace',
'superdeskFlags', '$location', 'macros', '$rootScope',
'authoring', 'send', 'editor', 'confirm', 'archiveService',
'authoring', 'send', 'editorResolver', 'confirm', 'archiveService',
'preferencesService', 'multi', 'datetimeHelper', 'config', 'privileges', 'storage'];
export function SendItem($q, api, desks, notify, authoringWorkspace,
superdeskFlags, $location, macros, $rootScope,
authoring, send, editor, confirm, archiveService,
authoring, send, editorResolver, confirm, archiveService,
preferencesService, multi, datetimeHelper, config, privileges, storage) {
return {
scope: {
Expand All @@ -26,6 +26,8 @@ export function SendItem($q, api, desks, notify, authoringWorkspace,
controllerAs: 'vm',
templateUrl: 'scripts/apps/authoring/views/send-item.html',
link: function sendItemLink(scope, elem, attrs, ctrl) {
const editor = editorResolver.get();

scope.mode = scope.mode || 'authoring';
scope.desks = null;
scope.stages = null;
Expand Down
8 changes: 3 additions & 5 deletions scripts/apps/authoring/editor/find-replace.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,14 @@
* AUTHORS and LICENSE files distributed with this source code, or
* at https://www.sourcefabric.org/apps/license
*/
FindReplaceDirective.$inject = ['editor', 'editor3', 'macros', 'authoring'];
FindReplaceDirective.$inject = ['editorResolver', 'macros'];
/**
* using directive here so that it can return focus
*/
function FindReplaceDirective(editor2, editor3, macros, authoring) {
function FindReplaceDirective(editorResolver, macros) {
return {
link: function(scope, elem) {
// use the editor service of editor3, if it's active
const isEditor3 = authoring.editor.body_html.editor3;
const editor = isEditor3 ? editor3 : editor2;
const editor = editorResolver.get();

scope.to = '';
scope.from = '';
Expand Down
6 changes: 4 additions & 2 deletions scripts/apps/authoring/macros/macros.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,12 +234,14 @@ function MacrosController($scope, macros, desks, autosave, $rootScope, storage)
* apply the results of triggered macro with the use of available set of methods such that next,
* prev and replace
*/
MacrosReplaceDirective.$inject = ['editor'];
function MacrosReplaceDirective(editor) {
MacrosReplaceDirective.$inject = ['editorResolver'];
function MacrosReplaceDirective(editorResolver) {
return {
scope: true,
templateUrl: 'scripts/apps/authoring/macros/views/macros-replace.html',
link: function(scope) {
const editor = editorResolver.get();

scope.diff = null;

scope.$on('macro:diff', (evt, diff) => {
Expand Down
8 changes: 7 additions & 1 deletion scripts/apps/authoring/tests/authoring.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2274,9 +2274,15 @@ describe('authoring themes', () => {

describe('send item directive', () => {
beforeEach(window.module(($provide) => {
$provide.constant('config', {server: {url: undefined}, iframely: {key: '123'}, editor: {}});
$provide.constant('config', {
server: {url: undefined},
iframely: {key: '123'},
editor: {},
features: {onlyEditor3: false}
});
}));

beforeEach(window.module('superdesk.core.editor3'));
beforeEach(window.module('superdesk.apps.editor2'));
beforeEach(window.module('superdesk.core.preferences'));
beforeEach(window.module('superdesk.apps.authoring'));
Expand Down
22 changes: 12 additions & 10 deletions scripts/apps/authoring/views/article-edit.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="field" sd-width="{{editor.headline.sdWidth || 'full'}}" ng-if="schema.headline && !editor.headline.editor3" ng-class="{'limit-error': item.headline.length > schema.headline.maxlength}" order="{{editor.headline.order}}" sd-validation-error="error.headline" data-required="schema.headline.required">
<div class="field" sd-width="{{editor.headline.sdWidth || 'full'}}" ng-if="schema.headline && !(features.onlyEditor3 || editor.body_html.editor3)" ng-class="{'limit-error': item.headline.length > schema.headline.maxlength}" order="{{editor.headline.order}}" sd-validation-error="error.headline" data-required="schema.headline.required">
<label translate>Headline</label>
<span ng-if="schema.headline.maxlength" sd-character-count data-item="item.headline" data-html="true" data-limit="schema.headline.maxlength"></span>
<div id="title" class="headline"
Expand All @@ -14,11 +14,12 @@
<div class="headline" ng-if="!_editable">{{item.headline}}</div>
</div>

<div class="field" sd-width="{{editor.headline.sdWidth || 'full'}}" ng-if="schema.headline && editor.headline.editor3" ng-class="{'limit-error': item.headline.length > schema.headline.maxlength}" order="{{editor.headline.order}}" sd-validation-error="error.headline" data-required="schema.headline.required">
<div class="field" sd-width="{{editor.headline.sdWidth || 'full'}}" ng-if="schema.headline && (features.onlyEditor3 || editor.body_html.editor3)" ng-class="{'limit-error': item.headline.length > schema.headline.maxlength}" order="{{editor.headline.order}}" sd-validation-error="error.headline" data-required="schema.headline.required">
<label translate>Headline</label>
<span ng-if="schema.headline.maxlength" sd-character-count data-item="item.headline" data-html="true" data-limit="schema.headline.maxlength"></span>
<div id="title" class="headline"
sd-editor3
data-single-line
data-editor-format="editor.headline.formatOptions"
data-language="item.language"
data-value="item.headline"
Expand Down Expand Up @@ -73,7 +74,7 @@
data-save="save()"></div>
</div>

<div class="field abstract" sd-width="{{editor.abstract.sdWidth || 'full'}}" ng-class="{'limit-error': item.abstract.length > schema.abstract.maxlength}" ng-if="schema.abstract && !editor.abstract.editor3 && !isMediaType" order="{{editor.abstract.order}}" sd-validation-error="error.abstract" data-required="schema.abstract.required">
<div class="field abstract" sd-width="{{editor.abstract.sdWidth || 'full'}}" ng-class="{'limit-error': item.abstract.length > schema.abstract.maxlength}" ng-if="schema.abstract && !(features.onlyEditor3 || editor.body_html.editor3) && !isMediaType" order="{{editor.abstract.order}}" sd-validation-error="error.abstract" data-required="schema.abstract.required">
<label translate>Abstract</label>
<span sd-character-count
data-item="item.abstract"
Expand All @@ -93,7 +94,7 @@
<div class="abstract" ng-if="!_editable" sd-html-preview="item.abstract"></div>
</div>

<div class="field abstract" sd-width="{{editor.abstract.sdWidth || 'full'}}" ng-class="{'limit-error': item.abstract.length > schema.abstract.maxlength}" ng-if="schema.abstract && editor.abstract.editor3" order="{{editor.abstract.order}}" sd-validation-error="error.abstract" data-required="schema.abstract.required">
<div class="field abstract" sd-width="{{editor.abstract.sdWidth || 'full'}}" ng-class="{'limit-error': item.abstract.length > schema.abstract.maxlength}" ng-if="schema.abstract && (features.onlyEditor3 || editor.body_html.editor3)" order="{{editor.abstract.order}}" sd-validation-error="error.abstract" data-required="schema.abstract.required">
<label translate>Abstract</label>
<span sd-character-count
data-item="item.abstract"
Expand Down Expand Up @@ -179,7 +180,7 @@
</div>
</div>

<div class="field body" ng-if="!isMediaType && schema.body_html && !editor.body_html.editor3" order="{{editor.body_html.order}}" sd-validation-error="error.body_html" data-required="schema.body_html.required" sd-width="{{editor.body_html.sdWidth || 'full'}}">
<div class="field body" ng-if="!isMediaType && schema.body_html && !(features.onlyEditor3 || editor.body_html.editor3)" order="{{editor.body_html.order}}" sd-validation-error="error.body_html" data-required="schema.body_html.required" sd-width="{{editor.body_html.sdWidth || 'full'}}">
<label translate>Body</label>
<span sd-character-count data-item="item.body_html" data-html="true"></span>
<span sd-word-count data-item="item.body_html" data-html="true"></span>
Expand All @@ -198,12 +199,13 @@
<div class="text-editor" sd-html-preview="item.body_html" ng-if="!_editable"></div>
</div>

<div class="field body" ng-if="!isMediaType && schema.body_html && editor.body_html.editor3" order="{{editor.body_html.order}}" sd-validation-error="error.body_html" data-required="schema.body_html.required" sd-width="{{editor.body_html.sdWidth || 'full'}}">
<div class="field body" ng-if="!isMediaType && schema.body_html && (features.onlyEditor3 || editor.body_html.editor3)" order="{{editor.body_html.order}}" sd-validation-error="error.body_html" data-required="schema.body_html.required" sd-width="{{editor.body_html.sdWidth || 'full'}}">
<label translate>Body</label>
<span sd-character-count data-item="item.body_html" data-html="true"></span>
<span sd-word-count data-item="item.body_html" data-html="true"></span>
<div id="bodyhtml"
sd-editor3
data-find-replace-target
data-scroll-container=".page-content-container"
data-editor-format="editor.body_html.formatOptions"
data-editor-state="item.editor_state"
Expand Down Expand Up @@ -241,7 +243,7 @@
</div>
</div>

<div ng-if="isMediaType && !editor.description_text.editor3"
<div ng-if="isMediaType && !(features.onlyEditor3 || editor.body_html.editor3)"
class="field abstract"
order="{{editor.media_description.order}}"
sd-validation-error="error.description_text"
Expand All @@ -261,7 +263,7 @@
<div class="abstract" ng-if="!_editable">{{item.description_text}}</div>
</div>

<div ng-if="isMediaType && editor.description_text.editor3" class="field abstract" order="{{editor.media_description.order}}" sd-validation-error="error.description_text" sd-width="{{editor.media_description.sdWidth || 'full'}}" data-required="schema.media_description.required">
<div ng-if="isMediaType && (features.onlyEditor3 || editor.body_html.editor3)" class="field abstract" order="{{editor.media_description.order}}" sd-validation-error="error.description_text" sd-width="{{editor.media_description.sdWidth || 'full'}}" data-required="schema.media_description.required">
<label translate>Caption</label>
<span ng-if="validator.description_text.maxlength" sd-character-count data-item="item.description_text" data-html="true" data-limit="validator.description_text.maxlength"></span>
<div id="title" class="abstract"
Expand Down Expand Up @@ -294,7 +296,7 @@
</div>

<div class="field"
ng-if="schema.body_footer && !editor.body_footer.editor3 && !isMediaType"
ng-if="schema.body_footer && !(features.onlyEditor3 || editor.body_html.editor3) && !isMediaType"
order="{{editor.body_footer.order}}"
sd-validation-error="error.body_footer"
data-required="schema.body_footer.required"
Expand All @@ -317,7 +319,7 @@
</div>

<div class="field"
ng-if="schema.body_footer && editor.body_footer.editor3"
ng-if="schema.body_footer && (features.onlyEditor3 || editor.body_html.editor3)"
order="{{editor.body_footer.order}}"
sd-validation-error="error.body_footer"
data-required="schema.body_footer.required"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ContentProfilesController.$inject = ['$scope', 'notify', 'content', 'modal', '$q'];
export function ContentProfilesController($scope, notify, content, modal, $q) {
ContentProfilesController.$inject = ['$scope', 'notify', 'content', 'modal', '$q', 'config'];
export function ContentProfilesController($scope, notify, content, modal, $q, config) {
var self = this;

// creating will be true while the modal for creating a new content
Expand All @@ -15,6 +15,8 @@ export function ContentProfilesController($scope, notify, content, modal, $q) {
// can be changed with a button
$scope.active_only = true;

$scope.withEditor3 = config.features.editor3;

/**
* @description Refreshes the list of content profiles by fetching them.
* @returns {Promise}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ContentProfileSchemaEditor.$inject = ['gettext', 'content', 'config'];
export function ContentProfileSchemaEditor(gettext, content, config) {
ContentProfileSchemaEditor.$inject = ['gettext', 'content'];
export function ContentProfileSchemaEditor(gettext, content) {
// labelMap maps schema entry keys to their display names.
const labelMap = {
headline: gettext('Headline'),
Expand Down Expand Up @@ -55,8 +55,6 @@ export function ContentProfileSchemaEditor(gettext, content, config) {

scope.directive = this.name;

scope.withEditor3 = config.features.editor3;

scope.formatingOptions = [
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull',
Expand Down
6 changes: 6 additions & 0 deletions scripts/apps/workspace/content/views/profile-settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ <h3>{{ :: 'Editing' | translate }} "{{ editing.form.label }}"</h3>
<span tooltip="{{ editing.form.enabled ? 'Yes' : 'No' | translate }}" tooltip-placement="right"></span>
<span class="pull-right" sd-switch ng-model="editing.form.enabled"></span>
</label>

<label class="right" ng-if="withEditor3">
{{:: 'Editor3' | translate }}&nbsp;
<span tooltip="{{ editing.form.editor.body_html.editor3 ? 'Yes' : 'No' | translate }}" tooltip-placement="right"></span>
<span class="pull-right" sd-switch ng-model="editing.form.editor.body_html.editor3"></span>
</label>
</div>
<div class="field">
<sd-content-schema-editor ng-model="editing.form"></sd-content-schema-editor>
Expand Down
4 changes: 0 additions & 4 deletions scripts/apps/workspace/content/views/schema-editor.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ <h4 translate>Schema Configuration</h4>
<div class="body" ng-if="model.editor[id].enabled">
<form>
<fieldset class="fieldset-flex">
<div class="field" ng-if="id == 'body_html' && withEditor3">
<label translate>Editor3</label>
<div sd-check ng-model="model.editor[id].editor3"></div>
</div>
<div class="field" ng-if="id == 'dateline'">
<label translate>Hide Date</label>
<div sd-check ng-model="model.editor[id].hideDate"></div>
Expand Down
15 changes: 12 additions & 3 deletions scripts/core/editor3/components/Editor3.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ export class Editor3Component extends React.Component {
return;
}

const toolbarStyle = editorRect.top < pageRect.top + 50 ? 'fixed' : 'relative';
const isToolbarOut = editorRect.top < pageRect.top + 50;
const isBottomOut = editorRect.bottom < pageRect.top + 60;
const toolbarStyle = isToolbarOut && !isBottomOut ? 'fixed' : 'relative';

if (toolbarStyle !== this.state.toolbarStyle) {
this.setState({toolbarStyle});
Expand Down Expand Up @@ -99,7 +101,12 @@ export class Editor3Component extends React.Component {
* @description Handles key commands in the editor.
*/
handleKeyCommand(command) {
const {editorState, onChange} = this.props;
const {editorState, onChange, singleLine} = this.props;

if (singleLine && command === 'split-block') {
return 'handled';
}

const newState = RichUtils.handleKeyCommand(editorState, command);

if (newState) {
Expand Down Expand Up @@ -142,6 +149,7 @@ export class Editor3Component extends React.Component {
let cx = classNames({
'Editor3-root Editor3-editor': true,
'floating-toolbar': toolbarStyle === 'fixed',
'no-toolbar': !showToolbar,
'read-only': readOnly
});

Expand Down Expand Up @@ -174,7 +182,8 @@ Editor3Component.propTypes = {
unsetReadOnly: React.PropTypes.func,
onTab: React.PropTypes.func,
dragDrop: React.PropTypes.func,
scrollContainer: React.PropTypes.object
scrollContainer: React.PropTypes.string,
singleLine: React.PropTypes.bool
};

const mapStateToProps = (state) => ({
Expand Down
2 changes: 1 addition & 1 deletion scripts/core/editor3/components/embeds/EmbedButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class EmbedButton extends Component {

return (
<div className="Editor3-styleButton">
<span onClick={this.showInput}>embed</span>
<span onClick={this.showInput}><i className="icon-code" /></span>
{dialogOpen ? <EmbedInput onCancel={this.hideInput} /> : null}
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion scripts/core/editor3/components/images/ImageButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import * as actions from '../../actions';
*/
const ImageButtonComponent = ({insertImages}) =>
<div className="Editor3-styleButton">
<span onClick={insertImages}>img</span>
<span onClick={insertImages}><i className="icon-picture" /></span>
</div>;

ImageButtonComponent.propTypes = {
Expand Down
4 changes: 3 additions & 1 deletion scripts/core/editor3/components/links/LinkButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ export class LinkButtonComponent extends Component {

return (
<div className="Editor3-styleButton">
<span className={cx} onClick={this.showInput.bind(this, '')}>link</span>
<span className={cx} onClick={this.showInput.bind(this, '')}>
<i className="icon-link" />
</span>

{entityType === 'LINK' ?
<LinkPopover
Expand Down
4 changes: 2 additions & 2 deletions scripts/core/editor3/components/links/LinkPopover.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ export class LinkPopover extends Component {
}

const {editorRect} = this.props;
const top = rect.top - editorRect.top + 52;
const left = rect.left - editorRect.left;
const top = rect.top - editorRect.top + 62;
const left = rect.left - editorRect.left + 20;

this.position = {top, left};
}
Expand Down
2 changes: 1 addition & 1 deletion scripts/core/editor3/components/tables/TableButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class TableButtonComponent extends Component {

return (
<div className="Editor3-styleButton">
<span onClick={addTable}>tbl</span>
<span onClick={addTable}><i className="icon-table" /></span>
</div>
);
}
Expand Down
9 changes: 1 addition & 8 deletions scripts/core/editor3/components/tests/editor3.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,7 @@ describe('editor3.component', () => {
const wrapper = shallow(<Editor3 showToolbar={false} />);

expect(wrapper.find('DraftEditor').length).toBe(1);
expect(wrapper.find('Toolbar').length).toBe(0);
});

it('should show toolbar when enabled', () => {
const wrapper = shallow(<Editor3 showToolbar={true} />);

expect(wrapper.find('DraftEditor').length).toBe(1);
expect(wrapper.find('Toolbar').length).toBe(1);
expect(wrapper.find('.Editor3-controls').length).toBe(0);
});

it('should mount and put client rect inside attribute on update', () => {
Expand Down
2 changes: 0 additions & 2 deletions scripts/core/editor3/components/tests/links.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ function getWrapper(es = null, shallowMount = false) {
describe('editor3.components.link-button', () => {
it('should render button text', () => {
const {wrapper} = getShallowWrapper();
const button = wrapper.find('span').first();

expect(button.text()).toBe('link');
expect(wrapper.find('LinkPopover').length).toBe(0);
});

Expand Down
4 changes: 2 additions & 2 deletions scripts/core/editor3/components/tests/styles.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import {InlineStyleControlsComponent as InlineStyleControls} from '../toolbar/In

describe('editor3.components.toolbar', () => {
it('(StyleButton) should render label', () => {
const wrapper = shallow(<StyleButton label={'button_label'} />);
const wrapper = shallow(<StyleButton label={'h1'} />);

expect(wrapper.hasClass('Editor3-styleButton')).toBe(true);
expect(wrapper.text()).toBe('button_label');
expect(wrapper.find('i.icon-heading-1').length).toBe(1);
});

it('(StyleButton) should become active when prop is set to true', () => {
Expand Down
Loading

0 comments on commit bff38c4

Please sign in to comment.