mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 09:35:49 +02:00
Started to rewrite CKEDITOR placeholder plugin for CKEDITOR5.
This commit is contained in:
parent
a33e93826a
commit
eba89cee62
8 changed files with 374 additions and 1 deletions
|
@ -60,6 +60,7 @@ import TableToolbar from '@ckeditor/ckeditor5-table/src/tabletoolbar.js';
|
||||||
import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline.js';
|
import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline.js';
|
||||||
import WordCount from '@ckeditor/ckeditor5-word-count/src/wordcount.js';
|
import WordCount from '@ckeditor/ckeditor5-word-count/src/wordcount.js';
|
||||||
import EditorWatchdog from '@ckeditor/ckeditor5-watchdog/src/editorwatchdog.js';
|
import EditorWatchdog from '@ckeditor/ckeditor5-watchdog/src/editorwatchdog.js';
|
||||||
|
import PartDBLabel from "./plugins/PartDBLabel/PartDBLabel";
|
||||||
|
|
||||||
class Editor extends ClassicEditor {}
|
class Editor extends ClassicEditor {}
|
||||||
|
|
||||||
|
@ -119,13 +120,16 @@ Editor.builtinPlugins = [
|
||||||
TableProperties,
|
TableProperties,
|
||||||
TableToolbar,
|
TableToolbar,
|
||||||
Underline,
|
Underline,
|
||||||
WordCount
|
WordCount,
|
||||||
|
|
||||||
|
PartDBLabel
|
||||||
];
|
];
|
||||||
|
|
||||||
// Editor configuration.
|
// Editor configuration.
|
||||||
Editor.defaultConfig = {
|
Editor.defaultConfig = {
|
||||||
toolbar: {
|
toolbar: {
|
||||||
items: [
|
items: [
|
||||||
|
'partdb_label',
|
||||||
'heading',
|
'heading',
|
||||||
'alignment',
|
'alignment',
|
||||||
'|',
|
'|',
|
||||||
|
|
22
assets/ckeditor/plugins/PartDBLabel/PartDBLabel.css
Normal file
22
assets/ckeditor/plugins/PartDBLabel/PartDBLabel.css
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
.cke_placeholder {
|
||||||
|
background: #ffff00;
|
||||||
|
padding: 4px 2px;
|
||||||
|
outline-offset: -2px;
|
||||||
|
line-height: 1em;
|
||||||
|
margin: 0 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cke_placeholder::selection {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*.cke_overflow_dropdown {
|
||||||
|
overflow: auto;
|
||||||
|
height: 40vh;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/** Make long editor dropdown panels scrollable */
|
||||||
|
.ck-dropdown__panel {
|
||||||
|
overflow: auto;
|
||||||
|
max-height: 40vh;
|
||||||
|
}
|
16
assets/ckeditor/plugins/PartDBLabel/PartDBLabel.js
Normal file
16
assets/ckeditor/plugins/PartDBLabel/PartDBLabel.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import PartDBLabelUI from "./PartDBLabelUI";
|
||||||
|
import PartDBLabelEditing from "./PartDBLabelEditing";
|
||||||
|
|
||||||
|
import "./PartDBLabel.css";
|
||||||
|
|
||||||
|
import Plugin from "@ckeditor/ckeditor5-core/src/plugin";
|
||||||
|
|
||||||
|
export default class PartDBLabel extends Plugin {
|
||||||
|
static get requires() {
|
||||||
|
return [PartDBLabelUI, PartDBLabelEditing];
|
||||||
|
}
|
||||||
|
|
||||||
|
static get pluginName() {
|
||||||
|
return 'PartDBLabel';
|
||||||
|
}
|
||||||
|
}
|
31
assets/ckeditor/plugins/PartDBLabel/PartDBLabelCommand.js
Normal file
31
assets/ckeditor/plugins/PartDBLabel/PartDBLabelCommand.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import Command from '@ckeditor/ckeditor5-core/src/command';
|
||||||
|
|
||||||
|
export default class PartDBLabelCommand extends Command {
|
||||||
|
execute( { value } ) {
|
||||||
|
const editor = this.editor;
|
||||||
|
const selection = editor.model.document.selection;
|
||||||
|
|
||||||
|
editor.model.change( writer => {
|
||||||
|
// Create a <placeholder> elment with the "name" attribute (and all the selection attributes)...
|
||||||
|
const placeholder = writer.createElement( 'partdb_label', {
|
||||||
|
...Object.fromEntries( selection.getAttributes() ),
|
||||||
|
name: value
|
||||||
|
} );
|
||||||
|
|
||||||
|
// ... and insert it into the document.
|
||||||
|
editor.model.insertContent( placeholder );
|
||||||
|
|
||||||
|
// Put the selection on the inserted element.
|
||||||
|
writer.setSelection( placeholder, 'on' );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
const model = this.editor.model;
|
||||||
|
const selection = model.document.selection;
|
||||||
|
|
||||||
|
const isAllowed = model.schema.checkChild( selection.focus.parent, 'partdb_label' );
|
||||||
|
|
||||||
|
this.isEnabled = isAllowed;
|
||||||
|
}
|
||||||
|
}
|
86
assets/ckeditor/plugins/PartDBLabel/PartDBLabelEditing.js
Normal file
86
assets/ckeditor/plugins/PartDBLabel/PartDBLabelEditing.js
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
|
||||||
|
import PartDBLabelCommand from "./PartDBLabelCommand";
|
||||||
|
|
||||||
|
import { toWidget } from '@ckeditor/ckeditor5-widget/src/utils';
|
||||||
|
import Widget from '@ckeditor/ckeditor5-widget/src/widget';
|
||||||
|
|
||||||
|
export default class PartDBLabelEditing extends Plugin {
|
||||||
|
static get requires() { // ADDED
|
||||||
|
return [ Widget ];
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
|
||||||
|
this._defineSchema();
|
||||||
|
this._defineConverters();
|
||||||
|
|
||||||
|
this.editor.commands.add('partdb_label', new PartDBLabelCommand( this.editor ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
_defineSchema() {
|
||||||
|
const schema = this.editor.model.schema;
|
||||||
|
|
||||||
|
schema.register('partdb_label', {
|
||||||
|
// Allow wherever text is allowed:
|
||||||
|
allowWhere: '$text',
|
||||||
|
|
||||||
|
// The placeholder will act as an inline node:
|
||||||
|
isInline: true,
|
||||||
|
|
||||||
|
// The inline widget is self-contained so it cannot be split by the caret and can be selected:
|
||||||
|
isObject: true,
|
||||||
|
|
||||||
|
allowAttributesOf: '$text',
|
||||||
|
|
||||||
|
allowAttributes: [ 'name' ]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_defineConverters() {
|
||||||
|
const conversion = this.editor.conversion;
|
||||||
|
|
||||||
|
conversion.for( 'upcast' ).elementToElement( {
|
||||||
|
view: {
|
||||||
|
name: 'span',
|
||||||
|
classes: [ 'cke_placeholder' ]
|
||||||
|
},
|
||||||
|
model: ( viewElement, { writer: modelWriter } ) => {
|
||||||
|
// Extract the "name" from "{name}".
|
||||||
|
const name = viewElement.getChild( 0 ).data.slice( 1, -1 );
|
||||||
|
|
||||||
|
return modelWriter.createElement( 'label-placeholder', { name } );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
conversion.for( 'editingDowncast' ).elementToElement( {
|
||||||
|
model: 'partdb_label',
|
||||||
|
view: ( modelItem, { writer: viewWriter } ) => {
|
||||||
|
const widgetElement = createPlaceholderView( modelItem, viewWriter );
|
||||||
|
|
||||||
|
// Enable widget handling on a placeholder element inside the editing view.
|
||||||
|
return toWidget( widgetElement, viewWriter );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
conversion.for( 'dataDowncast' ).elementToElement( {
|
||||||
|
model: 'partdb_label',
|
||||||
|
view: ( modelItem, { writer: viewWriter } ) => createPlaceholderView( modelItem, viewWriter )
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Helper method for both downcast converters.
|
||||||
|
function createPlaceholderView( modelItem, viewWriter ) {
|
||||||
|
const name = modelItem.getAttribute( 'name' );
|
||||||
|
|
||||||
|
const placeholderView = viewWriter.createContainerElement( 'span', {
|
||||||
|
class: 'cke_placeholder'
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Insert the placeholder name (as a text).
|
||||||
|
const innerText = viewWriter.createText( name );
|
||||||
|
viewWriter.insert( viewWriter.createPositionAt( placeholderView, 0 ), innerText );
|
||||||
|
|
||||||
|
return placeholderView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
149
assets/ckeditor/plugins/PartDBLabel/PartDBLabelUI.js
Normal file
149
assets/ckeditor/plugins/PartDBLabel/PartDBLabelUI.js
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
|
||||||
|
|
||||||
|
require('./lang/en.js');
|
||||||
|
|
||||||
|
import { addListToDropdown, createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
|
||||||
|
|
||||||
|
import Collection from '@ckeditor/ckeditor5-utils/src/collection';
|
||||||
|
import Model from '@ckeditor/ckeditor5-ui/src/model';
|
||||||
|
|
||||||
|
export default class PartDBLabelUI extends Plugin {
|
||||||
|
init() {
|
||||||
|
const editor = this.editor;
|
||||||
|
const t = editor.t;
|
||||||
|
|
||||||
|
// The "placeholder" dropdown must be registered among the UI components of the editor
|
||||||
|
// to be displayed in the toolbar.
|
||||||
|
editor.ui.componentFactory.add( 'partdb_label', locale => {
|
||||||
|
const dropdownView = createDropdown( locale );
|
||||||
|
|
||||||
|
// Populate the list in the dropdown with items.
|
||||||
|
addListToDropdown( dropdownView, getDropdownItemsDefinitions(t) );
|
||||||
|
|
||||||
|
dropdownView.buttonView.set( {
|
||||||
|
// The t() function helps localize the editor. All strings enclosed in t() can be
|
||||||
|
// translated and change when the language of the editor changes.
|
||||||
|
label: t( 'part_db.label' ),
|
||||||
|
tooltip: true,
|
||||||
|
withText: true
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Disable the placeholder button when the command is disabled.
|
||||||
|
const command = editor.commands.get( 'partdb_label' );
|
||||||
|
dropdownView.bind( 'isEnabled' ).to( command );
|
||||||
|
|
||||||
|
// Execute the command when the dropdown item is clicked (executed).
|
||||||
|
this.listenTo( dropdownView, 'execute', evt => {
|
||||||
|
editor.execute( 'partdb_label', { value: evt.source.commandParam } );
|
||||||
|
editor.editing.view.focus();
|
||||||
|
} );
|
||||||
|
|
||||||
|
return dropdownView;
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PLACEHOLDERS = [
|
||||||
|
{
|
||||||
|
label: 'section.part',
|
||||||
|
entries: [
|
||||||
|
['[[ID]]', 'part.id'],
|
||||||
|
['[[NAME]]', 'part.name'],
|
||||||
|
['[[CATEGORY]]', 'part.category'],
|
||||||
|
['[[CATEGORY_FULL]]', 'part.category_full'],
|
||||||
|
['[[MANUFACTURER]]', 'part.manufacturer'],
|
||||||
|
['[[MANUFACTURER_FULL]]', 'part.manufacturer_full'],
|
||||||
|
['[[FOOTPRINT]]', 'part.footprint'],
|
||||||
|
['[[FOOTPRINT_FULL]]', 'part.footprint'],
|
||||||
|
['[[MASS]]', 'part.mass'],
|
||||||
|
['[[MPN]]', 'part.mpn'],
|
||||||
|
['[[TAGS]]', 'part.tags'],
|
||||||
|
['[[M_STATUS]]', 'part.status'],
|
||||||
|
['[[DESCRIPTION]]', 'part.description'],
|
||||||
|
['[[DESCRIPTION_T]]', 'part.description_t'],
|
||||||
|
['[[COMMENT]]', 'part.comment'],
|
||||||
|
['[[COMMENT_T]]', 'part.comment_t'],
|
||||||
|
['[[LAST_MODIFIED]]', 'part.last_modified'],
|
||||||
|
['[[CREATION_DATE]]', 'part.creation_date'],
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'section.part_lot',
|
||||||
|
entries: [
|
||||||
|
['[[LOT_ID]]', 'lot.id'],
|
||||||
|
['[[LOT_NAME]]', 'lot.name'],
|
||||||
|
['[[LOT_COMMENT]]', 'lot.comment'],
|
||||||
|
['[[EXPIRATION_DATE]]', 'lot.expiration_date'],
|
||||||
|
['[[AMOUNT]]', 'lot.amount'],
|
||||||
|
['[[LOCATION]]', 'lot.location'],
|
||||||
|
['[[LOCATION_FULL]]', 'lot.location_full'],
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'section.storelocation',
|
||||||
|
entries: [
|
||||||
|
['[[ID]]', 'storelocation.id'],
|
||||||
|
['[[NAME]]', 'storelocation.name'],
|
||||||
|
['[[FULL_PATH]]', 'storelocation.full_path'],
|
||||||
|
['[[PARENT]]', 'storelocation.parent_name'],
|
||||||
|
['[[PARENT_FULL_PATH]]', 'storelocation.parent_full_path'],
|
||||||
|
['[[COMMENT]]', 'storelocation.comment'],
|
||||||
|
['[[COMMENT_T]]', 'storelocation.comment_t'],
|
||||||
|
['[[LAST_MODIFIED]]', 'storelocation.last_modified'],
|
||||||
|
['[[CREATION_DATE]]', 'storelocation.creation_date'],
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'section.global',
|
||||||
|
entries: [
|
||||||
|
['[[USERNAME]]', 'global.username'],
|
||||||
|
['[[USERNAME_FULL]]', 'global.username_full'],
|
||||||
|
['[[DATETIME]]', 'global.datetime'],
|
||||||
|
['[[DATE]]', 'global.date'],
|
||||||
|
['[[TIME]]', 'global.time'],
|
||||||
|
['[[INSTALL_NAME]]', 'global.install_name'],
|
||||||
|
['[[TYPE]]', 'global.type']
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
function getDropdownItemsDefinitions(t) {
|
||||||
|
const itemDefinitions = new Collection();
|
||||||
|
|
||||||
|
for ( const group of PLACEHOLDERS) {
|
||||||
|
//Add group header
|
||||||
|
itemDefinitions.add({
|
||||||
|
'type': 'separator',
|
||||||
|
model: new Model( {
|
||||||
|
withText: true,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
itemDefinitions.add({
|
||||||
|
type: 'button',
|
||||||
|
model: new Model( {
|
||||||
|
label: t(group.label),
|
||||||
|
withText: true,
|
||||||
|
isEnabled: false,
|
||||||
|
} )
|
||||||
|
});
|
||||||
|
|
||||||
|
//Add group entries
|
||||||
|
for ( const entry of group.entries) {
|
||||||
|
const definition = {
|
||||||
|
type: 'button',
|
||||||
|
model: new Model( {
|
||||||
|
commandParam: entry[0],
|
||||||
|
label: t(entry[1]),
|
||||||
|
withText: true
|
||||||
|
} ),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add the item definition to the collection.
|
||||||
|
itemDefinitions.add( definition );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemDefinitions;
|
||||||
|
}
|
58
assets/ckeditor/plugins/PartDBLabel/lang/en.js
Normal file
58
assets/ckeditor/plugins/PartDBLabel/lang/en.js
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
// Make sure that the global object is defined. If not, define it.
|
||||||
|
window.CKEDITOR_TRANSLATIONS = window.CKEDITOR_TRANSLATIONS || {};
|
||||||
|
|
||||||
|
// Make sure that the dictionary for Polish translations exist.
|
||||||
|
window.CKEDITOR_TRANSLATIONS[ 'en' ] = window.CKEDITOR_TRANSLATIONS[ 'en' ] || {};
|
||||||
|
window.CKEDITOR_TRANSLATIONS[ 'en' ].dictionary = window.CKEDITOR_TRANSLATIONS[ 'en' ].dictionary || {};
|
||||||
|
|
||||||
|
// Extend the dictionary for Polish translations with your translations:
|
||||||
|
Object.assign( window.CKEDITOR_TRANSLATIONS[ 'en' ].dictionary, {
|
||||||
|
'part_db.title': 'Insert Placeholders',
|
||||||
|
'part_db.label': 'Placeholders',
|
||||||
|
'section.global': 'Globals',
|
||||||
|
'section.part': 'Part',
|
||||||
|
'section.part_lot': 'Part lot',
|
||||||
|
'section.storelocation': 'Storage location',
|
||||||
|
'part.id': 'Database ID',
|
||||||
|
'part.name': 'Part name',
|
||||||
|
'part.category': 'Category',
|
||||||
|
'part.category_full': 'Category (Full path)',
|
||||||
|
'part.manufacturer': 'Manufacturer',
|
||||||
|
'part.manufacturer_full': 'Manufacturer (Full path)',
|
||||||
|
'part.footprint': 'Footprint',
|
||||||
|
'part.footprint_full': 'Footprint (Full path)',
|
||||||
|
'part.mass': 'Mass',
|
||||||
|
'part.tags': 'Tags',
|
||||||
|
'part.mpn': 'Manufacturer Product Number (MPN)',
|
||||||
|
'part.status': 'Manufacturing status',
|
||||||
|
'part.description': 'Description',
|
||||||
|
'part.description_t': 'Description (Text)',
|
||||||
|
'part.comment': 'Comment',
|
||||||
|
'part.comment_t': 'Comment (Text)',
|
||||||
|
'part.last_modified': 'Last modified datetime',
|
||||||
|
'part.creation_date': 'Creation datetime',
|
||||||
|
'global.username': 'Username',
|
||||||
|
'global.username_full': 'Username (including name)',
|
||||||
|
'global.datetime': 'Current datetime',
|
||||||
|
'global.date': 'Current date',
|
||||||
|
'global.time': 'Current time',
|
||||||
|
'global.install_name': 'Instance name',
|
||||||
|
'global.type': 'Target type',
|
||||||
|
'lot.id': 'Lot ID',
|
||||||
|
'lot.name': 'Lot name',
|
||||||
|
'lot.comment': 'Lot comment',
|
||||||
|
'lot.expiration_date': 'Expiration date',
|
||||||
|
'lot.amount': 'Lot amount',
|
||||||
|
'lot.location': 'Storage location',
|
||||||
|
'lot.location_full': 'Storage location (Full path)',
|
||||||
|
|
||||||
|
'storelocation.id': 'Location ID',
|
||||||
|
'storelocation.name': 'Name',
|
||||||
|
'storelocation.full_path': 'Full path',
|
||||||
|
'storelocation.parent_name': 'Parent name',
|
||||||
|
'storelocation.parent_full_path': 'Parent full path',
|
||||||
|
'storelocation.comment': 'Comment',
|
||||||
|
'storelocation.comment_t': 'Comment (Text)',
|
||||||
|
'storelocation.last_modified': 'Last modified datetime',
|
||||||
|
'storelocation.creation_date': 'Createion datetime',
|
||||||
|
} );
|
|
@ -16,6 +16,9 @@ class RichTextEditorType extends AbstractType
|
||||||
|
|
||||||
$resolver->setDefault('mode', 'markdown-full');
|
$resolver->setDefault('mode', 'markdown-full');
|
||||||
$resolver->setAllowedValues('mode', ['html-label', 'markdown-single_line', 'markdown-full']);
|
$resolver->setAllowedValues('mode', ['html-label', 'markdown-single_line', 'markdown-full']);
|
||||||
|
|
||||||
|
$resolver->setDefault('required', false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBlockPrefix()
|
public function getBlockPrefix()
|
||||||
|
@ -34,11 +37,15 @@ class RichTextEditorType extends AbstractType
|
||||||
{
|
{
|
||||||
$tmp = [];
|
$tmp = [];
|
||||||
|
|
||||||
|
//Set novalidate attribute or we will get problems that form can not be submitted as textarea is not focusable
|
||||||
|
$tmp['novalidate'] = 'novalidate';
|
||||||
|
|
||||||
$tmp['data-mode'] = $options['mode'];
|
$tmp['data-mode'] = $options['mode'];
|
||||||
|
|
||||||
//Add our data-controller element to the textarea
|
//Add our data-controller element to the textarea
|
||||||
$tmp['data-controller'] = 'elements--ckeditor';
|
$tmp['data-controller'] = 'elements--ckeditor';
|
||||||
|
|
||||||
|
|
||||||
return $tmp;
|
return $tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue