Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow edit headings' anchor ids #11

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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,78 @@ 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();
}

setAnchor(anchor) {
const editorState = this.props.getEditorState();
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);
}

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

this.setAnchor(null);
}

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

const editorState = this.props.getEditorState();
const block = this.getBlock(editorState);

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

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 +239,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}
>
Reset
</button>
</Tooltip>
</Portal>
)}
Expand All @@ -201,7 +273,7 @@ registerDraftPlugin({
decorators: [
{
strategy: headingStrategy,
component: UneditableAnchorDecorator,
component: HeaderAnchorDecorator,
},
],
onChange: (editorState, PluginFunctions) => {
Expand All @@ -217,9 +289,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