@manuscripts/body-editor 2.7.3 → 2.7.4-LEAN-4218.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/commands.js +15 -1
- package/dist/cjs/components/toolbar/InsertEmbedDialog.js +159 -0
- package/dist/cjs/components/views/LinkForm.js +7 -7
- package/dist/cjs/configs/editor-views.js +2 -0
- package/dist/cjs/lib/oembed.js +93 -0
- package/dist/cjs/menus.js +8 -0
- package/dist/cjs/versions.js +1 -1
- package/dist/cjs/views/embed.js +84 -0
- package/dist/es/commands.js +13 -0
- package/dist/es/components/toolbar/InsertEmbedDialog.js +127 -0
- package/dist/es/components/views/LinkForm.js +2 -2
- package/dist/es/configs/editor-views.js +2 -0
- package/dist/es/lib/oembed.js +85 -0
- package/dist/es/menus.js +8 -0
- package/dist/es/versions.js +1 -1
- package/dist/es/views/embed.js +77 -0
- package/dist/types/commands.d.ts +1 -0
- package/dist/types/components/toolbar/InsertEmbedDialog.d.ts +29 -0
- package/dist/types/components/views/LinkForm.d.ts +2 -0
- package/dist/types/configs/editor-views.d.ts +1 -0
- package/dist/types/lib/oembed.d.ts +8 -0
- package/dist/types/versions.d.ts +1 -1
- package/dist/types/views/embed.d.ts +30 -0
- package/package.json +3 -2
- package/styles/AdvancedEditor.css +22 -1
package/dist/cjs/commands.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.autoComplete = exports.addColumns = exports.addHeaderRow = exports.addRows = exports.addInlineComment = exports.addNodeComment = exports.createAndFillTableElement = exports.selectAllIsolating = exports.ignoreAtomBlockNodeForward = exports.isAtEndOfTextBlock = exports.ignoreMetaNodeBackspaceCommand = exports.ignoreAtomBlockNodeBackward = exports.isTextSelection = exports.isAtStartOfTextBlock = exports.insertTOCSection = exports.insertBibliographySection = exports.insertList = exports.insertKeywords = exports.insertAffiliation = exports.insertContributors = exports.insertAbstract = exports.insertBackmatterSection = exports.insertSection = exports.insertGraphicalAbstract = exports.insertBoxElement = exports.insertInlineFootnote = exports.insertFootnotesElement = exports.insertTableElementFooter = exports.insertInlineEquation = exports.insertCrossReference = exports.insertInlineCitation = exports.insertLink = exports.insertSectionLabel = exports.findPosBeforeFirstSubsection = exports.insertBreak = exports.deleteBlock = exports.insertBlock = exports.insertSupplement = exports.insertTable = exports.insertFigure = exports.insertGeneralTableFootnote = exports.insertInlineTableFootnote = exports.createBlock = exports.createSelection = exports.canInsert = exports.blockActive = exports.isNodeSelection = exports.markActive = exports.addToStart = void 0;
|
|
18
|
+
exports.autoComplete = exports.addColumns = exports.addHeaderRow = exports.addRows = exports.addInlineComment = exports.addNodeComment = exports.createAndFillTableElement = exports.selectAllIsolating = exports.ignoreAtomBlockNodeForward = exports.isAtEndOfTextBlock = exports.ignoreMetaNodeBackspaceCommand = exports.ignoreAtomBlockNodeBackward = exports.isTextSelection = exports.isAtStartOfTextBlock = exports.insertTOCSection = exports.insertBibliographySection = exports.insertList = exports.insertKeywords = exports.insertAffiliation = exports.insertContributors = exports.insertAbstract = exports.insertBackmatterSection = exports.insertSection = exports.insertGraphicalAbstract = exports.insertBoxElement = exports.insertInlineFootnote = exports.insertFootnotesElement = exports.insertTableElementFooter = exports.insertInlineEquation = exports.insertCrossReference = exports.insertInlineCitation = exports.insertLink = exports.insertSectionLabel = exports.findPosBeforeFirstSubsection = exports.insertBreak = exports.deleteBlock = exports.insertBlock = exports.insertSupplement = exports.insertTable = exports.insertFigure = exports.insertGeneralTableFootnote = exports.insertInlineTableFootnote = exports.insertEmbed = exports.createBlock = exports.createSelection = exports.canInsert = exports.blockActive = exports.isNodeSelection = exports.markActive = exports.addToStart = void 0;
|
|
19
19
|
const json_schema_1 = require("@manuscripts/json-schema");
|
|
20
20
|
const track_changes_plugin_1 = require("@manuscripts/track-changes-plugin");
|
|
21
21
|
const transform_1 = require("@manuscripts/transform");
|
|
@@ -150,6 +150,11 @@ const createBlock = (nodeType, position, state, dispatch, attrs) => {
|
|
|
150
150
|
state.schema.nodes.equation.create(),
|
|
151
151
|
]);
|
|
152
152
|
break;
|
|
153
|
+
case state.schema.nodes.embed:
|
|
154
|
+
node = state.schema.nodes.embed.create(attrs, [
|
|
155
|
+
createAndFillFigcaptionElement(state),
|
|
156
|
+
]);
|
|
157
|
+
break;
|
|
153
158
|
default:
|
|
154
159
|
node = nodeType.createAndFill(attrs);
|
|
155
160
|
}
|
|
@@ -160,6 +165,15 @@ const createBlock = (nodeType, position, state, dispatch, attrs) => {
|
|
|
160
165
|
}
|
|
161
166
|
};
|
|
162
167
|
exports.createBlock = createBlock;
|
|
168
|
+
const insertEmbed = (state, dispatch, attrs) => {
|
|
169
|
+
const position = findBlockInsertPosition(state);
|
|
170
|
+
if (position === null) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
(0, exports.createBlock)(transform_1.schema.nodes.embed, position, state, dispatch, attrs);
|
|
174
|
+
return true;
|
|
175
|
+
};
|
|
176
|
+
exports.insertEmbed = insertEmbed;
|
|
163
177
|
const insertInlineTableFootnote = (state, dispatch) => {
|
|
164
178
|
const $pos = state.selection.$to;
|
|
165
179
|
const table = (0, prosemirror_utils_1.findParentNodeOfTypeClosestToPos)($pos, transform_1.schema.nodes.table);
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*!
|
|
3
|
+
* © 2024 Atypon Systems LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
20
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
21
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
22
|
+
}
|
|
23
|
+
Object.defineProperty(o, k2, desc);
|
|
24
|
+
}) : (function(o, m, k, k2) {
|
|
25
|
+
if (k2 === undefined) k2 = k;
|
|
26
|
+
o[k2] = m[k];
|
|
27
|
+
}));
|
|
28
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
29
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
30
|
+
}) : function(o, v) {
|
|
31
|
+
o["default"] = v;
|
|
32
|
+
});
|
|
33
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
41
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
42
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
43
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
44
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
45
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
46
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
50
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
51
|
+
};
|
|
52
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
+
exports.openEmbedDialog = exports.NoPreviewMessageWithLink = exports.InsertEmbedDialog = void 0;
|
|
54
|
+
const style_guide_1 = require("@manuscripts/style-guide");
|
|
55
|
+
const lodash_1 = require("lodash");
|
|
56
|
+
const react_1 = __importStar(require("react"));
|
|
57
|
+
const styled_components_1 = __importDefault(require("styled-components"));
|
|
58
|
+
const commands_1 = require("../../commands");
|
|
59
|
+
const oembed_1 = require("../../lib/oembed");
|
|
60
|
+
const url_1 = require("../../lib/url");
|
|
61
|
+
const editor_props_1 = require("../../plugins/editor-props");
|
|
62
|
+
const ReactSubView_1 = __importDefault(require("../../views/ReactSubView"));
|
|
63
|
+
const LinkForm_1 = require("../views/LinkForm");
|
|
64
|
+
const Label = styled_components_1.default.label `
|
|
65
|
+
padding-bottom: 4px;
|
|
66
|
+
`;
|
|
67
|
+
const HeaderContainer = (0, styled_components_1.default)(style_guide_1.PrimaryBoldHeading) `
|
|
68
|
+
font-size: ${(props) => props.theme.font.size.large};
|
|
69
|
+
`;
|
|
70
|
+
const Container = styled_components_1.default.div `
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-direction: column;
|
|
73
|
+
padding-bottom: 16px;
|
|
74
|
+
`;
|
|
75
|
+
const DialogContainer = (0, styled_components_1.default)(style_guide_1.DialogModalBody) `
|
|
76
|
+
min-width: 750px;
|
|
77
|
+
`;
|
|
78
|
+
const PreviewContainer = styled_components_1.default.div `
|
|
79
|
+
display: flex;
|
|
80
|
+
justify-content: center;
|
|
81
|
+
border: 1px solid ${(props) => props.theme.colors.border.field.default};
|
|
82
|
+
border-radius: ${(props) => props.theme.grid.radius.small};
|
|
83
|
+
padding: 6px ${(props) => props.theme.grid.unit * 4}px;
|
|
84
|
+
`;
|
|
85
|
+
const InsertEmbedDialog = ({ state, dispatch, operation, }) => {
|
|
86
|
+
const [isOpen, setOpen] = (0, react_1.useState)(true);
|
|
87
|
+
const [url, setUrl] = (0, react_1.useState)(undefined);
|
|
88
|
+
const [oembedHTML, setOEmbedHTML] = (0, react_1.useState)(undefined);
|
|
89
|
+
const action = () => {
|
|
90
|
+
if (operation === 'Insert') {
|
|
91
|
+
(0, commands_1.insertEmbed)(state, dispatch, { href: url });
|
|
92
|
+
setOpen(false);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const debouncedUrlChange = (0, lodash_1.debounce)((e) => __awaiter(void 0, void 0, void 0, function* () {
|
|
98
|
+
const url = e.target.value.trim();
|
|
99
|
+
const oEmbedUrl = yield (0, oembed_1.getOEmbedUrl)(url, 368, 217);
|
|
100
|
+
if (oEmbedUrl) {
|
|
101
|
+
const oembedJSON = yield (0, oembed_1.getOEmbedHTML)(oEmbedUrl, url);
|
|
102
|
+
setOEmbedHTML(oembedJSON);
|
|
103
|
+
}
|
|
104
|
+
setUrl(url);
|
|
105
|
+
}), 500);
|
|
106
|
+
return (react_1.default.createElement(style_guide_1.StyledModal, { isOpen: isOpen, onRequestClose: () => setOpen(false) },
|
|
107
|
+
react_1.default.createElement(DialogContainer, null,
|
|
108
|
+
react_1.default.createElement(HeaderContainer, null,
|
|
109
|
+
operation,
|
|
110
|
+
" external media"),
|
|
111
|
+
react_1.default.createElement(style_guide_1.MessageContainer, null,
|
|
112
|
+
react_1.default.createElement(Container, null,
|
|
113
|
+
react_1.default.createElement(LinkForm_1.FieldHeading, null,
|
|
114
|
+
react_1.default.createElement(Label, { htmlFor: 'embed-link' }, "Media link"),
|
|
115
|
+
url && (0, url_1.allowedHref)(url) && (react_1.default.createElement(LinkForm_1.Open, { id: 'media-link', href: url, target: '_blank', rel: 'noopener' }))),
|
|
116
|
+
react_1.default.createElement(style_guide_1.TextArea, { id: 'embed-link', rows: 2, cols: 2, autoFocus: true, required: true, placeholder: 'https://youtube.com/...', onChange: debouncedUrlChange })),
|
|
117
|
+
url && (0, url_1.allowedHref)(url) && (react_1.default.createElement(Container, null,
|
|
118
|
+
react_1.default.createElement(Label, null, "Preview"),
|
|
119
|
+
(oembedHTML && (react_1.default.createElement(PreviewContainer, { dangerouslySetInnerHTML: { __html: oembedHTML } }))) || react_1.default.createElement(NoPreviewMessage, null)))),
|
|
120
|
+
react_1.default.createElement(style_guide_1.ButtonGroup, null,
|
|
121
|
+
react_1.default.createElement(style_guide_1.SecondaryButton, { onClick: () => setOpen(false) }, "Cancel"),
|
|
122
|
+
react_1.default.createElement(style_guide_1.PrimaryButton, { onClick: action, disabled: !(url && (0, url_1.allowedHref)(url)) }, operation)))));
|
|
123
|
+
};
|
|
124
|
+
exports.InsertEmbedDialog = InsertEmbedDialog;
|
|
125
|
+
const Wrapper = styled_components_1.default.div `
|
|
126
|
+
display: flex;
|
|
127
|
+
justify-content: center;
|
|
128
|
+
padding: 50px 0;
|
|
129
|
+
`;
|
|
130
|
+
const NoPreviewMessage = () => (react_1.default.createElement(PreviewContainer, null,
|
|
131
|
+
react_1.default.createElement(Wrapper, null, "Preview not available")));
|
|
132
|
+
const NoPreviewContainer = (0, styled_components_1.default)(PreviewContainer) `
|
|
133
|
+
flex-direction: column;
|
|
134
|
+
background: #fafafa;
|
|
135
|
+
padding: 16px 56px 16px 48px;
|
|
136
|
+
`;
|
|
137
|
+
const Heading = (0, styled_components_1.default)(style_guide_1.PrimaryBoldHeading) `
|
|
138
|
+
font-size: ${(props) => props.theme.font.size.medium};
|
|
139
|
+
`;
|
|
140
|
+
const NoPreviewMessageWithLink = ({ href, }) => (react_1.default.createElement(NoPreviewContainer, null,
|
|
141
|
+
react_1.default.createElement(Heading, null, "Preview currently not available"),
|
|
142
|
+
react_1.default.createElement(style_guide_1.PrimarySmallText, null,
|
|
143
|
+
react_1.default.createElement("a", { href: href, target: '_blank', rel: "noreferrer" }, "Click here"),
|
|
144
|
+
' ',
|
|
145
|
+
"to see the source media")));
|
|
146
|
+
exports.NoPreviewMessageWithLink = NoPreviewMessageWithLink;
|
|
147
|
+
const openEmbedDialog = (view, operation = 'Insert') => {
|
|
148
|
+
if (!view) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const { state, dispatch } = view;
|
|
152
|
+
const dialog = (0, ReactSubView_1.default)((0, editor_props_1.getEditorProps)(state), exports.InsertEmbedDialog, {
|
|
153
|
+
state,
|
|
154
|
+
dispatch,
|
|
155
|
+
operation,
|
|
156
|
+
}, state.doc, () => 0, view);
|
|
157
|
+
document.body.appendChild(dialog);
|
|
158
|
+
};
|
|
159
|
+
exports.openEmbedDialog = openEmbedDialog;
|
|
@@ -41,7 +41,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
41
41
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
42
42
|
};
|
|
43
43
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
-
exports.LinkForm = void 0;
|
|
44
|
+
exports.LinkForm = exports.Open = exports.FieldHeading = void 0;
|
|
45
45
|
const style_guide_1 = require("@manuscripts/style-guide");
|
|
46
46
|
const react_1 = __importStar(require("react"));
|
|
47
47
|
const styled_components_1 = __importDefault(require("styled-components"));
|
|
@@ -66,7 +66,7 @@ const ActionGroup = styled_components_1.default.span `
|
|
|
66
66
|
const Field = styled_components_1.default.div `
|
|
67
67
|
margin-bottom: ${(props) => props.theme.grid.unit * 4}px;
|
|
68
68
|
`;
|
|
69
|
-
|
|
69
|
+
exports.FieldHeading = styled_components_1.default.div `
|
|
70
70
|
display: flex;
|
|
71
71
|
align-items: center;
|
|
72
72
|
justify-content: space-between;
|
|
@@ -78,7 +78,7 @@ const Label = styled_components_1.default.label `
|
|
|
78
78
|
color: ${(props) => props.theme.colors.text.tertiary};
|
|
79
79
|
font-size: ${(props) => props.theme.font.size.normal};
|
|
80
80
|
`;
|
|
81
|
-
|
|
81
|
+
exports.Open = styled_components_1.default.a `
|
|
82
82
|
display: inline-block;
|
|
83
83
|
margin-left: ${(props) => props.theme.grid.unit * 2}px;
|
|
84
84
|
text-transform: uppercase;
|
|
@@ -101,16 +101,16 @@ const LinkForm = ({ onCancel, onRemove, onSave, value, }) => {
|
|
|
101
101
|
}, [href, text, title, onSave]);
|
|
102
102
|
return (react_1.default.createElement(Form, { onSubmit: handleSubmit },
|
|
103
103
|
react_1.default.createElement(Field, null,
|
|
104
|
-
react_1.default.createElement(FieldHeading, null,
|
|
104
|
+
react_1.default.createElement(exports.FieldHeading, null,
|
|
105
105
|
react_1.default.createElement(Label, null, "URL"),
|
|
106
|
-
href && (0, url_1.allowedHref)(href) && (react_1.default.createElement(Open, { href: href, target: '_blank', rel: 'noopener' }))),
|
|
106
|
+
href && (0, url_1.allowedHref)(href) && (react_1.default.createElement(exports.Open, { href: href, target: '_blank', rel: 'noopener' }))),
|
|
107
107
|
react_1.default.createElement(style_guide_1.TextField, { type: 'url', name: 'href', value: href, autoComplete: 'off', autoFocus: true, required: true, onChange: (e) => setHref(e.target.value) })),
|
|
108
108
|
react_1.default.createElement(Field, null,
|
|
109
|
-
react_1.default.createElement(FieldHeading, null,
|
|
109
|
+
react_1.default.createElement(exports.FieldHeading, null,
|
|
110
110
|
react_1.default.createElement(Label, null, "Text")),
|
|
111
111
|
react_1.default.createElement(style_guide_1.TextField, { type: 'text', name: 'text', value: text, autoComplete: 'off', required: true, onChange: (e) => setText(e.target.value) })),
|
|
112
112
|
react_1.default.createElement(Field, null,
|
|
113
|
-
react_1.default.createElement(FieldHeading, null,
|
|
113
|
+
react_1.default.createElement(exports.FieldHeading, null,
|
|
114
114
|
react_1.default.createElement(Label, null, "Title (optional)")),
|
|
115
115
|
react_1.default.createElement(style_guide_1.TextField, { type: 'text', name: 'title', value: title, autoComplete: 'off', required: false, onChange: (e) => setTitle(e.target.value) })),
|
|
116
116
|
react_1.default.createElement(Actions, null,
|
|
@@ -28,6 +28,7 @@ const box_element_1 = __importDefault(require("../views/box_element"));
|
|
|
28
28
|
const citation_editable_1 = __importDefault(require("../views/citation_editable"));
|
|
29
29
|
const contributors_1 = __importDefault(require("../views/contributors"));
|
|
30
30
|
const cross_reference_editable_1 = __importDefault(require("../views/cross_reference_editable"));
|
|
31
|
+
const embed_1 = __importDefault(require("../views/embed"));
|
|
31
32
|
const empty_1 = __importDefault(require("../views/empty"));
|
|
32
33
|
const equation_editable_1 = __importDefault(require("../views/equation_editable"));
|
|
33
34
|
const equation_element_editable_1 = __importDefault(require("../views/equation_element_editable"));
|
|
@@ -64,6 +65,7 @@ exports.default = (props, dispatch) => {
|
|
|
64
65
|
cross_reference: (0, cross_reference_editable_1.default)(props, dispatch),
|
|
65
66
|
contributors: (0, contributors_1.default)(props, dispatch),
|
|
66
67
|
affiliations: (0, affiliations_1.default)(props, dispatch),
|
|
68
|
+
embed: (0, embed_1.default)(props),
|
|
67
69
|
equation: (0, equation_editable_1.default)(props),
|
|
68
70
|
equation_element: (0, equation_element_editable_1.default)(props),
|
|
69
71
|
figure: (0, figure_editable_1.default)(props, dispatch),
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.getOEmbedHTML = exports.getOEmbedUrl = void 0;
|
|
16
|
+
const oembed_providers_1 = __importDefault(require("oembed-providers"));
|
|
17
|
+
const getOEmbedUrl = (url, width, height) => __awaiter(void 0, void 0, void 0, function* () {
|
|
18
|
+
let oembedUrl;
|
|
19
|
+
for (const provider of oembed_providers_1.default) {
|
|
20
|
+
if (provider) {
|
|
21
|
+
const { provider_url, endpoints } = provider;
|
|
22
|
+
for (const endpoint of endpoints) {
|
|
23
|
+
if (endpoint.schemes &&
|
|
24
|
+
endpoint.schemes.find((schema) => globToRegex(schema).test(url))) {
|
|
25
|
+
oembedUrl = endpoint.url.replace('{format}', 'json');
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
else if (!endpoint.schemes && url.startsWith(provider_url)) {
|
|
29
|
+
oembedUrl = endpoint.url.replace('{format}', 'json');
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (oembedUrl) {
|
|
36
|
+
const params = new URLSearchParams();
|
|
37
|
+
params.append('url', url);
|
|
38
|
+
params.set('format', 'json');
|
|
39
|
+
params.append('maxwidth', width.toString());
|
|
40
|
+
params.append('maxheight', height.toString());
|
|
41
|
+
return `${oembedUrl}?${params.toString()}`;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
try {
|
|
45
|
+
const response = yield fetch(url, { method: 'HEAD' });
|
|
46
|
+
let oembedUrl;
|
|
47
|
+
const linkHeader = response.headers.get('link');
|
|
48
|
+
if (linkHeader) {
|
|
49
|
+
linkHeader.split(',').map((link) => {
|
|
50
|
+
const typeMatch = /type="([^"]*)"/.exec(link);
|
|
51
|
+
if (typeMatch &&
|
|
52
|
+
(typeMatch[1] === 'application/json+oembed' ||
|
|
53
|
+
typeMatch[1] === 'text/xml+oembed')) {
|
|
54
|
+
oembedUrl = /<([^>]*)>/.exec(link);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return oembedUrl;
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
exports.getOEmbedUrl = getOEmbedUrl;
|
|
66
|
+
const globToRegex = (glob) => {
|
|
67
|
+
const regex = glob
|
|
68
|
+
.replace(/\./g, '\\.')
|
|
69
|
+
.replace(/\*/g, '.*');
|
|
70
|
+
return new RegExp(`^${regex}$`);
|
|
71
|
+
};
|
|
72
|
+
const getOEmbedHTML = (oembedUrl, sourceLink) => __awaiter(void 0, void 0, void 0, function* () {
|
|
73
|
+
try {
|
|
74
|
+
const response = yield fetch(oembedUrl);
|
|
75
|
+
if (response.status === 200) {
|
|
76
|
+
const oembed = yield response.json();
|
|
77
|
+
return oembed.html || renderAlternativeHTML(oembed, sourceLink);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
exports.getOEmbedHTML = getOEmbedHTML;
|
|
88
|
+
const renderAlternativeHTML = (oembed, sourceLink) => {
|
|
89
|
+
if (oembed.type === 'photo') {
|
|
90
|
+
return `<img src="${oembed.url}">`;
|
|
91
|
+
}
|
|
92
|
+
return `<a href="${sourceLink}" target="_blank">${oembed.title || 'Media link'}</a>`;
|
|
93
|
+
};
|
package/dist/cjs/menus.js
CHANGED
|
@@ -20,6 +20,7 @@ const transform_1 = require("@manuscripts/transform");
|
|
|
20
20
|
const prosemirror_commands_1 = require("prosemirror-commands");
|
|
21
21
|
const prosemirror_history_1 = require("prosemirror-history");
|
|
22
22
|
const commands_1 = require("./commands");
|
|
23
|
+
const InsertEmbedDialog_1 = require("./components/toolbar/InsertEmbedDialog");
|
|
23
24
|
const InsertTableDialog_1 = require("./components/toolbar/InsertTableDialog");
|
|
24
25
|
const ListMenuItem_1 = require("./components/toolbar/ListMenuItem");
|
|
25
26
|
const hierarchy_1 = require("./lib/hierarchy");
|
|
@@ -205,6 +206,13 @@ const getEditorMenus = (editor) => {
|
|
|
205
206
|
{
|
|
206
207
|
role: 'separator',
|
|
207
208
|
},
|
|
209
|
+
{
|
|
210
|
+
id: 'insert-embed-media',
|
|
211
|
+
label: 'Embedded Media',
|
|
212
|
+
isActive: (0, commands_1.blockActive)(transform_1.schema.nodes.embed)(state),
|
|
213
|
+
isEnabled: isCommandValid((0, commands_1.canInsert)(transform_1.schema.nodes.embed)),
|
|
214
|
+
run: () => (0, InsertEmbedDialog_1.openEmbedDialog)(editor.view),
|
|
215
|
+
},
|
|
208
216
|
{
|
|
209
217
|
id: 'insert-link',
|
|
210
218
|
label: 'Link',
|
package/dist/cjs/versions.js
CHANGED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*!
|
|
3
|
+
* © 2019 Atypon Systems LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
18
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
19
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
20
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
21
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
22
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
23
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
27
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
|
+
};
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.EmbedMediaView = void 0;
|
|
31
|
+
const InsertEmbedDialog_1 = require("../components/toolbar/InsertEmbedDialog");
|
|
32
|
+
const oembed_1 = require("../lib/oembed");
|
|
33
|
+
const block_view_1 = __importDefault(require("./block_view"));
|
|
34
|
+
const creators_1 = require("./creators");
|
|
35
|
+
const ReactSubView_1 = __importDefault(require("./ReactSubView"));
|
|
36
|
+
class EmbedMediaView extends block_view_1.default {
|
|
37
|
+
constructor() {
|
|
38
|
+
super(...arguments);
|
|
39
|
+
this.ignoreMutation = () => true;
|
|
40
|
+
this.stopEvent = () => true;
|
|
41
|
+
this.createElement = () => {
|
|
42
|
+
this.container = document.createElement('div');
|
|
43
|
+
this.container.classList.add('block');
|
|
44
|
+
this.container.setAttribute('id', this.node.attrs.id);
|
|
45
|
+
this.dom.appendChild(this.container);
|
|
46
|
+
this.contentDOM = document.createElement('div');
|
|
47
|
+
this.container.appendChild(this.contentDOM);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
updateContents() {
|
|
51
|
+
const _super = Object.create(null, {
|
|
52
|
+
updateContents: { get: () => super.updateContents }
|
|
53
|
+
});
|
|
54
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
55
|
+
_super.updateContents.call(this);
|
|
56
|
+
if (this.href !== this.node.attrs.href) {
|
|
57
|
+
this.href = this.node.attrs.href;
|
|
58
|
+
yield this.updateOEmbedPreview();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
updateOEmbedPreview() {
|
|
63
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
64
|
+
const preview = document.createElement('div');
|
|
65
|
+
preview.classList.add('embed-media-preview');
|
|
66
|
+
preview.setAttribute('contenteditable', 'false');
|
|
67
|
+
this.container.prepend(preview);
|
|
68
|
+
const oEmbedUrl = yield (0, oembed_1.getOEmbedUrl)(this.href, 643, 363);
|
|
69
|
+
if (oEmbedUrl) {
|
|
70
|
+
const oembedHTML = yield (0, oembed_1.getOEmbedHTML)(oEmbedUrl, this.href);
|
|
71
|
+
if (oembedHTML) {
|
|
72
|
+
preview.innerHTML = oembedHTML;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
this.showUnavailableMessage(preview);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
showUnavailableMessage(preview) {
|
|
80
|
+
preview.appendChild((0, ReactSubView_1.default)(this.props, InsertEmbedDialog_1.NoPreviewMessageWithLink, { href: this.node.attrs.href }, this.node, this.getPos, this.view));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.EmbedMediaView = EmbedMediaView;
|
|
84
|
+
exports.default = (0, creators_1.createNodeView)(EmbedMediaView);
|
package/dist/es/commands.js
CHANGED
|
@@ -141,6 +141,11 @@ export const createBlock = (nodeType, position, state, dispatch, attrs) => {
|
|
|
141
141
|
state.schema.nodes.equation.create(),
|
|
142
142
|
]);
|
|
143
143
|
break;
|
|
144
|
+
case state.schema.nodes.embed:
|
|
145
|
+
node = state.schema.nodes.embed.create(attrs, [
|
|
146
|
+
createAndFillFigcaptionElement(state),
|
|
147
|
+
]);
|
|
148
|
+
break;
|
|
144
149
|
default:
|
|
145
150
|
node = nodeType.createAndFill(attrs);
|
|
146
151
|
}
|
|
@@ -150,6 +155,14 @@ export const createBlock = (nodeType, position, state, dispatch, attrs) => {
|
|
|
150
155
|
dispatch(tr.setSelection(selection).scrollIntoView());
|
|
151
156
|
}
|
|
152
157
|
};
|
|
158
|
+
export const insertEmbed = (state, dispatch, attrs) => {
|
|
159
|
+
const position = findBlockInsertPosition(state);
|
|
160
|
+
if (position === null) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
createBlock(schema.nodes.embed, position, state, dispatch, attrs);
|
|
164
|
+
return true;
|
|
165
|
+
};
|
|
153
166
|
export const insertInlineTableFootnote = (state, dispatch) => {
|
|
154
167
|
const $pos = state.selection.$to;
|
|
155
168
|
const table = findParentNodeOfTypeClosestToPos($pos, schema.nodes.table);
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* © 2024 Atypon Systems LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
17
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
18
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
19
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
20
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
21
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
22
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
import { ButtonGroup, DialogModalBody, MessageContainer, PrimaryBoldHeading, PrimaryButton, PrimarySmallText, SecondaryButton, StyledModal, TextArea, } from '@manuscripts/style-guide';
|
|
26
|
+
import { debounce } from 'lodash';
|
|
27
|
+
import React, { useState } from 'react';
|
|
28
|
+
import styled from 'styled-components';
|
|
29
|
+
import { insertEmbed } from '../../commands';
|
|
30
|
+
import { getOEmbedHTML, getOEmbedUrl } from '../../lib/oembed';
|
|
31
|
+
import { allowedHref } from '../../lib/url';
|
|
32
|
+
import { getEditorProps } from '../../plugins/editor-props';
|
|
33
|
+
import ReactSubView from '../../views/ReactSubView';
|
|
34
|
+
import { FieldHeading, Open } from '../views/LinkForm';
|
|
35
|
+
const Label = styled.label `
|
|
36
|
+
padding-bottom: 4px;
|
|
37
|
+
`;
|
|
38
|
+
const HeaderContainer = styled(PrimaryBoldHeading) `
|
|
39
|
+
font-size: ${(props) => props.theme.font.size.large};
|
|
40
|
+
`;
|
|
41
|
+
const Container = styled.div `
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
padding-bottom: 16px;
|
|
45
|
+
`;
|
|
46
|
+
const DialogContainer = styled(DialogModalBody) `
|
|
47
|
+
min-width: 750px;
|
|
48
|
+
`;
|
|
49
|
+
const PreviewContainer = styled.div `
|
|
50
|
+
display: flex;
|
|
51
|
+
justify-content: center;
|
|
52
|
+
border: 1px solid ${(props) => props.theme.colors.border.field.default};
|
|
53
|
+
border-radius: ${(props) => props.theme.grid.radius.small};
|
|
54
|
+
padding: 6px ${(props) => props.theme.grid.unit * 4}px;
|
|
55
|
+
`;
|
|
56
|
+
export const InsertEmbedDialog = ({ state, dispatch, operation, }) => {
|
|
57
|
+
const [isOpen, setOpen] = useState(true);
|
|
58
|
+
const [url, setUrl] = useState(undefined);
|
|
59
|
+
const [oembedHTML, setOEmbedHTML] = useState(undefined);
|
|
60
|
+
const action = () => {
|
|
61
|
+
if (operation === 'Insert') {
|
|
62
|
+
insertEmbed(state, dispatch, { href: url });
|
|
63
|
+
setOpen(false);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const debouncedUrlChange = debounce((e) => __awaiter(void 0, void 0, void 0, function* () {
|
|
69
|
+
const url = e.target.value.trim();
|
|
70
|
+
const oEmbedUrl = yield getOEmbedUrl(url, 368, 217);
|
|
71
|
+
if (oEmbedUrl) {
|
|
72
|
+
const oembedJSON = yield getOEmbedHTML(oEmbedUrl, url);
|
|
73
|
+
setOEmbedHTML(oembedJSON);
|
|
74
|
+
}
|
|
75
|
+
setUrl(url);
|
|
76
|
+
}), 500);
|
|
77
|
+
return (React.createElement(StyledModal, { isOpen: isOpen, onRequestClose: () => setOpen(false) },
|
|
78
|
+
React.createElement(DialogContainer, null,
|
|
79
|
+
React.createElement(HeaderContainer, null,
|
|
80
|
+
operation,
|
|
81
|
+
" external media"),
|
|
82
|
+
React.createElement(MessageContainer, null,
|
|
83
|
+
React.createElement(Container, null,
|
|
84
|
+
React.createElement(FieldHeading, null,
|
|
85
|
+
React.createElement(Label, { htmlFor: 'embed-link' }, "Media link"),
|
|
86
|
+
url && allowedHref(url) && (React.createElement(Open, { id: 'media-link', href: url, target: '_blank', rel: 'noopener' }))),
|
|
87
|
+
React.createElement(TextArea, { id: 'embed-link', rows: 2, cols: 2, autoFocus: true, required: true, placeholder: 'https://youtube.com/...', onChange: debouncedUrlChange })),
|
|
88
|
+
url && allowedHref(url) && (React.createElement(Container, null,
|
|
89
|
+
React.createElement(Label, null, "Preview"),
|
|
90
|
+
(oembedHTML && (React.createElement(PreviewContainer, { dangerouslySetInnerHTML: { __html: oembedHTML } }))) || React.createElement(NoPreviewMessage, null)))),
|
|
91
|
+
React.createElement(ButtonGroup, null,
|
|
92
|
+
React.createElement(SecondaryButton, { onClick: () => setOpen(false) }, "Cancel"),
|
|
93
|
+
React.createElement(PrimaryButton, { onClick: action, disabled: !(url && allowedHref(url)) }, operation)))));
|
|
94
|
+
};
|
|
95
|
+
const Wrapper = styled.div `
|
|
96
|
+
display: flex;
|
|
97
|
+
justify-content: center;
|
|
98
|
+
padding: 50px 0;
|
|
99
|
+
`;
|
|
100
|
+
const NoPreviewMessage = () => (React.createElement(PreviewContainer, null,
|
|
101
|
+
React.createElement(Wrapper, null, "Preview not available")));
|
|
102
|
+
const NoPreviewContainer = styled(PreviewContainer) `
|
|
103
|
+
flex-direction: column;
|
|
104
|
+
background: #fafafa;
|
|
105
|
+
padding: 16px 56px 16px 48px;
|
|
106
|
+
`;
|
|
107
|
+
const Heading = styled(PrimaryBoldHeading) `
|
|
108
|
+
font-size: ${(props) => props.theme.font.size.medium};
|
|
109
|
+
`;
|
|
110
|
+
export const NoPreviewMessageWithLink = ({ href, }) => (React.createElement(NoPreviewContainer, null,
|
|
111
|
+
React.createElement(Heading, null, "Preview currently not available"),
|
|
112
|
+
React.createElement(PrimarySmallText, null,
|
|
113
|
+
React.createElement("a", { href: href, target: '_blank', rel: "noreferrer" }, "Click here"),
|
|
114
|
+
' ',
|
|
115
|
+
"to see the source media")));
|
|
116
|
+
export const openEmbedDialog = (view, operation = 'Insert') => {
|
|
117
|
+
if (!view) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const { state, dispatch } = view;
|
|
121
|
+
const dialog = ReactSubView(getEditorProps(state), InsertEmbedDialog, {
|
|
122
|
+
state,
|
|
123
|
+
dispatch,
|
|
124
|
+
operation,
|
|
125
|
+
}, state.doc, () => 0, view);
|
|
126
|
+
document.body.appendChild(dialog);
|
|
127
|
+
};
|
|
@@ -37,7 +37,7 @@ const ActionGroup = styled.span `
|
|
|
37
37
|
const Field = styled.div `
|
|
38
38
|
margin-bottom: ${(props) => props.theme.grid.unit * 4}px;
|
|
39
39
|
`;
|
|
40
|
-
const FieldHeading = styled.div `
|
|
40
|
+
export const FieldHeading = styled.div `
|
|
41
41
|
display: flex;
|
|
42
42
|
align-items: center;
|
|
43
43
|
justify-content: space-between;
|
|
@@ -49,7 +49,7 @@ const Label = styled.label `
|
|
|
49
49
|
color: ${(props) => props.theme.colors.text.tertiary};
|
|
50
50
|
font-size: ${(props) => props.theme.font.size.normal};
|
|
51
51
|
`;
|
|
52
|
-
const Open = styled.a `
|
|
52
|
+
export const Open = styled.a `
|
|
53
53
|
display: inline-block;
|
|
54
54
|
margin-left: ${(props) => props.theme.grid.unit * 2}px;
|
|
55
55
|
text-transform: uppercase;
|
|
@@ -23,6 +23,7 @@ import boxElement from '../views/box_element';
|
|
|
23
23
|
import citation from '../views/citation_editable';
|
|
24
24
|
import contributors from '../views/contributors';
|
|
25
25
|
import crossReference from '../views/cross_reference_editable';
|
|
26
|
+
import embed from '../views/embed';
|
|
26
27
|
import empty from '../views/empty';
|
|
27
28
|
import equation from '../views/equation_editable';
|
|
28
29
|
import equationElement from '../views/equation_element_editable';
|
|
@@ -59,6 +60,7 @@ export default (props, dispatch) => {
|
|
|
59
60
|
cross_reference: crossReference(props, dispatch),
|
|
60
61
|
contributors: contributors(props, dispatch),
|
|
61
62
|
affiliations: affiliations(props, dispatch),
|
|
63
|
+
embed: embed(props),
|
|
62
64
|
equation: equation(props),
|
|
63
65
|
equation_element: equationElement(props),
|
|
64
66
|
figure: figure(props, dispatch),
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import providers from 'oembed-providers';
|
|
11
|
+
export const getOEmbedUrl = (url, width, height) => __awaiter(void 0, void 0, void 0, function* () {
|
|
12
|
+
let oembedUrl;
|
|
13
|
+
for (const provider of providers) {
|
|
14
|
+
if (provider) {
|
|
15
|
+
const { provider_url, endpoints } = provider;
|
|
16
|
+
for (const endpoint of endpoints) {
|
|
17
|
+
if (endpoint.schemes &&
|
|
18
|
+
endpoint.schemes.find((schema) => globToRegex(schema).test(url))) {
|
|
19
|
+
oembedUrl = endpoint.url.replace('{format}', 'json');
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
else if (!endpoint.schemes && url.startsWith(provider_url)) {
|
|
23
|
+
oembedUrl = endpoint.url.replace('{format}', 'json');
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (oembedUrl) {
|
|
30
|
+
const params = new URLSearchParams();
|
|
31
|
+
params.append('url', url);
|
|
32
|
+
params.set('format', 'json');
|
|
33
|
+
params.append('maxwidth', width.toString());
|
|
34
|
+
params.append('maxheight', height.toString());
|
|
35
|
+
return `${oembedUrl}?${params.toString()}`;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
try {
|
|
39
|
+
const response = yield fetch(url, { method: 'HEAD' });
|
|
40
|
+
let oembedUrl;
|
|
41
|
+
const linkHeader = response.headers.get('link');
|
|
42
|
+
if (linkHeader) {
|
|
43
|
+
linkHeader.split(',').map((link) => {
|
|
44
|
+
const typeMatch = /type="([^"]*)"/.exec(link);
|
|
45
|
+
if (typeMatch &&
|
|
46
|
+
(typeMatch[1] === 'application/json+oembed' ||
|
|
47
|
+
typeMatch[1] === 'text/xml+oembed')) {
|
|
48
|
+
oembedUrl = /<([^>]*)>/.exec(link);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return oembedUrl;
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
const globToRegex = (glob) => {
|
|
60
|
+
const regex = glob
|
|
61
|
+
.replace(/\./g, '\\.')
|
|
62
|
+
.replace(/\*/g, '.*');
|
|
63
|
+
return new RegExp(`^${regex}$`);
|
|
64
|
+
};
|
|
65
|
+
export const getOEmbedHTML = (oembedUrl, sourceLink) => __awaiter(void 0, void 0, void 0, function* () {
|
|
66
|
+
try {
|
|
67
|
+
const response = yield fetch(oembedUrl);
|
|
68
|
+
if (response.status === 200) {
|
|
69
|
+
const oembed = yield response.json();
|
|
70
|
+
return oembed.html || renderAlternativeHTML(oembed, sourceLink);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
const renderAlternativeHTML = (oembed, sourceLink) => {
|
|
81
|
+
if (oembed.type === 'photo') {
|
|
82
|
+
return `<img src="${oembed.url}">`;
|
|
83
|
+
}
|
|
84
|
+
return `<a href="${sourceLink}" target="_blank">${oembed.title || 'Media link'}</a>`;
|
|
85
|
+
};
|
package/dist/es/menus.js
CHANGED
|
@@ -17,6 +17,7 @@ import { getGroupCateogries, schema, } from '@manuscripts/transform';
|
|
|
17
17
|
import { toggleMark } from 'prosemirror-commands';
|
|
18
18
|
import { redo, undo } from 'prosemirror-history';
|
|
19
19
|
import { addInlineComment, blockActive, canInsert, insertAbstract, insertAffiliation, insertBackmatterSection, insertBlock, insertBoxElement, insertContributors, insertCrossReference, insertGraphicalAbstract, insertInlineCitation, insertInlineEquation, insertInlineFootnote, insertKeywords, insertLink, insertList, insertSection, markActive, } from './commands';
|
|
20
|
+
import { openEmbedDialog } from './components/toolbar/InsertEmbedDialog';
|
|
20
21
|
import { openInsertTableDialog } from './components/toolbar/InsertTableDialog';
|
|
21
22
|
import { ListMenuItem } from './components/toolbar/ListMenuItem';
|
|
22
23
|
import { deleteClosestParentElement, findClosestParentElementNodeName, } from './lib/hierarchy';
|
|
@@ -202,6 +203,13 @@ export const getEditorMenus = (editor) => {
|
|
|
202
203
|
{
|
|
203
204
|
role: 'separator',
|
|
204
205
|
},
|
|
206
|
+
{
|
|
207
|
+
id: 'insert-embed-media',
|
|
208
|
+
label: 'Embedded Media',
|
|
209
|
+
isActive: blockActive(schema.nodes.embed)(state),
|
|
210
|
+
isEnabled: isCommandValid(canInsert(schema.nodes.embed)),
|
|
211
|
+
run: () => openEmbedDialog(editor.view),
|
|
212
|
+
},
|
|
205
213
|
{
|
|
206
214
|
id: 'insert-link',
|
|
207
215
|
label: 'Link',
|
package/dist/es/versions.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = '2.7.
|
|
1
|
+
export const VERSION = '2.7.4-LEAN-4218.0';
|
|
2
2
|
export const MATHJAX_VERSION = '3.2.2';
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* © 2019 Atypon Systems LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
17
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
18
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
19
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
20
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
21
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
22
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
import { NoPreviewMessageWithLink } from '../components/toolbar/InsertEmbedDialog';
|
|
26
|
+
import { getOEmbedHTML, getOEmbedUrl } from '../lib/oembed';
|
|
27
|
+
import BlockView from './block_view';
|
|
28
|
+
import { createNodeView } from './creators';
|
|
29
|
+
import ReactSubView from './ReactSubView';
|
|
30
|
+
export class EmbedMediaView extends BlockView {
|
|
31
|
+
constructor() {
|
|
32
|
+
super(...arguments);
|
|
33
|
+
this.ignoreMutation = () => true;
|
|
34
|
+
this.stopEvent = () => true;
|
|
35
|
+
this.createElement = () => {
|
|
36
|
+
this.container = document.createElement('div');
|
|
37
|
+
this.container.classList.add('block');
|
|
38
|
+
this.container.setAttribute('id', this.node.attrs.id);
|
|
39
|
+
this.dom.appendChild(this.container);
|
|
40
|
+
this.contentDOM = document.createElement('div');
|
|
41
|
+
this.container.appendChild(this.contentDOM);
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
updateContents() {
|
|
45
|
+
const _super = Object.create(null, {
|
|
46
|
+
updateContents: { get: () => super.updateContents }
|
|
47
|
+
});
|
|
48
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
49
|
+
_super.updateContents.call(this);
|
|
50
|
+
if (this.href !== this.node.attrs.href) {
|
|
51
|
+
this.href = this.node.attrs.href;
|
|
52
|
+
yield this.updateOEmbedPreview();
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
updateOEmbedPreview() {
|
|
57
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
58
|
+
const preview = document.createElement('div');
|
|
59
|
+
preview.classList.add('embed-media-preview');
|
|
60
|
+
preview.setAttribute('contenteditable', 'false');
|
|
61
|
+
this.container.prepend(preview);
|
|
62
|
+
const oEmbedUrl = yield getOEmbedUrl(this.href, 643, 363);
|
|
63
|
+
if (oEmbedUrl) {
|
|
64
|
+
const oembedHTML = yield getOEmbedHTML(oEmbedUrl, this.href);
|
|
65
|
+
if (oembedHTML) {
|
|
66
|
+
preview.innerHTML = oembedHTML;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
this.showUnavailableMessage(preview);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
showUnavailableMessage(preview) {
|
|
74
|
+
preview.appendChild(ReactSubView(this.props, NoPreviewMessageWithLink, { href: this.node.attrs.href }, this.node, this.getPos, this.view));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export default createNodeView(EmbedMediaView);
|
package/dist/types/commands.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export declare const blockActive: (type: ManuscriptNodeType) => (state: Manuscri
|
|
|
27
27
|
export declare const canInsert: (type: ManuscriptNodeType) => (state: ManuscriptEditorState) => boolean;
|
|
28
28
|
export declare const createSelection: (nodeType: ManuscriptNodeType, position: number, doc: ManuscriptNode) => Selection;
|
|
29
29
|
export declare const createBlock: (nodeType: ManuscriptNodeType, position: number, state: ManuscriptEditorState, dispatch?: Dispatch, attrs?: Attrs) => void;
|
|
30
|
+
export declare const insertEmbed: (state: ManuscriptEditorState, dispatch?: Dispatch, attrs?: Attrs) => boolean;
|
|
30
31
|
export declare const insertInlineTableFootnote: (state: ManuscriptEditorState, dispatch?: Dispatch) => boolean;
|
|
31
32
|
export declare const insertGeneralTableFootnote: (element: [ManuscriptNode, number], state: ManuscriptEditorState, dispatch?: Dispatch) => boolean | undefined;
|
|
32
33
|
export declare const insertFigure: (file: FileAttachment, state: ManuscriptEditorState, dispatch?: Dispatch) => boolean;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* © 2024 Atypon Systems LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { ManuscriptEditorState } from '@manuscripts/transform';
|
|
17
|
+
import { EditorView } from 'prosemirror-view';
|
|
18
|
+
import React from 'react';
|
|
19
|
+
import { Dispatch } from '../../commands';
|
|
20
|
+
export type InsertEmbedDialogProps = {
|
|
21
|
+
state: ManuscriptEditorState;
|
|
22
|
+
dispatch?: Dispatch;
|
|
23
|
+
operation: 'Insert' | 'Update';
|
|
24
|
+
};
|
|
25
|
+
export declare const InsertEmbedDialog: React.FC<InsertEmbedDialogProps>;
|
|
26
|
+
export declare const NoPreviewMessageWithLink: React.FC<{
|
|
27
|
+
href: string;
|
|
28
|
+
}>;
|
|
29
|
+
export declare const openEmbedDialog: (view?: EditorView, operation?: string) => void;
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import React from 'react';
|
|
17
|
+
export declare const FieldHeading: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
18
|
+
export declare const Open: import("styled-components").StyledComponent<"a", any, {}, never>;
|
|
17
19
|
export interface LinkValue {
|
|
18
20
|
text: string;
|
|
19
21
|
href: string;
|
|
@@ -49,6 +49,7 @@ declare const _default: (props: EditorProps, dispatch: Dispatch) => {
|
|
|
49
49
|
cross_reference: import("../types").NodeViewCreator<import("../views/cross_reference_editable").CrossReferenceEditableView>;
|
|
50
50
|
contributors: import("../types").NodeViewCreator<import("../views/contributors").ContributorsView>;
|
|
51
51
|
affiliations: import("../types").NodeViewCreator<import("../views/affiliations").AffiliationsView>;
|
|
52
|
+
embed: import("../types").NodeViewCreator<import("../views/embed").EmbedMediaView>;
|
|
52
53
|
equation: import("../types").NodeViewCreator<import("../views/equation_editable").EquationEditableView>;
|
|
53
54
|
equation_element: import("../types").NodeViewCreator<{
|
|
54
55
|
gutterButtons(): HTMLElement[];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type ProviderJson = {
|
|
2
|
+
type: 'photo' | 'video' | 'link' | 'rich';
|
|
3
|
+
html?: string;
|
|
4
|
+
url?: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare const getOEmbedUrl: (url: string, width: number, height: number) => Promise<string | undefined>;
|
|
8
|
+
export declare const getOEmbedHTML: (oembedUrl: string, sourceLink: string) => Promise<any>;
|
package/dist/types/versions.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "2.7.
|
|
1
|
+
export declare const VERSION = "2.7.4-LEAN-4218.0";
|
|
2
2
|
export declare const MATHJAX_VERSION = "3.2.2";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* © 2019 Atypon Systems LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { EmbedNode } from '@manuscripts/transform';
|
|
17
|
+
import { Trackable } from '../types';
|
|
18
|
+
import BlockView from './block_view';
|
|
19
|
+
export declare class EmbedMediaView extends BlockView<Trackable<EmbedNode>> {
|
|
20
|
+
private container;
|
|
21
|
+
private href;
|
|
22
|
+
ignoreMutation: () => boolean;
|
|
23
|
+
stopEvent: () => boolean;
|
|
24
|
+
createElement: () => void;
|
|
25
|
+
updateContents(): Promise<void>;
|
|
26
|
+
private updateOEmbedPreview;
|
|
27
|
+
private showUnavailableMessage;
|
|
28
|
+
}
|
|
29
|
+
declare const _default: (props: import("../configs/ManuscriptsEditor").EditorProps, dispatch?: import("..").Dispatch | undefined) => import("../types").NodeViewCreator<EmbedMediaView>;
|
|
30
|
+
export default _default;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@manuscripts/body-editor",
|
|
3
3
|
"description": "Prosemirror components for editing and viewing manuscripts",
|
|
4
|
-
"version": "2.7.
|
|
4
|
+
"version": "2.7.4-LEAN-4218.0",
|
|
5
5
|
"repository": "github:Atypon-OpenSource/manuscripts-body-editor",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"main": "dist/cjs",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@manuscripts/library": "1.3.11",
|
|
35
35
|
"@manuscripts/style-guide": "2.0.28",
|
|
36
36
|
"@manuscripts/track-changes-plugin": "1.9.1",
|
|
37
|
-
"@manuscripts/transform": "3.0.
|
|
37
|
+
"@manuscripts/transform": "3.0.33-LEAN-4218.0",
|
|
38
38
|
"@popperjs/core": "^2.11.8",
|
|
39
39
|
"astrocite-eutils": "^0.16.4",
|
|
40
40
|
"codemirror": "^5.58.1",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"grapheme-splitter": "^1.0.4",
|
|
44
44
|
"history": "^4.10.1",
|
|
45
45
|
"mathjax": "3.2.2",
|
|
46
|
+
"oembed-providers": "^1.0.20241022",
|
|
46
47
|
"prosemirror-collab": "^1.3.1",
|
|
47
48
|
"prosemirror-commands": "^1.5.0",
|
|
48
49
|
"prosemirror-dropcursor": "^1.6.1",
|
|
@@ -1071,4 +1071,25 @@ th:hover > .table-context-menu-button,
|
|
|
1071
1071
|
.ProseMirror .block-contributors .action-gutter {
|
|
1072
1072
|
right: 15px;
|
|
1073
1073
|
pointer-events: none;
|
|
1074
|
-
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
.ProseMirror .block-embed {
|
|
1077
|
+
grid-template-columns: none;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
.ProseMirror .block-embed .block {
|
|
1081
|
+
text-align: center;
|
|
1082
|
+
margin: 0 52px;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
.tracking-visible .ProseMirror .block-embed[data-track-status='pending'][data-track-op='delete'] .block > * {
|
|
1086
|
+
opacity: 0.5
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
.ProseMirror .block-embed .tools-panel {
|
|
1090
|
+
position: relative;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
.ProseMirror .block-embed .block-gutter, .block-embed .action-gutter {
|
|
1094
|
+
display: none;
|
|
1095
|
+
}
|