-
-
Save putzwasser/289cdc3fece578912756003c187ead68 to your computer and use it in GitHub Desktop.
LinkEditor rel attribute extension
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
export default function findLinkRange(position, value, model) { | |
return model.createRange( | |
_findBound(position, value, true, model), | |
_findBound(position, value, false, model) | |
); | |
} | |
function _findBound(position, value, lookBack, model) { | |
let node = | |
position.textNode || | |
(lookBack ? position.nodeBefore : position.nodeAfter); | |
let lastNode = null; | |
while (node && node.getAttribute("linkHref") == value) { | |
lastNode = node; | |
node = lookBack ? node.previousSibling : node.nextSibling; | |
} | |
return lastNode | |
? model.createPositionAt(lastNode, lookBack ? "before" : "after") | |
: position; | |
} |
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
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
import {Command} from 'ckeditor5-exports'; | |
import findLinkRange from './findLinkRange'; | |
import toMap from './toMap'; | |
export default class LinkAttributeCommand 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 ? | |
[findLinkRange(selection.getFirstPosition(), selection.getAttribute('linkHref'), model)] : selection.getRanges(); | |
for (const range of rangesToUnset) { | |
writer.removeAttribute(this.attributeKey, range); | |
} | |
} else if (selection.isCollapsed) { | |
const position = selection.getFirstPosition(); | |
if (selection.hasAttribute('linkHref')) { | |
const linkRange = findLinkRange(selection.getFirstPosition(), selection.getAttribute('linkHref'), model); | |
if (value === false) { | |
writer.removeAttribute(this.attributeKey, linkRange); | |
} else { | |
writer.setAttribute(this.attributeKey, value, linkRange); | |
writer.setSelection(linkRange); | |
} | |
} 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); | |
} | |
} | |
} | |
}); | |
} | |
} |
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
import React, { PureComponent } from "react"; | |
import PropTypes from "prop-types"; | |
import { $get, $transform } from "plow-js"; | |
import {connect} from "react-redux"; | |
import {selectors} from "@neos-project/neos-ui-redux-store"; | |
import { executeCommand } from "@neos-project/neos-ui-ckeditor5-bindings"; | |
import { SelectBox } from '@neos-project/neos-ui-editors'; | |
const optionValues = { | |
nofollow: { | |
label: 'nofollow', | |
group: 'Annotation' | |
}, | |
noopener: { | |
label: 'noopener', | |
group: 'Annotation' | |
}, | |
noreferrer: { | |
label: 'noreferrer', | |
group: 'Annotation' | |
}, | |
sponsored: { | |
label: 'sponsored', | |
group: 'Annotation' | |
}, | |
ugc: { | |
label: 'user generated content', | |
group: 'Annotation' | |
}, | |
alternate: { | |
label: 'alternate', | |
group: 'Relation' | |
}, | |
author: { | |
label: 'author', | |
group: 'Relation' | |
}, | |
bookmark: { | |
label: 'bookmark', | |
group: 'Relation' | |
}, | |
help: { | |
label: 'help document', | |
group: 'Relation' | |
}, | |
license: { | |
label: 'copyright information', | |
group: 'Relation' | |
}, | |
prev: { | |
label: 'previous document', | |
group: 'Relation' | |
}, | |
next: { | |
label: 'next document', | |
group: 'Relation' | |
}, | |
tag: { | |
label: 'keyword/tag', | |
group: 'Relation' | |
} | |
}; | |
@connect( | |
$transform({ | |
formattingUnderCursor: selectors.UI.ContentCanvas.formattingUnderCursor | |
}) | |
) | |
export default class LinkEditorOptions extends PureComponent { | |
static propTypes = { | |
formattingUnderCursor: PropTypes.objectOf(PropTypes.oneOfType([ | |
PropTypes.array, | |
PropTypes.string, | |
PropTypes.object | |
])), | |
linkingOptions: PropTypes.object | |
}; | |
getLinkRelAttributeValue() { | |
return $get("linkRelAttribute", this.props.formattingUnderCursor) || ""; | |
} | |
handleLinkRelChange = value => { | |
executeCommand('linkRelAttribute', value, false); | |
} | |
render() { | |
return $get('linkRelAttribute', this.props.linkingOptions) ? ( | |
<div style={{ flexGrow: 1 }}> | |
<div style={{ padding: 8 }}> | |
Rel attribute | |
<SelectBox | |
options={{ values: optionValues, allowEmpty: true, multiple: true, placeholder: 'Set link relation...' }} | |
commit={value => { | |
executeCommand('linkRelAttribute', value, false); | |
}} | |
value={this.getLinkRelAttributeValue()} | |
/> | |
</div> | |
</div> | |
) : null; | |
} | |
} |
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
import { Plugin } from "ckeditor5-exports"; | |
import LinkAttributeCommand from "./LinkAttributeCommand"; | |
import { downcastAttributeToElement } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters'; | |
import { upcastAttributeToAttribute } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters'; | |
const REL = 'linkRelAttribute'; | |
export default class LinkRelAttributePlugin extends Plugin { | |
static get pluginName() { | |
return "LinkRelAttribute"; | |
} | |
init() { | |
const editor = this.editor; | |
editor.model.schema.extend('$text', { allowAttributes: REL }); | |
this.editor.commands.get("unlink").on("execute", (evt, args) => { | |
editor.execute("removeRelAttribute"); | |
}); | |
editor.conversion.for("downcast").add(downcastAttributeToElement({ | |
model: REL, | |
view: (attributeValue, writer) => { | |
// the priority has got to be the same as here so the elements would get merged: | |
// https://github.com/ckeditor/ckeditor5-link/blob/20e96361014fd13bfb93620f5eb5f528e6b1fe6d/src/utils.js#L33 | |
const linkElement = writer.createAttributeElement( | |
"a", | |
attributeValue ? { rel: attributeValue.join(' ') } : {}, | |
{ priority: 5 } | |
); | |
return linkElement; | |
}, | |
converterPriority: 'low' | |
})); | |
editor.conversion.for('upcast').add(upcastAttributeToAttribute({ | |
view: { | |
name: 'a', | |
key: 'rel' | |
}, | |
model: 'linkTarget', | |
converterPriority: 'low' | |
})); | |
editor.commands.add(REL, new LinkAttributeCommand(this.editor, REL)); | |
} | |
} |
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
import manifest from "@neos-project/neos-ui-extensibility"; | |
import LinkRelAttributePlugin from "./LinkRelAttributePlugin"; | |
import LinkEditorOptions from "./LinkEditorOptions"; | |
import {$add, $get} 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('Oevre.EditorPlugins:LinkEditor', {}, globalRegistry => { | |
const ckEditorRegistry = globalRegistry.get('ckEditor5'); | |
const config = ckEditorRegistry.get("config"); | |
config.set( | |
"linkRelAttribute", | |
addPlugin(LinkRelAttributePlugin, a => $get("linking.linkRelAttribute", a)) | |
); | |
const containerRegistry = globalRegistry.get("containers"); | |
containerRegistry.set( | |
"LinkInput/OptionsPanel/LinkEditorRelAttribute", | |
LinkEditorOptions | |
); | |
}); |
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
export default function objectToMap(obj) { | |
const map = new Map(); | |
for (const key in obj) { | |
map.set(key, obj[key]); | |
} | |
return map; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment