1
Fork 0

Hacky "fix" for 4.1.1 support for now

This commit is contained in:
Jason Dilworth 2023-02-20 11:00:31 +00:00
parent c32a8d7498
commit ad7facef5c
4 changed files with 342 additions and 225 deletions

View file

@ -1,13 +1,13 @@
from django.apps import apps
from django.template.loader import render_to_string
from draftjs_exporter.dom import DOM
from wagtail.admin.rich_text.converters.contentstate_models import Entity
from wagtail.admin.rich_text.converters.html_to_contentstate import LinkElementHandler, AtomicBlockEntityElementHandler
from wagtail.admin.rich_text.converters.html_to_contentstate import (
AtomicBlockEntityElementHandler, LinkElementHandler)
from wagtail.core.rich_text import EmbedHandler, LinkHandler
from .utils import get_snippet_link_frontend_template, get_snippet_embed_frontend_template
from .utils import (get_snippet_embed_frontend_template,
get_snippet_link_frontend_template)
# Snippet Link

View file

@ -1,15 +1,21 @@
SNIPPET_MODEL_CHOOSER_MODAL_ONLOAD_HANDLERS = {
'choose': function(modal, jsonData) {
function getSelectedModelMeta(context) {
$('a.snippet-model-choice', modal.body).on('click', function(event) {
event.preventDefault();
let modelMeta = {'appName': this.dataset.appName, 'modelName': this.dataset.modelName};
modal.respond('snippetModelChosen', modelMeta);
modal.close();
$(".modal-backdrop").remove();
});
}
choose: function (modal, jsonData) {
function getSelectedModelMeta(context) {
$("a.snippet-model-choice", modal.body).on(
"click",
function (event) {
event.preventDefault();
let modelMeta = {
appName: this.dataset.appName,
modelName: this.dataset.modelName,
};
modal.respond("snippetModelChosen", modelMeta);
modal.close();
$(".modal-backdrop").remove();
}
);
}
getSelectedModelMeta(modal.body);
},
getSelectedModelMeta(modal.body);
},
};

View file

@ -1,224 +1,339 @@
(() => {
'use strict';
"use strict";
const React = window.React;
const Modifier = window.DraftJS.Modifier;
const AtomicBlockUtils = window.DraftJS.AtomicBlockUtils;
const RichUtils = window.DraftJS.RichUtils;
const EditorState = window.DraftJS.EditorState;
const React = window.React;
const Modifier = window.DraftJS.Modifier;
const AtomicBlockUtils = window.DraftJS.AtomicBlockUtils;
const RichUtils = window.DraftJS.RichUtils;
const EditorState = window.DraftJS.EditorState;
const TooltipEntity = window.draftail.TooltipEntity;
const TooltipEntity = window.draftail.TooltipEntity;
const global = globalThis;
const $ = global.jQuery;
const global = globalThis;
const $ = global.jQuery;
const MUTABILITY = {};
MUTABILITY['SNIPPET'] = 'MUTABLE';
MUTABILITY['SNIPPET-EMBED'] = 'IMMUTABLE';
global.TEST_SNIPPET_CHOOSER_MODAL_ONLOAD_HANDLERS = {
choose: function (modal, jsonData) {
function ajaxifyLinks(context) {
$("a.snippet-choice", modal.body).on("click", function () {
modal.loadUrl(this.href);
return false;
});
const getSnippetModelChooserConfig = (entityType) => {
let url;
let urlParams;
$("[data-chooser-modal-choice]", context).on(
"click",
function (e) {
e.preventDefault();
if (entityType.type === 'SNIPPET') {
return {
url: global.chooserUrls.snippetLinkModelChooser,
urlParams: {},
onload: global.SNIPPET_MODEL_CHOOSER_MODAL_ONLOAD_HANDLERS,
};
}
else if (entityType.type === 'SNIPPET-EMBED') {
return {
url: global.chooserUrls.snippetEmbedModelChooser,
urlParams: {},
onload: global.SNIPPET_MODEL_CHOOSER_MODAL_ONLOAD_HANDLERS,
};
}
else {
return {
url: null,
urlParams: {},
onload: {},
};
}
};
global.TEST_SNIPPET_CHOOSER_MODAL_ONLOAD_HANDLERS.chosen(
modal,
modal.loadUrl(this.href)
);
}
);
const getSnippetModelObjectChooserConfig = () => {
let url;
let urlParams;
$(".pagination a", context).on("click", function () {
loadResults(this.href);
return false;
});
}
return {
url: global.chooserUrls.snippetChooser.concat(window.snippetModelMeta.appName, '/', window.snippetModelMeta.modelName, '/'),
urlParams: {},
onload: global.SNIPPET_CHOOSER_MODAL_ONLOAD_HANDLERS,
var searchForm$ = $("form.search-bar", modal.body);
var searchUrl = searchForm$.attr("action");
var request;
function search() {
loadResults(searchUrl, searchForm$.serialize());
return false;
}
function loadResults(url, data) {
var opts = {
url: url,
success: function (data, status) {
request = null;
$("#search-results").html(data);
ajaxifyLinks($("#search-results"));
},
error: function () {
request = null;
},
};
if (data) {
opts.data = data;
}
request = $.ajax(opts);
}
searchForm$.on("submit", search);
$("#snippet-chooser-locale", modal.body).on("change", search);
$("#id_q").on("input", function () {
if (request) {
request.abort();
}
clearTimeout($.data(this, "timer"));
var wait = setTimeout(search, 200);
$(this).data("timer", wait);
});
ajaxifyLinks(modal.body);
},
chosen: function (modal, jsonData) {
// alert(`SNIPPET CHOSEN`);
if (jsonData) {
modal.respond("snippetChosen", jsonData["result"]);
modal.close();
}
},
};
};
const filterSnippetEntityData = (entityType, data) => {
return {
edit_link: data.edit_link,
string: data.string,
id: data.id,
app_name: window.snippetModelMeta.appName,
model_name: window.snippetModelMeta.modelName,
};
};
const MUTABILITY = {};
MUTABILITY["SNIPPET"] = "MUTABLE";
MUTABILITY["SNIPPET-EMBED"] = "IMMUTABLE";
/**
* Interfaces with Wagtail's ModalWorkflow to open the chooser,
* and create new content in Draft.js based on the data.
*/
class SnippetModalWorkflowSource extends React.Component {
constructor(props) {
super(props);
const getSnippetModelChooserConfig = (entityType) => {
let url;
let urlParams;
this.onChosen = this.onChosen.bind(this);
this.onClose = this.onClose.bind(this);
this.onModelChosen = this.onModelChosen.bind(this);
}
componentDidMount() {
const { onClose, entityType, entity, editorState } = this.props;
const { url, urlParams, onload } = getSnippetModelChooserConfig(entityType);
$(document.body).on('hidden.bs.modal', this.onClose);
// eslint-disable-next-line new-cap
this.model_workflow = global.ModalWorkflow({
url,
urlParams,
onload,
responses: {
snippetModelChosen: this.onModelChosen,
},
onError: () => {
// eslint-disable-next-line no-alert
window.alert(global.wagtailConfig.STRINGS.SERVER_ERROR);
onClose();
},
});
}
componentWillUnmount() {
this.model_workflow = null;
this.workflow = null;
$(document.body).off('hidden.bs.modal', this.onClose);
}
onModelChosen(snippetModelMeta) {
window.snippetModelMeta = snippetModelMeta;
const { url, urlParams, onload } = getSnippetModelObjectChooserConfig();
this.model_workflow.close();
// eslint-disable-next-line new-cap
this.workflow = global.ModalWorkflow({
url,
urlParams,
onload,
responses: {
snippetChosen: this.onChosen,
},
onError: () => {
// eslint-disable-next-line no-alert
window.alert(global.wagtailConfig.STRINGS.SERVER_ERROR);
onClose();
},
});
}
onChosen(data) {
const { editorState, entityType, onComplete } = this.props;
const content = editorState.getCurrentContent();
const selection = editorState.getSelection();
const entityData = filterSnippetEntityData(entityType, data);
const mutability = MUTABILITY[entityType.type];
const contentWithEntity = content.createEntity(entityType.type, mutability, entityData);
const entityKey = contentWithEntity.getLastCreatedEntityKey();
let nextState;
if (entityType.block) {
// Only supports adding entities at the moment, not editing existing ones.
// See https://github.com/springload/draftail/blob/cdc8988fe2e3ac32374317f535a5338ab97e8637/examples/sources/ImageSource.js#L44-L62.
// See https://github.com/springload/draftail/blob/cdc8988fe2e3ac32374317f535a5338ab97e8637/examples/sources/EmbedSource.js#L64-L91
nextState = AtomicBlockUtils.insertAtomicBlock(editorState, entityKey, ' ');
} else {
// Replace text if the chooser demands it, or if there is no selected text in the first place.
const shouldReplaceText = data.prefer_this_title_as_link_text || selection.isCollapsed();
if (shouldReplaceText) {
// If there is a title attribute, use it. Otherwise we inject the URL.
const newText = data.string;
const newContent = Modifier.replaceText(content, selection, newText, null, entityKey);
nextState = EditorState.push(editorState, newContent, 'insert-characters');
if (entityType.type === "SNIPPET") {
return {
url: global.chooserUrls.snippetLinkModelChooser,
urlParams: {},
onload: global.SNIPPET_MODEL_CHOOSER_MODAL_ONLOAD_HANDLERS,
};
} else if (entityType.type === "SNIPPET-EMBED") {
return {
url: global.chooserUrls.snippetEmbedModelChooser,
urlParams: {},
onload: global.SNIPPET_MODEL_CHOOSER_MODAL_ONLOAD_HANDLERS,
};
} else {
nextState = RichUtils.toggleLink(editorState, selection, entityKey);
return {
url: null,
urlParams: {},
onload: {},
};
}
}
};
// IE11 crashes when rendering the new entity in contenteditable if the modal is still open.
// Other browsers do not mind. This is probably a focus management problem.
// From the user's perspective, this is all happening too fast to notice either way.
if (this.workflow) {
this.workflow.close();
}
const getSnippetModelObjectChooserConfig = () => {
let url;
let urlParams;
onComplete(nextState);
return {
url: global.chooserUrls.snippetChooser.concat(
window.snippetModelMeta.appName,
"/",
window.snippetModelMeta.modelName,
"/"
),
urlParams: {},
onload: TEST_SNIPPET_CHOOSER_MODAL_ONLOAD_HANDLERS,
};
};
const filterSnippetEntityData = (entityType, data) => {
return {
edit_link: data.edit_link,
string: data.string,
id: data.id,
app_name: window.snippetModelMeta.appName,
model_name: window.snippetModelMeta.modelName,
};
};
/**
* Interfaces with Wagtail's ModalWorkflow to open the chooser,
* and create new content in Draft.js based on the data.
*/
class SnippetModalWorkflowSource extends React.Component {
constructor(props) {
super(props);
this.onChosen = this.onChosen.bind(this);
this.onClose = this.onClose.bind(this);
this.onModelChosen = this.onModelChosen.bind(this);
}
componentDidMount() {
const { onClose, entityType, entity, editorState } = this.props;
const { url, urlParams, onload } =
getSnippetModelChooserConfig(entityType);
$(document.body).on("hidden.bs.modal", this.onClose);
// eslint-disable-next-line new-cap
this.model_workflow = global.ModalWorkflow({
url,
urlParams,
onload,
responses: {
snippetModelChosen: this.onModelChosen,
},
onError: () => {
// eslint-disable-next-line no-alert
window.alert(global.wagtailConfig.STRINGS.SERVER_ERROR);
onClose();
},
});
}
componentWillUnmount() {
this.model_workflow = null;
this.workflow = null;
$(document.body).off("hidden.bs.modal", this.onClose);
}
onModelChosen(snippetModelMeta) {
window.snippetModelMeta = snippetModelMeta;
const { url, urlParams, onload } =
getSnippetModelObjectChooserConfig();
this.model_workflow.close();
// eslint-disable-next-line new-cap
this.workflow = global.ModalWorkflow({
url,
urlParams,
onload,
responses: {
snippetChosen: this.onChosen,
chosen: this.onChosen,
},
onError: () => {
// eslint-disable-next-line no-alert
window.alert(global.wagtailConfig.STRINGS.SERVER_ERROR);
onClose();
},
});
}
onChosen(data) {
const { editorState, entityType, onComplete } = this.props;
const content = editorState.getCurrentContent();
const selection = editorState.getSelection();
const entityData = filterSnippetEntityData(entityType, data);
const mutability = MUTABILITY[entityType.type];
const contentWithEntity = content.createEntity(
entityType.type,
mutability,
entityData
);
const entityKey = contentWithEntity.getLastCreatedEntityKey();
let nextState;
if (entityType.block) {
// Only supports adding entities at the moment, not editing existing ones.
// See https://github.com/springload/draftail/blob/cdc8988fe2e3ac32374317f535a5338ab97e8637/examples/sources/ImageSource.js#L44-L62.
// See https://github.com/springload/draftail/blob/cdc8988fe2e3ac32374317f535a5338ab97e8637/examples/sources/EmbedSource.js#L64-L91
nextState = AtomicBlockUtils.insertAtomicBlock(
editorState,
entityKey,
" "
);
} else {
// Replace text if the chooser demands it, or if there is no selected text in the first place.
const shouldReplaceText =
data.prefer_this_title_as_link_text ||
selection.isCollapsed();
if (shouldReplaceText) {
// If there is a title attribute, use it. Otherwise we inject the URL.
const newText = data.string;
const newContent = Modifier.replaceText(
content,
selection,
newText,
null,
entityKey
);
nextState = EditorState.push(
editorState,
newContent,
"insert-characters"
);
} else {
nextState = RichUtils.toggleLink(
editorState,
selection,
entityKey
);
}
}
// IE11 crashes when rendering the new entity in contenteditable if the modal is still open.
// Other browsers do not mind. This is probably a focus management problem.
// From the user's perspective, this is all happening too fast to notice either way.
if (this.workflow) {
this.workflow.close();
}
onComplete(nextState);
}
onClose(e) {
const { onClose } = this.props;
e.preventDefault();
onClose();
}
render() {
return null;
}
}
onClose(e) {
const { onClose } = this.props;
e.preventDefault();
const SnippetLink = (props) => {
const { entityKey, contentState } = props;
const data = contentState.getEntity(entityKey).getData();
onClose();
}
let icon = React.createElement(window.wagtail.components.Icon, {
name: "snippet",
});
let label = data.string || "";
render() {
return null;
}
}
return React.createElement(TooltipEntity, {
entityKey: props.entityKey,
children: props.children,
onEdit: props.onEdit,
onRemove: props.onRemove,
icon: icon,
label: label,
});
};
const SnippetLink = props => {
const { entityKey, contentState } = props;
const data = contentState.getEntity(entityKey).getData();
const SnippetEmbed = (props) => {
const { entity, onRemoveEntity, entityKey } = props.blockProps;
const data = entity.getData();
let icon = React.createElement(window.wagtail.components.Icon, {name: 'snippet'});
let label = data.string || '';
let icon = React.createElement(window.wagtail.components.Icon, {
name: "snippet",
});
let label = data.string || "";
return React.createElement(TooltipEntity, {
entityKey: props.entityKey,
children: props.children,
onEdit: props.onEdit,
onRemove: props.onRemove,
icon: icon,
label: label
return React.createElement(
"div",
{
class: "MediaBlock",
},
icon,
`${label}`
);
};
window.draftail.registerPlugin({
type: "SNIPPET",
source: SnippetModalWorkflowSource,
decorator: SnippetLink,
});
};
const SnippetEmbed = props => {
const { entity, onRemoveEntity, entityKey } = props.blockProps;
const data = entity.getData();
let icon = React.createElement(window.wagtail.components.Icon, {name: 'snippet'});
let label = data.string || '';
return React.createElement("div", {
class: "MediaBlock"
}, icon, `${label}`);
};
window.draftail.registerPlugin({
type: 'SNIPPET',
source: SnippetModalWorkflowSource,
decorator: SnippetLink,
});
window.draftail.registerPlugin({
type: 'SNIPPET-EMBED',
source: SnippetModalWorkflowSource,
block: SnippetEmbed,
});
window.draftail.registerPlugin({
type: "SNIPPET-EMBED",
source: SnippetModalWorkflowSource,
block: SnippetEmbed,
});
})();

View file

@ -1,18 +1,14 @@
from django.urls import include, path
from django.urls import reverse
from django.urls import include, path, reverse
from django.utils.html import format_html
from django.utils.translation import gettext
from wagtail.admin.rich_text.editors.draftail import features as draftail_features
from wagtail.admin.rich_text.editors.draftail import \
features as draftail_features
from wagtail.core import hooks
from . import urls
from .richtext import (
ContentstateSnippetLinkConversionRule,
ContentstateSnippetEmbedConversionRule,
SnippetLinkHandler,
SnippetEmbedHandler,
)
from .richtext import (ContentstateSnippetEmbedConversionRule,
ContentstateSnippetLinkConversionRule,
SnippetEmbedHandler, SnippetLinkHandler)
@hooks.register("register_rich_text_features")
@ -28,7 +24,7 @@ def register_snippet_link_feature(features):
draftail_features.EntityFeature(
{"type": type_, "icon": "snippet", "description": gettext("Snippet Link")},
js=[
"wagtailsnippets/js/snippet-chooser-modal.js",
"wagtailsnippets/js/snippet-chooser.js",
"wagtail_draftail_snippet/js/snippet-model-chooser-modal.js",
"wagtail_draftail_snippet/js/wagtail-draftail-snippet.js",
],
@ -53,7 +49,7 @@ def register_snippet_embed_feature(features):
draftail_features.EntityFeature(
{"type": type_, "icon": "code", "description": gettext("Snippet Embed")},
js=[
"wagtailsnippets/js/snippet-chooser-modal.js",
"wagtailsnippets/js/snippet-chooser.js",
"wagtail_draftail_snippet/js/snippet-model-chooser-modal.js",
"wagtail_draftail_snippet/js/wagtail-draftail-snippet.js",
],