Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions wagtail_draftail_anchors/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"main": "index.js",
"scripts": {
"build": "webpack --mode production",
"dev": "webpack --watch --progress --mode development",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
Expand Down
16 changes: 14 additions & 2 deletions wagtail_draftail_anchors/rich_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,18 @@ def __init__(self, tag):
def __call__(self, props):
block_data = props["block"]["data"]

elem_data = {
"id": block_data.get("anchor") or block_data.get("id"),
}

if block_data.get("anchor"):
elem_data["anchor"] = block_data.get("anchor")

# Here, we want to display the block's content so we pass the `children` prop as the last parameter.
return DOM.create_element(
self.tag, {"id": block_data.get("anchor")}, props["children"]
self.tag,
elem_data,
props["children"]
)


Expand All @@ -123,5 +132,8 @@ def create_block(self, name, attrs, state, contentstate):
return DataBlock(
self.block_type,
depth=state.list_depth,
data={"anchor": attrs.get("id", "")},
data={
"id": attrs.get("id", ""),
"anchor": attrs.get("anchor", "")
},
)

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,15 @@ window.draftail.initEditor = initEditorOverride;

class AnchorIdentifierSource extends React.Component {
componentDidMount() {
const { editorState, entityType, onComplete } = this.props;
const { editorState, entityType, onComplete, entity } = this.props;

const content = editorState.getCurrentContent();

const anchor = window.prompt("Anchor identifier:");
let anchor_id = "";
if (entity) {
anchor_id = entity.data.anchor;
}
const anchor = window.prompt("Anchor identifier:", anchor_id);

// Uses the Draft.js API to create a new entity with the right data.
if (anchor) {
Expand Down Expand Up @@ -97,7 +101,6 @@ const getAnchorIdentifierAttributes = (data) => {
const AnchorIdentifier = (props) => {
const { entityKey, contentState } = props;
const data = contentState.getEntity(entityKey).getData();

return <TooltipEntity {...props} {...getAnchorIdentifierAttributes(data)} />;
};

Expand All @@ -107,14 +110,16 @@ window.draftail.registerPlugin({
decorator: AnchorIdentifier,
});

class UneditableAnchorDecorator extends React.Component {
class HeaderAnchorDecorator extends React.Component {
constructor(props) {
super(props);

this.state = {
showTooltipAt: null,
};

this.onEdit = this.onEdit.bind(this);
this.onRemove = this.onRemove.bind(this);
this.openTooltip = this.openTooltip.bind(this);
this.closeTooltip = this.closeTooltip.bind(this);
}
Expand Down Expand Up @@ -152,26 +157,74 @@ class UneditableAnchorDecorator extends React.Component {
this.setState({ showTooltipAt: null });
}

getBlock(editorState){
const block_key = editorState.getSelection().getFocusKey();
return this.props.contentState.getBlockForKey(block_key);
}

getData(editorState) {
const block = this.getBlock(editorState);
return block.getData();
}

onRemove(e) {
e.preventDefault();
e.stopPropagation();
this.setState({ showTooltipAt: null });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't actually remove it, which feels confusing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thx, I will check and let you know soon

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have fixed. Now onRemove sets anchor to null, which means that slugified header text will be set as id. Also I have renamed button to 'Reset'.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jacobtoppm will you have time to have a look?

}

onEdit(e) {
e.preventDefault();
e.stopPropagation();

const editorState = this.props.getEditorState();
const data = this.getData(editorState);
const block = this.getBlock(editorState);
const anchor = window.prompt('Anchor Link:', data.get("anchor") || data.get("id") || slugify(block.getText().toLowerCase()));

this.setState({ showTooltipAt: null });

let newEditorState = editorState;

let newData = new Map();
newData.set("anchor", anchor);

let content = editorState.getCurrentContent();
const selection = editorState.getSelection();
content = Modifier.mergeBlockData(content, selection, newData);

newEditorState = EditorState.push(
editorState,
content,
editorState.getLastChangeType()
);
newEditorState = EditorState.acceptSelection(newEditorState, selection);
this.props.setEditorState(newEditorState);
}

render() {
const children = this.props.children;
const anchor = `#${slugify(this.props.decoratedText.toLowerCase())}`;

const { showTooltipAt } = this.state;

const editorState = this.props.getEditorState();
const data = this.getData(editorState);
const block = this.getBlock(editorState);
// try to get custom anchor first, then id and only then build it from the text
const anchor = data.get("anchor") || data.get("id") || slugify(block.getText().toLowerCase());
const url = `#${anchor}`;

// Contrary to what JSX A11Y says, this should be a button but it shouldn't be focusable.
/* eslint-disable springload/jsx-a11y/interactive-supports-focus */
return (
<a
href=""
name={anchor}
role="button"
// Use onMouseUp to preserve focus in the text even after clicking.
onMouseUp={this.openTooltip}
className="TooltipEntity"
data-draftail-trigger
>
<sub>
<Icon name="anchor" className="TooltipEntity__icon" />
</sub>
{children}
{showTooltipAt && (
<Portal
Expand All @@ -182,7 +235,22 @@ class UneditableAnchorDecorator extends React.Component {
closeOnResize
>
<Tooltip target={showTooltipAt} direction="top">
{anchor}
<span className="Tooltip__link">
{url}
</span>
<button
className="button Tooltip__button"
onClick={this.onEdit}
>
Edit
</button>

<button
className="button button-secondary no Tooltip__button"
onClick={this.onRemove}
>
Cancel
</button>
</Tooltip>
</Portal>
)}
Expand All @@ -201,7 +269,7 @@ registerDraftPlugin({
decorators: [
{
strategy: headingStrategy,
component: UneditableAnchorDecorator,
component: HeaderAnchorDecorator,
},
],
onChange: (editorState, PluginFunctions) => {
Expand All @@ -217,9 +285,14 @@ registerDraftPlugin({
let newEditorState = editorState;
for (let [key, block] of blocks.entries()) {
if (block.getType().includes("header")) {
let blockSelection = SelectionState.createEmpty(key);
const blockSelection = SelectionState.createEmpty(key);
const data = block.getData()
// do not change if there is a custom anchor
if (data.get("anchor")){
continue;
}
let newData = new Map();
newData.set("anchor", slugify(block.getText().toLowerCase()));
newData.set("id", slugify(block.getText().toLowerCase()));
content = Modifier.mergeBlockData(content, blockSelection, newData);
}
}
Expand Down
9 changes: 1 addition & 8 deletions wagtail_draftail_anchors/wagtail_hooks.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
from django.conf import settings

from wagtail import VERSION as wagtail_version
import wagtail.admin.rich_text.editors.draftail.features as draftail_features
from wagtail.admin.rich_text.converters.html_to_contentstate import (
BlockElementHandler,
InlineStyleElementHandler,
)


if wagtail_version >= (3, 0):
from wagtail import hooks
Expand All @@ -20,8 +15,6 @@
anchor_identifier_entity_decorator,
)

from django.utils.html import format_html_join


@hooks.register('register_icons')
def register_icons(icons):
Expand Down