diff --git a/package.json b/package.json index aeea162..63df697 100644 --- a/package.json +++ b/package.json @@ -67,8 +67,8 @@ "karma-chai": "^0.1.0", "karma-chrome-launcher": "^3.1.0", "karma-coverage": "^2.0.2", - "karma-firefox-launcher": "^1.3.0", "karma-coveralls": "^2.1.0", + "karma-firefox-launcher": "^1.3.0", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "karma-sinon": "^1.0.5", @@ -84,6 +84,7 @@ "react-dom": "^16.8.1", "react-redux": "^7.2.0", "react-rnd": "^10.0.0", + "react-svg-loader": "^3.0.3", "redux": "^4.0.5", "sinon": "^7.2.3", "style-loader": "^1.2.1", diff --git a/src/assets/img/arrow-down.svg b/src/assets/img/arrow-down.svg new file mode 100644 index 0000000..b1cb25b --- /dev/null +++ b/src/assets/img/arrow-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/img/console.svg b/src/assets/img/console.svg new file mode 100644 index 0000000..3d8f1ad --- /dev/null +++ b/src/assets/img/console.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/img/eye.svg b/src/assets/img/eye.svg new file mode 100644 index 0000000..70dc172 --- /dev/null +++ b/src/assets/img/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/img/play.svg b/src/assets/img/play.svg new file mode 100644 index 0000000..27d0b41 --- /dev/null +++ b/src/assets/img/play.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/img/read-only.svg b/src/assets/img/read-only.svg new file mode 100644 index 0000000..8942629 --- /dev/null +++ b/src/assets/img/read-only.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/img/source.svg b/src/assets/img/source.svg new file mode 100644 index 0000000..1c3ebfe --- /dev/null +++ b/src/assets/img/source.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/img/trash.svg b/src/assets/img/trash.svg new file mode 100644 index 0000000..fd49761 --- /dev/null +++ b/src/assets/img/trash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ckeditorinspector.js b/src/ckeditorinspector.js index 0a9d278..3ceee3b 100644 --- a/src/ckeditorinspector.js +++ b/src/ckeditorinspector.js @@ -11,7 +11,10 @@ import { createStore } from 'redux'; import { Provider } from 'react-redux'; import { reducer } from './data/reducer'; -import { setEditors } from './data/actions'; +import { + setEditors, + updateCurrentEditorIsReadOnly +} from './data/actions'; import { updateModelState } from './model/data/actions'; import { updateViewState } from './view/data/actions'; import { updateCommandsState } from './commands/data/actions'; @@ -214,6 +217,9 @@ export default class CKEditorInspector { } store.dispatch( updateViewState() ); + }, + onReadOnlyChange() { + CKEditorInspector._store.dispatch( updateCurrentEditorIsReadOnly() ); } } ); @@ -222,6 +228,7 @@ export default class CKEditorInspector { CKEditorInspector._store = createStore( reducer, { editors: CKEditorInspector._editors, currentEditorName: getFirstEditorName( CKEditorInspector._editors ), + currentEditorGlobals: {}, ui: { isCollapsed: options.isCollapsed } diff --git a/src/commands/commandinspector.js b/src/commands/commandinspector.js index 1674e65..c9fa11f 100644 --- a/src/commands/commandinspector.js +++ b/src/commands/commandinspector.js @@ -12,6 +12,9 @@ import ObjectInspector from '../components/objectinspector'; import Logger from '../logger'; +import ConsoleIcon from '../assets/img/console.svg'; +import PlayIcon from '../assets/img/play.svg'; + class CommandInspector extends Component { constructor( props ) { super( props ); @@ -47,13 +50,13 @@ class CommandInspector extends Component { , ; } } diff --git a/src/components/objectinspector.css b/src/components/objectinspector.css index f0f4008..0d46c5b 100644 --- a/src/components/objectinspector.css +++ b/src/components/objectinspector.css @@ -16,16 +16,22 @@ } & h2 { + display: flex; + align-items: center; padding: 1em; overflow: hidden; text-overflow: ellipsis; - & span { + & > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; - margin-right: 1em; + margin-right: auto; + } + + & > .ck-inspector-button { + margin-left: .5em; } & a { @@ -40,20 +46,23 @@ } & h3 { + display: flex; + align-items: center; font-size: 12px; padding: .4em .7em; & a { color: inherit; font-weight: bold; + margin-right: auto; } & .ck-inspector-button { - display: none; + visibility: hidden; } &:hover .ck-inspector-button { - display: block + visibility: visible; } } diff --git a/src/components/pane.css b/src/components/pane.css index 355573f..1232c68 100644 --- a/src/components/pane.css +++ b/src/components/pane.css @@ -42,18 +42,6 @@ } & .ck-inspector-tree__config { - & + .ck-inspector-tree__config { - &::before { - content: ""; - border-right: 1px solid var(--ck-inspector-color-border); - display: inline-block; - width: 0px; - height: 20px; - margin: 0 1em; - vertical-align: middle; - } - } - & label { margin: 0 .5em; } diff --git a/src/data/actions.js b/src/data/actions.js index c974d9a..0fe81ee 100644 --- a/src/data/actions.js +++ b/src/data/actions.js @@ -8,6 +8,7 @@ export const SET_HEIGHT = 'SET_HEIGHT'; export const SET_SIDE_PANE_WIDTH = 'SET_SIDE_PANE_WIDTH'; export const SET_EDITORS = 'SET_EDITORS'; export const SET_CURRENT_EDITOR_NAME = 'SET_CURRENT_EDITOR_NAME'; +export const UPDATE_CURRENT_EDITOR_IS_READ_ONLY = 'UPDATE_CURRENT_EDITOR_IS_READ_ONLY'; export const SET_ACTIVE_INSPECTOR_TAB = 'SET_ACTIVE_INSPECTOR_TAB'; export function toggleIsCollapsed() { @@ -33,3 +34,7 @@ export function setCurrentEditorName( editorName ) { export function setActiveTab( tabName ) { return { type: SET_ACTIVE_INSPECTOR_TAB, tabName }; } + +export function updateCurrentEditorIsReadOnly() { + return { type: UPDATE_CURRENT_EDITOR_IS_READ_ONLY }; +} diff --git a/src/data/reducer.js b/src/data/reducer.js index 50788a3..58c46bd 100644 --- a/src/data/reducer.js +++ b/src/data/reducer.js @@ -13,10 +13,12 @@ import { SET_SIDE_PANE_WIDTH, SET_EDITORS, SET_CURRENT_EDITOR_NAME, + UPDATE_CURRENT_EDITOR_IS_READ_ONLY, SET_ACTIVE_INSPECTOR_TAB } from './actions'; import { getFirstEditorName } from '../utils'; +import { getCurrentEditor } from './utils'; import LocalStorageManager from '../localstoragemanager'; @@ -28,6 +30,7 @@ export const LOCAL_STORAGE_SIDE_PANE_WIDTH = 'side-pane-width'; export function reducer( state, action ) { const newState = appReducer( state, action ); + newState.currentEditorGlobals = currentEditorGlobalsReducer( newState, newState.currentEditorGlobals, action ); newState.ui = appUIReducer( newState.ui, action ); newState.model = modelReducer( newState, newState.model, action ); newState.view = viewReducer( newState, newState.view, action ); @@ -50,6 +53,18 @@ function appReducer( state, action ) { } } +function currentEditorGlobalsReducer( state, globalsState, action ) { + switch ( action.type ) { + case SET_EDITORS: + case SET_CURRENT_EDITOR_NAME: + return getCurrentEditorGlobalState( state ); + case UPDATE_CURRENT_EDITOR_IS_READ_ONLY: + return getNewCurrentEditorIsReadOnlyState( state, globalsState ); + default: + return globalsState; + } +} + function appUIReducer( UIState, action ) { // This happens when the inspector store is created for the first time only. // Only #isCollapsed is passed then because it's configurable in inspector options. @@ -96,6 +111,24 @@ function getNewCurrentEditorNameState( appState, action ) { }; } +function getCurrentEditorGlobalState( appState ) { + const isReadOnlyState = getNewCurrentEditorIsReadOnlyState( appState, {} ); + + return { + ...isReadOnlyState + }; +} + +function getNewCurrentEditorIsReadOnlyState( appState, globalsState ) { + const editor = getCurrentEditor( appState ); + + return { + ...globalsState, + + isReadOnly: editor ? editor.isReadOnly : false + }; +} + function getNewEditorsState( appState, action ) { const newState = { editors: new Map( action.editors ) diff --git a/src/data/utils.js b/src/data/utils.js index a2581d2..5f17b8b 100644 --- a/src/data/utils.js +++ b/src/data/utils.js @@ -11,10 +11,16 @@ export default class EditorListener { startListening( currentEditor ) { currentEditor.model.document.on( 'change', this._config.onModelChange ); currentEditor.editing.view.on( 'render', this._config.onViewRender ); + currentEditor.on( 'change:isReadOnly', this._config.onReadOnlyChange ); } stopListening( currentEditor ) { currentEditor.model.document.off( 'change', this._config.onModelChange ); currentEditor.editing.view.off( 'render', this._config.onViewRender ); + currentEditor.off( 'change:isReadOnly', this._config.onReadOnlyChange ); } } + +export function getCurrentEditor( globalState ) { + return globalState.editors.get( globalState.currentEditorName ); +} diff --git a/src/editorquickactions.css b/src/editorquickactions.css new file mode 100644 index 0000000..afc17c5 --- /dev/null +++ b/src/editorquickactions.css @@ -0,0 +1,17 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +.ck-inspector .ck-inspector-editor-quick-actions { + display: flex; + align-content: center; + justify-content: center; + align-items: center; + flex-direction: row; + flex-wrap: nowrap; + + & > .ck-inspector-button { + margin-left: .3em; + } +} diff --git a/src/editorquickactions.js b/src/editorquickactions.js new file mode 100644 index 0000000..8511ee9 --- /dev/null +++ b/src/editorquickactions.js @@ -0,0 +1,56 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import React, { Component } from 'react'; +import { connect } from 'react-redux'; + +import Button from './components/button'; + +import SourceIcon from './assets/img/source.svg'; +import ConsoleIcon from './assets/img/console.svg'; +import ReadOnlyIcon from './assets/img/read-only.svg'; +import TrashIcon from './assets/img/trash.svg'; + +import './editorquickactions.css'; + +class EditorQuickActions extends Component { + render() { + return
+
; + } +} + +const mapStateToProps = ( { editors, currentEditorName, currentEditorGlobals: { isReadOnly } } ) => { + const editor = editors.get( currentEditorName ); + + return { editor, isReadOnly }; +}; + +export default connect( mapStateToProps, {} )( EditorQuickActions ); diff --git a/src/model/data/reducer.js b/src/model/data/reducer.js index aafb9ee..ac09113 100644 --- a/src/model/data/reducer.js +++ b/src/model/data/reducer.js @@ -27,6 +27,7 @@ import { } from './utils'; import { isModelRoot } from '../utils'; +import { getCurrentEditor } from '../../data/utils'; import LocalStorageManager from '../../localstoragemanager'; @@ -213,7 +214,3 @@ function getEssentialState( globalState, modelState, modelStateOverrides ) { markers }; } - -function getCurrentEditor( globalState ) { - return globalState.editors.get( globalState.currentEditorName ); -} diff --git a/src/model/markerinspector.js b/src/model/markerinspector.js index 53d2efb..572aa1a 100644 --- a/src/model/markerinspector.js +++ b/src/model/markerinspector.js @@ -13,6 +13,8 @@ import { stringifyPropertyList } from '../components/utils'; import Logger from '../logger'; +import ConsoleIcon from '../assets/img/console.svg'; + const API_DOCS_PREFIX = 'https://ckeditor.com/docs/ckeditor5/latest/api/module_engine_model_markercollection-Marker.html'; class ModelMarkerInspector extends Component { @@ -37,7 +39,7 @@ class ModelMarkerInspector extends Component { , ; + ].join( ' ' )} + />; } componentDidMount() { @@ -162,7 +169,7 @@ export class EditorInstanceSelectorVisual extends Component { return
{this.props.currentEditorName ?