-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 66aadb4
Showing
14 changed files
with
7,801 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Neos: | ||
Neos: | ||
Ui: | ||
resources: | ||
javascript: | ||
'Psmb.Footnote:Footnote': | ||
resource: resource://Psmb.Footnote/Public/JavaScript/Footnote/Plugin.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
This package provides a footnote plugin for CKeditor5 integraion in Neos CMS. | ||
|
||
## Installation | ||
|
||
1. Switch to using CKeditor 5 | ||
2. `composer require '@psmb/footnote'` | ||
3. Enable footnote button on node properties that should support it, e.g.: | ||
|
||
``` | ||
'Neos.NodeTypes:TextMixin': | ||
properties: | ||
text: | ||
ui: | ||
inline: | ||
editorOptions: | ||
formatting: | ||
footnote: true | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[*] | ||
charset = utf-8 | ||
end_of_line = lf | ||
insert_final_newline = true | ||
trim_trailing_whitespace = true | ||
indent_style = space | ||
indent_size = 4 | ||
|
||
[*.{yml,yaml}] | ||
indent_size = 2 | ||
|
||
[*.{md}] | ||
indent_size = 2 | ||
trim_trailing_whitespace = false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"description": "Footnote", | ||
"license": "MIT", | ||
"private": true, | ||
"scripts": { | ||
"build": "neos-react-scripts build", | ||
"watch": "neos-react-scripts watch" | ||
}, | ||
"devDependencies": { | ||
"@neos-project/neos-ui-extensibility": "^1.3" | ||
}, | ||
"neos": { | ||
"buildTargetDirectory": "../../Public/JavaScript/Footnote" | ||
}, | ||
"dependencies": { | ||
"@ckeditor/ckeditor5-utils": "^10.2.1", | ||
"lodash.upperfirst": "^4.3.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import React, {PureComponent, Fragment} from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import {connect} from 'react-redux'; | ||
import {$get, $transform} from 'plow-js'; | ||
|
||
import {IconButton, TextArea} from '@neos-project/react-ui-components'; | ||
import {neos} from '@neos-project/neos-ui-decorators'; | ||
import {executeCommand} from '@neos-project/neos-ui-ckeditor5-bindings'; | ||
|
||
import {selectors} from '@neos-project/neos-ui-redux-store'; | ||
|
||
import style from './style.css'; | ||
|
||
@connect($transform({ | ||
formattingUnderCursor: selectors.UI.ContentCanvas.formattingUnderCursor | ||
})) | ||
@neos(globalRegistry => ({ | ||
i18nRegistry: globalRegistry.get('i18n') | ||
})) | ||
export default class FootnoteButton extends PureComponent { | ||
static propTypes = { | ||
formattingUnderCursor: PropTypes.objectOf(PropTypes.oneOfType([ | ||
PropTypes.number, | ||
PropTypes.bool, | ||
PropTypes.string, | ||
PropTypes.object | ||
])), | ||
inlineEditorOptions: PropTypes.object, | ||
i18nRegistry: PropTypes.object.isRequired | ||
}; | ||
|
||
state = { | ||
isOpen: false | ||
}; | ||
|
||
componentWillReceiveProps(nextProps) { | ||
// if new selection doesn't have a footnote, close the footnote dialog | ||
if (!$get('footnote', nextProps.formattingUnderCursor)) { | ||
this.setState({isOpen: false}); | ||
} | ||
} | ||
|
||
handleFootnoteButtonClick = () => { | ||
if (this.isOpen()) { | ||
if ($get('footnote', this.props.formattingUnderCursor) !== undefined) { | ||
executeCommand('footnote'); | ||
} | ||
this.setState({isOpen: false}); | ||
} else { | ||
this.setState({isOpen: true}); | ||
} | ||
} | ||
|
||
render() { | ||
const {i18nRegistry, formattingUnderCursor, inlineEditorOptions } = this.props; | ||
|
||
return ( | ||
<div> | ||
<IconButton | ||
title={this.getFootnote() ? `${i18nRegistry.translate('Psmb.Footnote:Main:removeFootnote', 'Remove footnote')}` : `${i18nRegistry.translate('Psmb.Footnote:Main:insertFootnote', 'Insert footnote')}`} | ||
isActive={this.isOpen()} | ||
icon={this.getFootnote() ? 'ban' : 'asterisk'} | ||
onClick={this.handleFootnoteButtonClick} | ||
/> | ||
{this.isOpen() ? <FootnoteTextField footnoteValue={this.getFootnote()} formattingUnderCursor={formattingUnderCursor} inlineEditorOptions={inlineEditorOptions} /> : null} | ||
</div> | ||
); | ||
} | ||
|
||
isOpen() { | ||
return Boolean(this.state.isOpen || this.getFootnote()); | ||
} | ||
|
||
getFootnote() { | ||
return $get('footnote', this.props.formattingUnderCursor) || ''; | ||
} | ||
} | ||
|
||
@neos(globalRegistry => ({ | ||
i18nRegistry: globalRegistry.get('i18n') | ||
})) | ||
class FootnoteTextField extends PureComponent { | ||
static propTypes = { | ||
i18nRegistry: PropTypes.object, | ||
footnoteValue: PropTypes.string, | ||
inlineEditorOptions: PropTypes.object | ||
}; | ||
|
||
render() { | ||
return ( | ||
<div className={style.flyout}> | ||
<TextArea | ||
value={this.props.footnoteValue} | ||
placeholder={this.props.i18nRegistry.translate('Psmb.Footnote:Main:placeholder', 'Enter footnote text')} | ||
onChange={value => { | ||
executeCommand('footnote', value, false); | ||
}} | ||
/> | ||
</div> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import toMap from '@ckeditor/ckeditor5-utils/src/tomap'; | ||
import {Command, Plugin, UpcastConverters, DowncastConverters, ModelRange as Range, ModelPosition as Position} from 'ckeditor5-exports'; | ||
const {downcastAttributeToElement} = DowncastConverters; | ||
const {upcastElementToAttribute} = UpcastConverters; | ||
|
||
const FOOTNOTE = 'footnote'; | ||
|
||
function findFootnote(position, value) { | ||
return new Range(_findBound(position, value, true), _findBound(position, value, false)); | ||
} | ||
|
||
function _findBound(position, value, lookBack) { | ||
let node = position.textNode || (lookBack ? position.nodeBefore : position.nodeAfter); | ||
|
||
let lastNode = null; | ||
|
||
while (node && node.getAttribute(FOOTNOTE) === value) { | ||
lastNode = node; | ||
node = lookBack ? node.previousSibling : node.nextSibling; | ||
} | ||
|
||
return lastNode ? Position.createAt(lastNode, lookBack ? 'before' : 'after') : position; | ||
} | ||
|
||
class FootnoteCommand extends Command { | ||
constructor(editor, attributeKey) { | ||
super(editor); | ||
|
||
this.attributeKey = attributeKey; | ||
} | ||
|
||
refresh() { | ||
const model = this.editor.model; | ||
const doc = model.document; | ||
|
||
this.value = doc.selection.getAttribute(this.attributeKey); | ||
this.isEnabled = model.schema.checkAttributeInSelection(doc.selection, this.attributeKey); | ||
} | ||
|
||
execute(value) { | ||
const model = this.editor.model; | ||
const doc = model.document; | ||
const selection = doc.selection; | ||
const toggleMode = value === undefined; | ||
value = toggleMode ? !this.value : value; | ||
|
||
model.change(writer => { | ||
if (toggleMode && !value) { | ||
const rangesToUnset = selection.isCollapsed ? | ||
[findFootnote(selection.getFirstPosition(), selection.getAttribute(FOOTNOTE))] : selection.getRanges(); | ||
for (const range of rangesToUnset) { | ||
writer.removeAttribute(this.attributeKey, range); | ||
} | ||
} else if (selection.isCollapsed) { | ||
const position = selection.getFirstPosition(); | ||
|
||
if (selection.hasAttribute(FOOTNOTE)) { | ||
const footnoteRange = findFootnote(selection.getFirstPosition(), selection.getAttribute(FOOTNOTE)); | ||
if (value === false) { | ||
writer.removeAttribute(this.attributeKey, footnoteRange); | ||
} else { | ||
writer.setAttribute(this.attributeKey, value, footnoteRange); | ||
writer.setSelection(footnoteRange); | ||
} | ||
} else if (value !== '') { | ||
const attributes = toMap(selection.getAttributes()); | ||
attributes.set(this.attributeKey, value); | ||
const node = writer.createText(value, attributes); | ||
writer.insert(node, position); | ||
writer.setSelection(Range.createOn(node)); | ||
} | ||
} else { | ||
const ranges = model.schema.getValidRanges(selection.getRanges(), this.attributeKey); | ||
|
||
for (const range of ranges) { | ||
if (value === false) { | ||
writer.removeAttribute(this.attributeKey, range); | ||
} else { | ||
writer.setAttribute(this.attributeKey, value, range); | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
|
||
export default class Footnote extends Plugin { | ||
static get pluginName() { | ||
return 'Footnote'; | ||
} | ||
init() { | ||
const editor = this.editor; | ||
editor.model.schema.extend('$text', {allowAttributes: FOOTNOTE}); | ||
editor.conversion.for('downcast').add(downcastAttributeToElement({ | ||
model: FOOTNOTE, | ||
view: (footnote, writer) => writer.createAttributeElement('span', {'data-footnote': footnote}) | ||
})); | ||
editor.conversion.for('upcast') | ||
.add(upcastElementToAttribute({ | ||
view: { | ||
name: 'span', | ||
attributes: { | ||
'data-footnote': true | ||
} | ||
}, | ||
model: { | ||
key: FOOTNOTE, | ||
value: viewElement => viewElement.getAttribute('data-footnote') | ||
} | ||
})); | ||
editor.commands.add(FOOTNOTE, new FootnoteCommand(this.editor, FOOTNOTE)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
require('./manifest'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import manifest from '@neos-project/neos-ui-extensibility'; | ||
import FootnotePlugin from './footnotePlugin'; | ||
import FootnoteButton from './FootnoteButton'; | ||
import {$add} from 'plow-js' | ||
|
||
const addPlugin = (Plugin, isEnabled) => (ckEditorConfiguration, options) => { | ||
if (!isEnabled || isEnabled(options.editorOptions, options)) { | ||
ckEditorConfiguration.plugins = ckEditorConfiguration.plugins || []; | ||
return $add('plugins', Plugin, ckEditorConfiguration); | ||
} | ||
return ckEditorConfiguration; | ||
}; | ||
|
||
manifest('Psmb.Footnote:Footnote', {}, globalRegistry => { | ||
const richtextToolbar = globalRegistry.get('ckEditor5').get('richtextToolbar'); | ||
richtextToolbar.set('footnote', { | ||
component: FootnoteButton, | ||
isVisible: $get('formatting.footnote') | ||
}, 'before strong'); | ||
|
||
const config = globalRegistry.get('ckEditor5').get('config'); | ||
config.set('footnote', addPlugin(FootnotePlugin)); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.flyout { | ||
background-color: var(--colors-ContrastDarker); | ||
position: fixed; | ||
z-index: var(--zIndex-SecondaryToolbar-LinkIconButtonFlyout); | ||
width: 460px; | ||
border: var(--spacing-Half) solid var(--colors-ContrastDarker); | ||
} |
Oops, something went wrong.