Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge 51b59c8 into 9ae7f30
Browse files Browse the repository at this point in the history
  • Loading branch information
jodator committed May 8, 2019
2 parents 9ae7f30 + 51b59c8 commit 3e6524b
Show file tree
Hide file tree
Showing 5 changed files with 311 additions and 12 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@ckeditor/ckeditor5-font": "^11.1.0",
"@ckeditor/ckeditor5-paragraph": "^11.0.1",
"@ckeditor/ckeditor5-undo": "^11.0.1",
"@ckeditor/ckeditor5-widget": "^11.0.1",
"eslint": "^5.5.0",
"eslint-config-ckeditor5": "^1.0.11",
"husky": "^1.3.1",
Expand Down
32 changes: 27 additions & 5 deletions src/mentionui.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import clickOutsideHandler from '@ckeditor/ckeditor5-ui/src/bindings/clickoutsid
import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect';
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
import env from '@ckeditor/ckeditor5-utils/src/env';
import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon';

import TextWatcher from './textwatcher';
Expand Down Expand Up @@ -533,15 +534,36 @@ function getBalloonPanelPositions( preferredPosition ) {
];
}

// Creates a regex pattern for the marker.
// Creates a regex for marker.
//
// @param {String} marker
// @param {Number} minimumCharacters
// @returns {String}
function createPattern( marker, minimumCharacters ) {
function createRegExp( marker, minimumCharacters ) {
const numberOfCharacters = minimumCharacters == 0 ? '*' : `{${ minimumCharacters },}`;

return `(^| )(\\${ marker })([_a-zA-Z0-9À-ž]${ numberOfCharacters }?)$`;
if ( !env.isEdge ) {
// Unfortunately Edge does not throw on `/[\p{Ps}]/u` as it does on `/\p{Ps}/u (no square brackets in latter).
try {
// Uses the ES2018 syntax. See ckeditor5-mention#44.
return new RegExp( buildPattern( '\\p{Ps}"\'', marker, numberOfCharacters ), 'u' );
} catch ( error ) {
// It's OK we're fallback to non ES2018 RegExp later.
}
}

// ES2018 RegExp Unicode property escapes are not supported - fallback to save character list.
return new RegExp( buildPattern( '\\(\\[{"\'', marker, numberOfCharacters ), 'u' );
}

// Creates a regex pattern for the marker.
//
// @param {String} whitelistedCharacters
// @param {String} marker
// @param {Number} minimumCharacters
// @returns {String}
function buildPattern( whitelistedCharacters, marker, numberOfCharacters ) {
return `(^|[ ${ whitelistedCharacters }])([${ marker }])([_a-zA-Z0-9À-ž]${ numberOfCharacters }?)$`;
}

// Creates a test callback for the marker to be used in the text watcher instance.
Expand All @@ -550,7 +572,7 @@ function createPattern( marker, minimumCharacters ) {
// @param {Number} minimumCharacters
// @returns {Function}
function createTestCallback( marker, minimumCharacters ) {
const regExp = new RegExp( createPattern( marker, minimumCharacters ) );
const regExp = createRegExp( marker, minimumCharacters );

return text => regExp.test( text );
}
Expand All @@ -560,7 +582,7 @@ function createTestCallback( marker, minimumCharacters ) {
// @param {String} marker
// @returns {Function}
function createTextMatcher( marker ) {
const regExp = new RegExp( createPattern( marker, 0 ) );
const regExp = createRegExp( marker, 0 );

return text => {
const match = text.match( regExp );
Expand Down
18 changes: 14 additions & 4 deletions src/textwatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,17 @@ export default class TextWatcher {
*/
_getText() {
const editor = this.editor;
const selection = editor.model.document.selection;
const model = editor.model;
const selection = model.document.selection;

// Do nothing if the selection is not collapsed.
if ( !selection.isCollapsed ) {
return;
}

const block = selection.focus.parent;
const rangeBeforeSelection = model.createRange( model.createPositionAt( selection.focus.parent, 0 ), selection.focus );

return _getText( editor.model.createRangeIn( block ) ).slice( 0, selection.focus.offset );
return _getText( rangeBeforeSelection );
}
}

Expand All @@ -135,7 +136,16 @@ export default class TextWatcher {
* @returns {String}
*/
export function _getText( range ) {
return Array.from( range.getItems() ).reduce( ( a, b ) => a + b.data, '' );
const rangeText = Array.from( range.getItems() ).reduce( ( rangeText, node ) => {
if ( node.is( 'softBreak' ) ) {
// Trim text to softBreak
return '';
}

return rangeText + node.data;
}, '' );

return rangeText;
}

mix( TextWatcher, EmitterMixin );
Expand Down
97 changes: 95 additions & 2 deletions tests/manual/mention.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,109 @@ import Mention from '../../src/mention';
import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline';
import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
import Font from '@ckeditor/ckeditor5-font/src/font';
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';

import { toWidget, viewToModelPositionOutsideModelElement } from '@ckeditor/ckeditor5-widget/src/utils';
import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';

class InlineWidget extends Plugin {
constructor( editor ) {
super( editor );

editor.model.schema.register( 'placeholder', {
allowWhere: '$text',
isObject: true,
isInline: true,
allowAttributes: [ 'type' ]
} );

editor.conversion.for( 'editingDowncast' ).elementToElement( {
model: 'placeholder',
view: ( modelItem, viewWriter ) => {
const widgetElement = createPlaceholderView( modelItem, viewWriter );

return toWidget( widgetElement, viewWriter );
}
} );

editor.conversion.for( 'dataDowncast' ).elementToElement( {
model: 'placeholder',
view: createPlaceholderView
} );

editor.conversion.for( 'upcast' ).elementToElement( {
view: 'placeholder',
model: ( viewElement, modelWriter ) => {
let type = 'general';

if ( viewElement.childCount ) {
const text = viewElement.getChild( 0 );

if ( text.is( 'text' ) ) {
type = text.data.slice( 1, -1 );
}
}

return modelWriter.createElement( 'placeholder', { type } );
}
} );

editor.editing.mapper.on(
'viewToModelPosition',
viewToModelPositionOutsideModelElement( editor.model, viewElement => viewElement.name == 'placeholder' )
);

this._createToolbarButton();

function createPlaceholderView( modelItem, viewWriter ) {
const widgetElement = viewWriter.createContainerElement( 'placeholder' );
const viewText = viewWriter.createText( '{' + modelItem.getAttribute( 'type' ) + '}' );

viewWriter.insert( viewWriter.createPositionAt( widgetElement, 0 ), viewText );

return widgetElement;
}
}

_createToolbarButton() {
const editor = this.editor;
const t = editor.t;

editor.ui.componentFactory.add( 'placeholder', locale => {
const buttonView = new ButtonView( locale );

buttonView.set( {
label: t( 'Insert placeholder' ),
tooltip: true,
withText: true
} );

this.listenTo( buttonView, 'execute', () => {
const model = editor.model;

model.change( writer => {
const placeholder = writer.createElement( 'placeholder', { type: 'placeholder' } );

model.insertContent( placeholder );

writer.setSelection( placeholder, 'on' );
} );
} );

return buttonView;
} );
}
}

ClassicEditor
.create( global.document.querySelector( '#editor' ), {
plugins: [ ArticlePluginSet, Underline, Font, Mention ],
plugins: [ ArticlePluginSet, Underline, Font, Mention, InlineWidget ],
toolbar: [
'heading',
'|', 'bulletedList', 'numberedList', 'blockQuote',
'|', 'bold', 'italic', 'underline', 'link',
'|', 'fontFamily', 'fontSize', 'fontColor', 'fontBackgroundColor',
'|', 'insertTable',
'|', 'insertTable', 'placeholder',
'|', 'undo', 'redo'
],
image: {
Expand Down

0 comments on commit 3e6524b

Please sign in to comment.