Hacky "fix" for 4.1.1 support for now
This commit is contained in:
parent
c32a8d7498
commit
ad7facef5c
4 changed files with 342 additions and 225 deletions
|
@ -1,13 +1,13 @@
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
from draftjs_exporter.dom import DOM
|
from draftjs_exporter.dom import DOM
|
||||||
from wagtail.admin.rich_text.converters.contentstate_models import Entity
|
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 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
|
# Snippet Link
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
SNIPPET_MODEL_CHOOSER_MODAL_ONLOAD_HANDLERS = {
|
SNIPPET_MODEL_CHOOSER_MODAL_ONLOAD_HANDLERS = {
|
||||||
'choose': function(modal, jsonData) {
|
choose: function (modal, jsonData) {
|
||||||
function getSelectedModelMeta(context) {
|
function getSelectedModelMeta(context) {
|
||||||
$('a.snippet-model-choice', modal.body).on('click', function(event) {
|
$("a.snippet-model-choice", modal.body).on(
|
||||||
|
"click",
|
||||||
|
function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let modelMeta = {'appName': this.dataset.appName, 'modelName': this.dataset.modelName};
|
let modelMeta = {
|
||||||
modal.respond('snippetModelChosen', modelMeta);
|
appName: this.dataset.appName,
|
||||||
|
modelName: this.dataset.modelName,
|
||||||
|
};
|
||||||
|
modal.respond("snippetModelChosen", modelMeta);
|
||||||
modal.close();
|
modal.close();
|
||||||
$(".modal-backdrop").remove();
|
$(".modal-backdrop").remove();
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelectedModelMeta(modal.body);
|
getSelectedModelMeta(modal.body);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
(() => {
|
(() => {
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
const React = window.React;
|
const React = window.React;
|
||||||
const Modifier = window.DraftJS.Modifier;
|
const Modifier = window.DraftJS.Modifier;
|
||||||
|
@ -12,29 +12,103 @@
|
||||||
const global = globalThis;
|
const global = globalThis;
|
||||||
const $ = global.jQuery;
|
const $ = global.jQuery;
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
|
||||||
|
$("[data-chooser-modal-choice]", context).on(
|
||||||
|
"click",
|
||||||
|
function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
global.TEST_SNIPPET_CHOOSER_MODAL_ONLOAD_HANDLERS.chosen(
|
||||||
|
modal,
|
||||||
|
modal.loadUrl(this.href)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$(".pagination a", context).on("click", function () {
|
||||||
|
loadResults(this.href);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 MUTABILITY = {};
|
const MUTABILITY = {};
|
||||||
MUTABILITY['SNIPPET'] = 'MUTABLE';
|
MUTABILITY["SNIPPET"] = "MUTABLE";
|
||||||
MUTABILITY['SNIPPET-EMBED'] = 'IMMUTABLE';
|
MUTABILITY["SNIPPET-EMBED"] = "IMMUTABLE";
|
||||||
|
|
||||||
const getSnippetModelChooserConfig = (entityType) => {
|
const getSnippetModelChooserConfig = (entityType) => {
|
||||||
let url;
|
let url;
|
||||||
let urlParams;
|
let urlParams;
|
||||||
|
|
||||||
if (entityType.type === 'SNIPPET') {
|
if (entityType.type === "SNIPPET") {
|
||||||
return {
|
return {
|
||||||
url: global.chooserUrls.snippetLinkModelChooser,
|
url: global.chooserUrls.snippetLinkModelChooser,
|
||||||
urlParams: {},
|
urlParams: {},
|
||||||
onload: global.SNIPPET_MODEL_CHOOSER_MODAL_ONLOAD_HANDLERS,
|
onload: global.SNIPPET_MODEL_CHOOSER_MODAL_ONLOAD_HANDLERS,
|
||||||
};
|
};
|
||||||
}
|
} else if (entityType.type === "SNIPPET-EMBED") {
|
||||||
else if (entityType.type === 'SNIPPET-EMBED') {
|
|
||||||
return {
|
return {
|
||||||
url: global.chooserUrls.snippetEmbedModelChooser,
|
url: global.chooserUrls.snippetEmbedModelChooser,
|
||||||
urlParams: {},
|
urlParams: {},
|
||||||
onload: global.SNIPPET_MODEL_CHOOSER_MODAL_ONLOAD_HANDLERS,
|
onload: global.SNIPPET_MODEL_CHOOSER_MODAL_ONLOAD_HANDLERS,
|
||||||
};
|
};
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return {
|
return {
|
||||||
url: null,
|
url: null,
|
||||||
urlParams: {},
|
urlParams: {},
|
||||||
|
@ -48,9 +122,14 @@
|
||||||
let urlParams;
|
let urlParams;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url: global.chooserUrls.snippetChooser.concat(window.snippetModelMeta.appName, '/', window.snippetModelMeta.modelName, '/'),
|
url: global.chooserUrls.snippetChooser.concat(
|
||||||
|
window.snippetModelMeta.appName,
|
||||||
|
"/",
|
||||||
|
window.snippetModelMeta.modelName,
|
||||||
|
"/"
|
||||||
|
),
|
||||||
urlParams: {},
|
urlParams: {},
|
||||||
onload: global.SNIPPET_CHOOSER_MODAL_ONLOAD_HANDLERS,
|
onload: TEST_SNIPPET_CHOOSER_MODAL_ONLOAD_HANDLERS,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -79,9 +158,10 @@
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { onClose, entityType, entity, editorState } = this.props;
|
const { onClose, entityType, entity, editorState } = this.props;
|
||||||
const { url, urlParams, onload } = getSnippetModelChooserConfig(entityType);
|
const { url, urlParams, onload } =
|
||||||
|
getSnippetModelChooserConfig(entityType);
|
||||||
|
|
||||||
$(document.body).on('hidden.bs.modal', this.onClose);
|
$(document.body).on("hidden.bs.modal", this.onClose);
|
||||||
|
|
||||||
// eslint-disable-next-line new-cap
|
// eslint-disable-next-line new-cap
|
||||||
this.model_workflow = global.ModalWorkflow({
|
this.model_workflow = global.ModalWorkflow({
|
||||||
|
@ -103,12 +183,13 @@
|
||||||
this.model_workflow = null;
|
this.model_workflow = null;
|
||||||
this.workflow = null;
|
this.workflow = null;
|
||||||
|
|
||||||
$(document.body).off('hidden.bs.modal', this.onClose);
|
$(document.body).off("hidden.bs.modal", this.onClose);
|
||||||
}
|
}
|
||||||
|
|
||||||
onModelChosen(snippetModelMeta) {
|
onModelChosen(snippetModelMeta) {
|
||||||
window.snippetModelMeta = snippetModelMeta;
|
window.snippetModelMeta = snippetModelMeta;
|
||||||
const { url, urlParams, onload } = getSnippetModelObjectChooserConfig();
|
const { url, urlParams, onload } =
|
||||||
|
getSnippetModelObjectChooserConfig();
|
||||||
|
|
||||||
this.model_workflow.close();
|
this.model_workflow.close();
|
||||||
|
|
||||||
|
@ -119,6 +200,7 @@
|
||||||
onload,
|
onload,
|
||||||
responses: {
|
responses: {
|
||||||
snippetChosen: this.onChosen,
|
snippetChosen: this.onChosen,
|
||||||
|
chosen: this.onChosen,
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
// eslint-disable-next-line no-alert
|
// eslint-disable-next-line no-alert
|
||||||
|
@ -135,7 +217,11 @@
|
||||||
|
|
||||||
const entityData = filterSnippetEntityData(entityType, data);
|
const entityData = filterSnippetEntityData(entityType, data);
|
||||||
const mutability = MUTABILITY[entityType.type];
|
const mutability = MUTABILITY[entityType.type];
|
||||||
const contentWithEntity = content.createEntity(entityType.type, mutability, entityData);
|
const contentWithEntity = content.createEntity(
|
||||||
|
entityType.type,
|
||||||
|
mutability,
|
||||||
|
entityData
|
||||||
|
);
|
||||||
const entityKey = contentWithEntity.getLastCreatedEntityKey();
|
const entityKey = contentWithEntity.getLastCreatedEntityKey();
|
||||||
|
|
||||||
let nextState;
|
let nextState;
|
||||||
|
@ -144,18 +230,38 @@
|
||||||
// Only supports adding entities at the moment, not editing existing ones.
|
// 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/ImageSource.js#L44-L62.
|
||||||
// See https://github.com/springload/draftail/blob/cdc8988fe2e3ac32374317f535a5338ab97e8637/examples/sources/EmbedSource.js#L64-L91
|
// See https://github.com/springload/draftail/blob/cdc8988fe2e3ac32374317f535a5338ab97e8637/examples/sources/EmbedSource.js#L64-L91
|
||||||
nextState = AtomicBlockUtils.insertAtomicBlock(editorState, entityKey, ' ');
|
nextState = AtomicBlockUtils.insertAtomicBlock(
|
||||||
|
editorState,
|
||||||
|
entityKey,
|
||||||
|
" "
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Replace text if the chooser demands it, or if there is no selected text in the first place.
|
// 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();
|
const shouldReplaceText =
|
||||||
|
data.prefer_this_title_as_link_text ||
|
||||||
|
selection.isCollapsed();
|
||||||
|
|
||||||
if (shouldReplaceText) {
|
if (shouldReplaceText) {
|
||||||
// If there is a title attribute, use it. Otherwise we inject the URL.
|
// If there is a title attribute, use it. Otherwise we inject the URL.
|
||||||
const newText = data.string;
|
const newText = data.string;
|
||||||
const newContent = Modifier.replaceText(content, selection, newText, null, entityKey);
|
const newContent = Modifier.replaceText(
|
||||||
nextState = EditorState.push(editorState, newContent, 'insert-characters');
|
content,
|
||||||
|
selection,
|
||||||
|
newText,
|
||||||
|
null,
|
||||||
|
entityKey
|
||||||
|
);
|
||||||
|
nextState = EditorState.push(
|
||||||
|
editorState,
|
||||||
|
newContent,
|
||||||
|
"insert-characters"
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
nextState = RichUtils.toggleLink(editorState, selection, entityKey);
|
nextState = RichUtils.toggleLink(
|
||||||
|
editorState,
|
||||||
|
selection,
|
||||||
|
entityKey
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,12 +287,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SnippetLink = props => {
|
const SnippetLink = (props) => {
|
||||||
const { entityKey, contentState } = props;
|
const { entityKey, contentState } = props;
|
||||||
const data = contentState.getEntity(entityKey).getData();
|
const data = contentState.getEntity(entityKey).getData();
|
||||||
|
|
||||||
let icon = React.createElement(window.wagtail.components.Icon, {name: 'snippet'});
|
let icon = React.createElement(window.wagtail.components.Icon, {
|
||||||
let label = data.string || '';
|
name: "snippet",
|
||||||
|
});
|
||||||
|
let label = data.string || "";
|
||||||
|
|
||||||
return React.createElement(TooltipEntity, {
|
return React.createElement(TooltipEntity, {
|
||||||
entityKey: props.entityKey,
|
entityKey: props.entityKey,
|
||||||
|
@ -194,30 +302,37 @@
|
||||||
onEdit: props.onEdit,
|
onEdit: props.onEdit,
|
||||||
onRemove: props.onRemove,
|
onRemove: props.onRemove,
|
||||||
icon: icon,
|
icon: icon,
|
||||||
label: label
|
label: label,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const SnippetEmbed = props => {
|
const SnippetEmbed = (props) => {
|
||||||
const { entity, onRemoveEntity, entityKey } = props.blockProps;
|
const { entity, onRemoveEntity, entityKey } = props.blockProps;
|
||||||
const data = entity.getData();
|
const data = entity.getData();
|
||||||
|
|
||||||
let icon = React.createElement(window.wagtail.components.Icon, {name: 'snippet'});
|
let icon = React.createElement(window.wagtail.components.Icon, {
|
||||||
let label = data.string || '';
|
name: "snippet",
|
||||||
|
});
|
||||||
|
let label = data.string || "";
|
||||||
|
|
||||||
return React.createElement("div", {
|
return React.createElement(
|
||||||
class: "MediaBlock"
|
"div",
|
||||||
}, icon, `${label}`);
|
{
|
||||||
|
class: "MediaBlock",
|
||||||
|
},
|
||||||
|
icon,
|
||||||
|
`${label}`
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.draftail.registerPlugin({
|
window.draftail.registerPlugin({
|
||||||
type: 'SNIPPET',
|
type: "SNIPPET",
|
||||||
source: SnippetModalWorkflowSource,
|
source: SnippetModalWorkflowSource,
|
||||||
decorator: SnippetLink,
|
decorator: SnippetLink,
|
||||||
});
|
});
|
||||||
|
|
||||||
window.draftail.registerPlugin({
|
window.draftail.registerPlugin({
|
||||||
type: 'SNIPPET-EMBED',
|
type: "SNIPPET-EMBED",
|
||||||
source: SnippetModalWorkflowSource,
|
source: SnippetModalWorkflowSource,
|
||||||
block: SnippetEmbed,
|
block: SnippetEmbed,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
from django.urls import include, path
|
from django.urls import include, path, reverse
|
||||||
from django.urls import reverse
|
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.translation import gettext
|
from django.utils.translation import gettext
|
||||||
|
from wagtail.admin.rich_text.editors.draftail import \
|
||||||
from wagtail.admin.rich_text.editors.draftail import features as draftail_features
|
features as draftail_features
|
||||||
from wagtail.core import hooks
|
from wagtail.core import hooks
|
||||||
|
|
||||||
from . import urls
|
from . import urls
|
||||||
from .richtext import (
|
from .richtext import (ContentstateSnippetEmbedConversionRule,
|
||||||
ContentstateSnippetLinkConversionRule,
|
ContentstateSnippetLinkConversionRule,
|
||||||
ContentstateSnippetEmbedConversionRule,
|
SnippetEmbedHandler, SnippetLinkHandler)
|
||||||
SnippetLinkHandler,
|
|
||||||
SnippetEmbedHandler,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@hooks.register("register_rich_text_features")
|
@hooks.register("register_rich_text_features")
|
||||||
|
@ -28,7 +24,7 @@ def register_snippet_link_feature(features):
|
||||||
draftail_features.EntityFeature(
|
draftail_features.EntityFeature(
|
||||||
{"type": type_, "icon": "snippet", "description": gettext("Snippet Link")},
|
{"type": type_, "icon": "snippet", "description": gettext("Snippet Link")},
|
||||||
js=[
|
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/snippet-model-chooser-modal.js",
|
||||||
"wagtail_draftail_snippet/js/wagtail-draftail-snippet.js",
|
"wagtail_draftail_snippet/js/wagtail-draftail-snippet.js",
|
||||||
],
|
],
|
||||||
|
@ -53,7 +49,7 @@ def register_snippet_embed_feature(features):
|
||||||
draftail_features.EntityFeature(
|
draftail_features.EntityFeature(
|
||||||
{"type": type_, "icon": "code", "description": gettext("Snippet Embed")},
|
{"type": type_, "icon": "code", "description": gettext("Snippet Embed")},
|
||||||
js=[
|
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/snippet-model-chooser-modal.js",
|
||||||
"wagtail_draftail_snippet/js/wagtail-draftail-snippet.js",
|
"wagtail_draftail_snippet/js/wagtail-draftail-snippet.js",
|
||||||
],
|
],
|
||||||
|
|
Reference in a new issue