@manuscripts/body-editor 2.8.1 → 2.8.2
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/components/toolbar/helpers.js +135 -0
- package/dist/cjs/components/toolbar/type-selector/OptionComponent.js +31 -0
- package/dist/cjs/components/toolbar/type-selector/TypeSelector.js +85 -0
- package/dist/cjs/components/toolbar/type-selector/styles.js +24 -0
- package/dist/cjs/index.js +3 -3
- package/dist/cjs/versions.js +1 -1
- package/dist/es/components/toolbar/helpers.js +127 -0
- package/dist/es/components/toolbar/type-selector/OptionComponent.js +24 -0
- package/dist/es/components/toolbar/type-selector/TypeSelector.js +78 -0
- package/dist/es/components/toolbar/type-selector/styles.js +18 -0
- package/dist/es/index.js +1 -1
- package/dist/es/versions.js +1 -1
- package/dist/types/components/toolbar/helpers.d.ts +8 -0
- package/dist/types/components/toolbar/type-selector/OptionComponent.d.ts +4 -0
- package/dist/types/components/toolbar/type-selector/TypeSelector.d.ts +15 -0
- package/dist/types/components/toolbar/type-selector/styles.d.ts +105 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/versions.d.ts +1 -1
- package/package.json +2 -2
- package/dist/cjs/components/toolbar/LevelSelector.js +0 -467
- package/dist/es/components/toolbar/LevelSelector.js +0 -460
- package/dist/types/components/toolbar/LevelSelector.d.ts +0 -23
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.promoteParagraphToSection = exports.demoteSectionToParagraph = exports.findSelectedOption = exports.titleCase = exports.optionName = void 0;
|
|
4
|
+
const track_changes_plugin_1 = require("@manuscripts/track-changes-plugin");
|
|
5
|
+
const transform_1 = require("@manuscripts/transform");
|
|
6
|
+
const prosemirror_model_1 = require("prosemirror-model");
|
|
7
|
+
const prosemirror_state_1 = require("prosemirror-state");
|
|
8
|
+
const optionName = (nodeType) => {
|
|
9
|
+
switch (nodeType) {
|
|
10
|
+
case nodeType.schema.nodes.section:
|
|
11
|
+
return 'Heading';
|
|
12
|
+
default:
|
|
13
|
+
return transform_1.nodeNames.get(nodeType) || nodeType.name;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
exports.optionName = optionName;
|
|
17
|
+
const titleCase = (text) => text.replace(/\b([a-z])/g, (match) => match.toUpperCase());
|
|
18
|
+
exports.titleCase = titleCase;
|
|
19
|
+
const findSelectedOption = (options) => {
|
|
20
|
+
for (const option of options) {
|
|
21
|
+
if (option.isSelected) {
|
|
22
|
+
return option;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
exports.findSelectedOption = findSelectedOption;
|
|
27
|
+
const getDeepestSubsection = (subsection) => {
|
|
28
|
+
while (subsection.lastChild && (0, transform_1.isSectionNodeType)(subsection.lastChild.type)) {
|
|
29
|
+
subsection = subsection.lastChild;
|
|
30
|
+
}
|
|
31
|
+
return subsection;
|
|
32
|
+
};
|
|
33
|
+
const replaceDeepestSubsection = (parentNode, newSubsection) => {
|
|
34
|
+
if (!parentNode.lastChild || !(0, transform_1.isSectionNodeType)(parentNode.lastChild.type)) {
|
|
35
|
+
return newSubsection;
|
|
36
|
+
}
|
|
37
|
+
const lastSubsectionIndex = parentNode.content.childCount - 1;
|
|
38
|
+
const lastSubsection = parentNode.content.child(lastSubsectionIndex);
|
|
39
|
+
const updatedSubsection = replaceDeepestSubsection(lastSubsection, newSubsection);
|
|
40
|
+
return parentNode.copy(parentNode.content.replaceChild(lastSubsectionIndex, updatedSubsection));
|
|
41
|
+
};
|
|
42
|
+
const demoteSectionToParagraph = (state, dispatch, view) => {
|
|
43
|
+
const { doc, selection: { $from }, schema, tr, } = state;
|
|
44
|
+
const { nodes } = schema;
|
|
45
|
+
const sectionTitle = $from.node($from.depth);
|
|
46
|
+
const afterSectionTitle = $from.after($from.depth);
|
|
47
|
+
const $afterSectionTitle = doc.resolve(afterSectionTitle);
|
|
48
|
+
const afterSectionTitleOffset = $afterSectionTitle.parentOffset;
|
|
49
|
+
const sectionDepth = $from.depth - 1;
|
|
50
|
+
const section = $from.node(sectionDepth);
|
|
51
|
+
const beforeSection = $from.before(sectionDepth);
|
|
52
|
+
const afterSection = $from.after(sectionDepth);
|
|
53
|
+
const $beforeSection = doc.resolve(beforeSection);
|
|
54
|
+
const previousNode = $beforeSection.nodeBefore;
|
|
55
|
+
const paragraph = nodes.paragraph.create({
|
|
56
|
+
id: (0, transform_1.generateNodeID)(nodes.paragraph),
|
|
57
|
+
}, sectionTitle.content);
|
|
58
|
+
let anchor;
|
|
59
|
+
let fragment;
|
|
60
|
+
const sectionContent = section.content.cut(afterSectionTitleOffset);
|
|
61
|
+
if (previousNode && (0, transform_1.isSectionNodeType)(previousNode.type)) {
|
|
62
|
+
const hasSubsections = previousNode.lastChild && (0, transform_1.isSectionNodeType)(previousNode.lastChild.type);
|
|
63
|
+
if (hasSubsections) {
|
|
64
|
+
const deepestSubsection = getDeepestSubsection(previousNode.lastChild);
|
|
65
|
+
const updatedDeepestSubsection = deepestSubsection.copy(deepestSubsection.content
|
|
66
|
+
.append(prosemirror_model_1.Fragment.from(paragraph))
|
|
67
|
+
.append(sectionContent));
|
|
68
|
+
const updatedPreviousNode = replaceDeepestSubsection(previousNode, updatedDeepestSubsection);
|
|
69
|
+
fragment = updatedPreviousNode.content;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
fragment = prosemirror_model_1.Fragment.from(previousNode.content)
|
|
73
|
+
.append(prosemirror_model_1.Fragment.from(paragraph))
|
|
74
|
+
.append(sectionContent);
|
|
75
|
+
}
|
|
76
|
+
tr.replaceWith(beforeSection - previousNode.nodeSize, afterSection, previousNode.copy(fragment));
|
|
77
|
+
anchor = beforeSection;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
tr.replaceWith(beforeSection, afterSection, prosemirror_model_1.Fragment.from(paragraph).append(sectionContent));
|
|
81
|
+
anchor = beforeSection + 1;
|
|
82
|
+
}
|
|
83
|
+
tr.setSelection(prosemirror_state_1.TextSelection.create(tr.doc, anchor, anchor + paragraph.content.size));
|
|
84
|
+
dispatch((0, track_changes_plugin_1.skipTracking)(tr));
|
|
85
|
+
view && view.focus();
|
|
86
|
+
};
|
|
87
|
+
exports.demoteSectionToParagraph = demoteSectionToParagraph;
|
|
88
|
+
const promoteParagraphToSection = (state, dispatch, view) => {
|
|
89
|
+
const { doc, selection: { $from }, schema, tr, } = state;
|
|
90
|
+
const { nodes } = schema;
|
|
91
|
+
const paragraph = $from.node($from.depth);
|
|
92
|
+
const beforeParagraph = $from.before($from.depth);
|
|
93
|
+
const $beforeParagraph = doc.resolve(beforeParagraph);
|
|
94
|
+
const beforeParagraphOffset = $beforeParagraph.parentOffset;
|
|
95
|
+
const afterParagraphOffset = beforeParagraphOffset + paragraph.nodeSize;
|
|
96
|
+
const sectionDepth = $from.depth - 1;
|
|
97
|
+
const parentSection = $from.node(sectionDepth);
|
|
98
|
+
const startIndex = $from.index(sectionDepth);
|
|
99
|
+
const endIndex = $from.indexAfter(sectionDepth);
|
|
100
|
+
const beforeParentSection = $from.before(sectionDepth);
|
|
101
|
+
const afterParentSection = $from.after(sectionDepth);
|
|
102
|
+
const items = [];
|
|
103
|
+
let offset = 0;
|
|
104
|
+
if (startIndex > 0) {
|
|
105
|
+
const precedingSection = parentSection.cut(0, beforeParagraphOffset);
|
|
106
|
+
items.push(precedingSection);
|
|
107
|
+
offset += precedingSection.nodeSize;
|
|
108
|
+
}
|
|
109
|
+
const textContent = paragraph.textContent;
|
|
110
|
+
const sectionTitle = textContent
|
|
111
|
+
? nodes.section_title.create({}, schema.text(textContent))
|
|
112
|
+
: nodes.section_title.create();
|
|
113
|
+
let sectionContent = prosemirror_model_1.Fragment.from(sectionTitle);
|
|
114
|
+
if (endIndex < parentSection.childCount) {
|
|
115
|
+
sectionContent = sectionContent.append(parentSection.content.cut(afterParagraphOffset));
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
sectionContent = sectionContent.append(prosemirror_model_1.Fragment.from(paragraph.copy()));
|
|
119
|
+
}
|
|
120
|
+
if (parentSection.type.name === 'body') {
|
|
121
|
+
const newSection = nodes.section.create({}, sectionContent);
|
|
122
|
+
items[0] = nodes.body.create({}, items[0]
|
|
123
|
+
? items[0].content.append(prosemirror_model_1.Fragment.from(newSection))
|
|
124
|
+
: prosemirror_model_1.Fragment.from(newSection));
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
items.push(parentSection.copy(sectionContent));
|
|
128
|
+
}
|
|
129
|
+
tr.replaceWith(beforeParentSection, afterParentSection, items);
|
|
130
|
+
const anchor = beforeParentSection + offset + 2;
|
|
131
|
+
tr.setSelection(prosemirror_state_1.TextSelection.create(tr.doc, anchor, anchor + sectionTitle.content.size));
|
|
132
|
+
dispatch((0, track_changes_plugin_1.skipTracking)(tr));
|
|
133
|
+
view && view.focus();
|
|
134
|
+
};
|
|
135
|
+
exports.promoteParagraphToSection = promoteParagraphToSection;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.OptionComponent = void 0;
|
|
7
|
+
const style_guide_1 = require("@manuscripts/style-guide");
|
|
8
|
+
const react_1 = __importDefault(require("react"));
|
|
9
|
+
const styled_components_1 = __importDefault(require("styled-components"));
|
|
10
|
+
const helpers_1 = require("../helpers");
|
|
11
|
+
const OptionContainer = styled_components_1.default.div `
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: space-between;
|
|
15
|
+
font-size: 14px;
|
|
16
|
+
cursor: pointer;
|
|
17
|
+
padding: 8px;
|
|
18
|
+
|
|
19
|
+
&:hover {
|
|
20
|
+
background: ${(props) => props.theme.colors.background.fifth};
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
23
|
+
const OptionLabel = styled_components_1.default.span ``;
|
|
24
|
+
const TickIconWrapper = styled_components_1.default.div ``;
|
|
25
|
+
const OptionComponent = ({ innerProps, data, }) => {
|
|
26
|
+
return (react_1.default.createElement(OptionContainer, Object.assign({}, innerProps, { ref: null }),
|
|
27
|
+
react_1.default.createElement(OptionLabel, null, (0, helpers_1.titleCase)((0, helpers_1.optionName)(data.nodeType))),
|
|
28
|
+
data.isSelected && (react_1.default.createElement(TickIconWrapper, null,
|
|
29
|
+
react_1.default.createElement(style_guide_1.TickIcon, null)))));
|
|
30
|
+
};
|
|
31
|
+
exports.OptionComponent = OptionComponent;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TypeSelector = void 0;
|
|
7
|
+
const transform_1 = require("@manuscripts/transform");
|
|
8
|
+
const prosemirror_utils_1 = require("prosemirror-utils");
|
|
9
|
+
const react_1 = __importDefault(require("react"));
|
|
10
|
+
const hierarchy_1 = require("../../../lib/hierarchy");
|
|
11
|
+
const helpers_1 = require("../helpers");
|
|
12
|
+
const OptionComponent_1 = require("./OptionComponent");
|
|
13
|
+
const styles_1 = require("./styles");
|
|
14
|
+
const buildOptions = (state) => {
|
|
15
|
+
const { doc, selection: { $from, $to }, schema, } = state;
|
|
16
|
+
const { nodes } = schema;
|
|
17
|
+
if (!$from.sameParent($to)) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
const parentElement = (0, hierarchy_1.findClosestParentElement)($from);
|
|
21
|
+
if (!parentElement) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
const parentElementType = parentElement.node.type;
|
|
25
|
+
const options = [];
|
|
26
|
+
switch (parentElementType) {
|
|
27
|
+
case parentElementType.schema.nodes.section: {
|
|
28
|
+
const sectionDepth = Math.max(1, $from.depth - 1);
|
|
29
|
+
const beforeSection = $from.before(sectionDepth);
|
|
30
|
+
const $beforeSection = doc.resolve(beforeSection);
|
|
31
|
+
const sectionOffset = $beforeSection.parentOffset;
|
|
32
|
+
options.push({
|
|
33
|
+
nodeType: nodes.paragraph,
|
|
34
|
+
label: 'Paragraph',
|
|
35
|
+
action: helpers_1.demoteSectionToParagraph,
|
|
36
|
+
isDisabled: sectionDepth <= 1 && sectionOffset <= 1,
|
|
37
|
+
isSelected: false,
|
|
38
|
+
}, {
|
|
39
|
+
nodeType: nodes.section,
|
|
40
|
+
label: 'Heading',
|
|
41
|
+
isDisabled: true,
|
|
42
|
+
isSelected: true,
|
|
43
|
+
});
|
|
44
|
+
return options;
|
|
45
|
+
}
|
|
46
|
+
case parentElementType.schema.nodes.paragraph: {
|
|
47
|
+
options.push({
|
|
48
|
+
nodeType: nodes.paragraph,
|
|
49
|
+
label: 'Paragraph',
|
|
50
|
+
isDisabled: true,
|
|
51
|
+
isSelected: true,
|
|
52
|
+
}, {
|
|
53
|
+
nodeType: nodes.section,
|
|
54
|
+
label: 'Heading',
|
|
55
|
+
action: helpers_1.promoteParagraphToSection,
|
|
56
|
+
isDisabled: false,
|
|
57
|
+
isSelected: false,
|
|
58
|
+
});
|
|
59
|
+
return options;
|
|
60
|
+
}
|
|
61
|
+
default: {
|
|
62
|
+
options.push({
|
|
63
|
+
nodeType: parentElementType,
|
|
64
|
+
label: (0, helpers_1.titleCase)((0, helpers_1.optionName)(parentElementType)),
|
|
65
|
+
isDisabled: true,
|
|
66
|
+
isSelected: true,
|
|
67
|
+
});
|
|
68
|
+
return options;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const TypeSelector = ({ state, dispatch, view }) => {
|
|
73
|
+
const options = buildOptions(state);
|
|
74
|
+
const isInBody = (0, prosemirror_utils_1.hasParentNodeOfType)(transform_1.schema.nodes.body)(state.selection);
|
|
75
|
+
return (react_1.default.createElement(styles_1.StyledSelect, { onChange: (value) => {
|
|
76
|
+
if (value && value.action) {
|
|
77
|
+
value.action(state, dispatch, view);
|
|
78
|
+
}
|
|
79
|
+
}, value: options.length === 1
|
|
80
|
+
? options[0]
|
|
81
|
+
: (0, helpers_1.findSelectedOption)(options), options: options, components: {
|
|
82
|
+
Option: OptionComponent_1.OptionComponent,
|
|
83
|
+
}, styles: styles_1.customStyles, isDisabled: options.length <= 1 || !isInBody, isSearchable: false }));
|
|
84
|
+
};
|
|
85
|
+
exports.TypeSelector = TypeSelector;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.customStyles = exports.StyledSelect = void 0;
|
|
7
|
+
const react_select_1 = __importDefault(require("react-select"));
|
|
8
|
+
const styled_components_1 = __importDefault(require("styled-components"));
|
|
9
|
+
exports.StyledSelect = (0, styled_components_1.default)((react_select_1.default)) `
|
|
10
|
+
& > div:hover {
|
|
11
|
+
border-color: ${(props) => props.theme.colors.border.secondary};
|
|
12
|
+
}
|
|
13
|
+
`;
|
|
14
|
+
exports.customStyles = {
|
|
15
|
+
control: (styles) => (Object.assign(Object.assign({}, styles), { backgroundColor: '#fff', borderWidth: 1, borderStyle: 'solid', borderColor: '#e2e2e2', boxShadow: 'none', fontSize: '14px', minHeight: 0, padding: 0, width: 200, overflowX: 'hidden', textOverflow: 'ellipsis', cursor: 'pointer', height: 32 })),
|
|
16
|
+
indicatorSeparator: () => ({
|
|
17
|
+
display: 'none',
|
|
18
|
+
}),
|
|
19
|
+
dropdownIndicator: (styles) => (Object.assign(Object.assign({}, styles), { padding: '0 4px' })),
|
|
20
|
+
menu: (styles) => (Object.assign(Object.assign({}, styles), { width: '200px' })),
|
|
21
|
+
singleValue: (styles) => (Object.assign(Object.assign({}, styles), { padding: 0 })),
|
|
22
|
+
valueContainer: (styles) => (Object.assign(Object.assign({}, styles), { padding: '1px 8px' })),
|
|
23
|
+
container: (styles) => (Object.assign(Object.assign({}, styles), { border: 'none' })),
|
|
24
|
+
};
|
package/dist/cjs/index.js
CHANGED
|
@@ -29,14 +29,14 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
29
29
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
30
30
|
};
|
|
31
31
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
-
exports.affiliationLabel = exports.authorLabel = exports.metadata = exports.getClosestMatch = exports.getNewMatch = exports.searchReplacePluginKey = exports.bibliographyPluginKey = exports.footnotesPluginKey = exports.objectsPluginKey = exports.selectedSuggestionKey = exports.PopperManager = exports.CollabProvider = exports.
|
|
32
|
+
exports.affiliationLabel = exports.authorLabel = exports.metadata = exports.getClosestMatch = exports.getNewMatch = exports.searchReplacePluginKey = exports.bibliographyPluginKey = exports.footnotesPluginKey = exports.objectsPluginKey = exports.selectedSuggestionKey = exports.PopperManager = exports.CollabProvider = exports.TypeSelector = exports.OutlineItemIcon = exports.ManuscriptOutline = void 0;
|
|
33
33
|
__exportStar(require("./commands"), exports);
|
|
34
34
|
var ManuscriptOutline_1 = require("./components/outline/ManuscriptOutline");
|
|
35
35
|
Object.defineProperty(exports, "ManuscriptOutline", { enumerable: true, get: function () { return ManuscriptOutline_1.ManuscriptOutline; } });
|
|
36
36
|
var Outline_1 = require("./components/outline/Outline");
|
|
37
37
|
Object.defineProperty(exports, "OutlineItemIcon", { enumerable: true, get: function () { return Outline_1.OutlineItemIcon; } });
|
|
38
|
-
var
|
|
39
|
-
Object.defineProperty(exports, "
|
|
38
|
+
var TypeSelector_1 = require("./components/toolbar/type-selector/TypeSelector");
|
|
39
|
+
Object.defineProperty(exports, "TypeSelector", { enumerable: true, get: function () { return TypeSelector_1.TypeSelector; } });
|
|
40
40
|
__exportStar(require("./components/toolbar/ListMenuItem"), exports);
|
|
41
41
|
__exportStar(require("./components/toolbar/InsertTableDialog"), exports);
|
|
42
42
|
__exportStar(require("./menus"), exports);
|
package/dist/cjs/versions.js
CHANGED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { skipTracking } from '@manuscripts/track-changes-plugin';
|
|
2
|
+
import { generateNodeID, isSectionNodeType, nodeNames, } from '@manuscripts/transform';
|
|
3
|
+
import { Fragment } from 'prosemirror-model';
|
|
4
|
+
import { TextSelection } from 'prosemirror-state';
|
|
5
|
+
export const optionName = (nodeType) => {
|
|
6
|
+
switch (nodeType) {
|
|
7
|
+
case nodeType.schema.nodes.section:
|
|
8
|
+
return 'Heading';
|
|
9
|
+
default:
|
|
10
|
+
return nodeNames.get(nodeType) || nodeType.name;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export const titleCase = (text) => text.replace(/\b([a-z])/g, (match) => match.toUpperCase());
|
|
14
|
+
export const findSelectedOption = (options) => {
|
|
15
|
+
for (const option of options) {
|
|
16
|
+
if (option.isSelected) {
|
|
17
|
+
return option;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const getDeepestSubsection = (subsection) => {
|
|
22
|
+
while (subsection.lastChild && isSectionNodeType(subsection.lastChild.type)) {
|
|
23
|
+
subsection = subsection.lastChild;
|
|
24
|
+
}
|
|
25
|
+
return subsection;
|
|
26
|
+
};
|
|
27
|
+
const replaceDeepestSubsection = (parentNode, newSubsection) => {
|
|
28
|
+
if (!parentNode.lastChild || !isSectionNodeType(parentNode.lastChild.type)) {
|
|
29
|
+
return newSubsection;
|
|
30
|
+
}
|
|
31
|
+
const lastSubsectionIndex = parentNode.content.childCount - 1;
|
|
32
|
+
const lastSubsection = parentNode.content.child(lastSubsectionIndex);
|
|
33
|
+
const updatedSubsection = replaceDeepestSubsection(lastSubsection, newSubsection);
|
|
34
|
+
return parentNode.copy(parentNode.content.replaceChild(lastSubsectionIndex, updatedSubsection));
|
|
35
|
+
};
|
|
36
|
+
export const demoteSectionToParagraph = (state, dispatch, view) => {
|
|
37
|
+
const { doc, selection: { $from }, schema, tr, } = state;
|
|
38
|
+
const { nodes } = schema;
|
|
39
|
+
const sectionTitle = $from.node($from.depth);
|
|
40
|
+
const afterSectionTitle = $from.after($from.depth);
|
|
41
|
+
const $afterSectionTitle = doc.resolve(afterSectionTitle);
|
|
42
|
+
const afterSectionTitleOffset = $afterSectionTitle.parentOffset;
|
|
43
|
+
const sectionDepth = $from.depth - 1;
|
|
44
|
+
const section = $from.node(sectionDepth);
|
|
45
|
+
const beforeSection = $from.before(sectionDepth);
|
|
46
|
+
const afterSection = $from.after(sectionDepth);
|
|
47
|
+
const $beforeSection = doc.resolve(beforeSection);
|
|
48
|
+
const previousNode = $beforeSection.nodeBefore;
|
|
49
|
+
const paragraph = nodes.paragraph.create({
|
|
50
|
+
id: generateNodeID(nodes.paragraph),
|
|
51
|
+
}, sectionTitle.content);
|
|
52
|
+
let anchor;
|
|
53
|
+
let fragment;
|
|
54
|
+
const sectionContent = section.content.cut(afterSectionTitleOffset);
|
|
55
|
+
if (previousNode && isSectionNodeType(previousNode.type)) {
|
|
56
|
+
const hasSubsections = previousNode.lastChild && isSectionNodeType(previousNode.lastChild.type);
|
|
57
|
+
if (hasSubsections) {
|
|
58
|
+
const deepestSubsection = getDeepestSubsection(previousNode.lastChild);
|
|
59
|
+
const updatedDeepestSubsection = deepestSubsection.copy(deepestSubsection.content
|
|
60
|
+
.append(Fragment.from(paragraph))
|
|
61
|
+
.append(sectionContent));
|
|
62
|
+
const updatedPreviousNode = replaceDeepestSubsection(previousNode, updatedDeepestSubsection);
|
|
63
|
+
fragment = updatedPreviousNode.content;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
fragment = Fragment.from(previousNode.content)
|
|
67
|
+
.append(Fragment.from(paragraph))
|
|
68
|
+
.append(sectionContent);
|
|
69
|
+
}
|
|
70
|
+
tr.replaceWith(beforeSection - previousNode.nodeSize, afterSection, previousNode.copy(fragment));
|
|
71
|
+
anchor = beforeSection;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
tr.replaceWith(beforeSection, afterSection, Fragment.from(paragraph).append(sectionContent));
|
|
75
|
+
anchor = beforeSection + 1;
|
|
76
|
+
}
|
|
77
|
+
tr.setSelection(TextSelection.create(tr.doc, anchor, anchor + paragraph.content.size));
|
|
78
|
+
dispatch(skipTracking(tr));
|
|
79
|
+
view && view.focus();
|
|
80
|
+
};
|
|
81
|
+
export const promoteParagraphToSection = (state, dispatch, view) => {
|
|
82
|
+
const { doc, selection: { $from }, schema, tr, } = state;
|
|
83
|
+
const { nodes } = schema;
|
|
84
|
+
const paragraph = $from.node($from.depth);
|
|
85
|
+
const beforeParagraph = $from.before($from.depth);
|
|
86
|
+
const $beforeParagraph = doc.resolve(beforeParagraph);
|
|
87
|
+
const beforeParagraphOffset = $beforeParagraph.parentOffset;
|
|
88
|
+
const afterParagraphOffset = beforeParagraphOffset + paragraph.nodeSize;
|
|
89
|
+
const sectionDepth = $from.depth - 1;
|
|
90
|
+
const parentSection = $from.node(sectionDepth);
|
|
91
|
+
const startIndex = $from.index(sectionDepth);
|
|
92
|
+
const endIndex = $from.indexAfter(sectionDepth);
|
|
93
|
+
const beforeParentSection = $from.before(sectionDepth);
|
|
94
|
+
const afterParentSection = $from.after(sectionDepth);
|
|
95
|
+
const items = [];
|
|
96
|
+
let offset = 0;
|
|
97
|
+
if (startIndex > 0) {
|
|
98
|
+
const precedingSection = parentSection.cut(0, beforeParagraphOffset);
|
|
99
|
+
items.push(precedingSection);
|
|
100
|
+
offset += precedingSection.nodeSize;
|
|
101
|
+
}
|
|
102
|
+
const textContent = paragraph.textContent;
|
|
103
|
+
const sectionTitle = textContent
|
|
104
|
+
? nodes.section_title.create({}, schema.text(textContent))
|
|
105
|
+
: nodes.section_title.create();
|
|
106
|
+
let sectionContent = Fragment.from(sectionTitle);
|
|
107
|
+
if (endIndex < parentSection.childCount) {
|
|
108
|
+
sectionContent = sectionContent.append(parentSection.content.cut(afterParagraphOffset));
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
sectionContent = sectionContent.append(Fragment.from(paragraph.copy()));
|
|
112
|
+
}
|
|
113
|
+
if (parentSection.type.name === 'body') {
|
|
114
|
+
const newSection = nodes.section.create({}, sectionContent);
|
|
115
|
+
items[0] = nodes.body.create({}, items[0]
|
|
116
|
+
? items[0].content.append(Fragment.from(newSection))
|
|
117
|
+
: Fragment.from(newSection));
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
items.push(parentSection.copy(sectionContent));
|
|
121
|
+
}
|
|
122
|
+
tr.replaceWith(beforeParentSection, afterParentSection, items);
|
|
123
|
+
const anchor = beforeParentSection + offset + 2;
|
|
124
|
+
tr.setSelection(TextSelection.create(tr.doc, anchor, anchor + sectionTitle.content.size));
|
|
125
|
+
dispatch(skipTracking(tr));
|
|
126
|
+
view && view.focus();
|
|
127
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { TickIcon } from '@manuscripts/style-guide';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import { optionName, titleCase } from '../helpers';
|
|
5
|
+
const OptionContainer = styled.div `
|
|
6
|
+
display: flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
justify-content: space-between;
|
|
9
|
+
font-size: 14px;
|
|
10
|
+
cursor: pointer;
|
|
11
|
+
padding: 8px;
|
|
12
|
+
|
|
13
|
+
&:hover {
|
|
14
|
+
background: ${(props) => props.theme.colors.background.fifth};
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
const OptionLabel = styled.span ``;
|
|
18
|
+
const TickIconWrapper = styled.div ``;
|
|
19
|
+
export const OptionComponent = ({ innerProps, data, }) => {
|
|
20
|
+
return (React.createElement(OptionContainer, Object.assign({}, innerProps, { ref: null }),
|
|
21
|
+
React.createElement(OptionLabel, null, titleCase(optionName(data.nodeType))),
|
|
22
|
+
data.isSelected && (React.createElement(TickIconWrapper, null,
|
|
23
|
+
React.createElement(TickIcon, null)))));
|
|
24
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { schema, } from '@manuscripts/transform';
|
|
2
|
+
import { hasParentNodeOfType } from 'prosemirror-utils';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { findClosestParentElement } from '../../../lib/hierarchy';
|
|
5
|
+
import { demoteSectionToParagraph, findSelectedOption, optionName, promoteParagraphToSection, titleCase, } from '../helpers';
|
|
6
|
+
import { OptionComponent } from './OptionComponent';
|
|
7
|
+
import { customStyles, StyledSelect } from './styles';
|
|
8
|
+
const buildOptions = (state) => {
|
|
9
|
+
const { doc, selection: { $from, $to }, schema, } = state;
|
|
10
|
+
const { nodes } = schema;
|
|
11
|
+
if (!$from.sameParent($to)) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
const parentElement = findClosestParentElement($from);
|
|
15
|
+
if (!parentElement) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
const parentElementType = parentElement.node.type;
|
|
19
|
+
const options = [];
|
|
20
|
+
switch (parentElementType) {
|
|
21
|
+
case parentElementType.schema.nodes.section: {
|
|
22
|
+
const sectionDepth = Math.max(1, $from.depth - 1);
|
|
23
|
+
const beforeSection = $from.before(sectionDepth);
|
|
24
|
+
const $beforeSection = doc.resolve(beforeSection);
|
|
25
|
+
const sectionOffset = $beforeSection.parentOffset;
|
|
26
|
+
options.push({
|
|
27
|
+
nodeType: nodes.paragraph,
|
|
28
|
+
label: 'Paragraph',
|
|
29
|
+
action: demoteSectionToParagraph,
|
|
30
|
+
isDisabled: sectionDepth <= 1 && sectionOffset <= 1,
|
|
31
|
+
isSelected: false,
|
|
32
|
+
}, {
|
|
33
|
+
nodeType: nodes.section,
|
|
34
|
+
label: 'Heading',
|
|
35
|
+
isDisabled: true,
|
|
36
|
+
isSelected: true,
|
|
37
|
+
});
|
|
38
|
+
return options;
|
|
39
|
+
}
|
|
40
|
+
case parentElementType.schema.nodes.paragraph: {
|
|
41
|
+
options.push({
|
|
42
|
+
nodeType: nodes.paragraph,
|
|
43
|
+
label: 'Paragraph',
|
|
44
|
+
isDisabled: true,
|
|
45
|
+
isSelected: true,
|
|
46
|
+
}, {
|
|
47
|
+
nodeType: nodes.section,
|
|
48
|
+
label: 'Heading',
|
|
49
|
+
action: promoteParagraphToSection,
|
|
50
|
+
isDisabled: false,
|
|
51
|
+
isSelected: false,
|
|
52
|
+
});
|
|
53
|
+
return options;
|
|
54
|
+
}
|
|
55
|
+
default: {
|
|
56
|
+
options.push({
|
|
57
|
+
nodeType: parentElementType,
|
|
58
|
+
label: titleCase(optionName(parentElementType)),
|
|
59
|
+
isDisabled: true,
|
|
60
|
+
isSelected: true,
|
|
61
|
+
});
|
|
62
|
+
return options;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
export const TypeSelector = ({ state, dispatch, view }) => {
|
|
67
|
+
const options = buildOptions(state);
|
|
68
|
+
const isInBody = hasParentNodeOfType(schema.nodes.body)(state.selection);
|
|
69
|
+
return (React.createElement(StyledSelect, { onChange: (value) => {
|
|
70
|
+
if (value && value.action) {
|
|
71
|
+
value.action(state, dispatch, view);
|
|
72
|
+
}
|
|
73
|
+
}, value: options.length === 1
|
|
74
|
+
? options[0]
|
|
75
|
+
: findSelectedOption(options), options: options, components: {
|
|
76
|
+
Option: OptionComponent,
|
|
77
|
+
}, styles: customStyles, isDisabled: options.length <= 1 || !isInBody, isSearchable: false }));
|
|
78
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Select from 'react-select';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
export const StyledSelect = styled((Select)) `
|
|
4
|
+
& > div:hover {
|
|
5
|
+
border-color: ${(props) => props.theme.colors.border.secondary};
|
|
6
|
+
}
|
|
7
|
+
`;
|
|
8
|
+
export const customStyles = {
|
|
9
|
+
control: (styles) => (Object.assign(Object.assign({}, styles), { backgroundColor: '#fff', borderWidth: 1, borderStyle: 'solid', borderColor: '#e2e2e2', boxShadow: 'none', fontSize: '14px', minHeight: 0, padding: 0, width: 200, overflowX: 'hidden', textOverflow: 'ellipsis', cursor: 'pointer', height: 32 })),
|
|
10
|
+
indicatorSeparator: () => ({
|
|
11
|
+
display: 'none',
|
|
12
|
+
}),
|
|
13
|
+
dropdownIndicator: (styles) => (Object.assign(Object.assign({}, styles), { padding: '0 4px' })),
|
|
14
|
+
menu: (styles) => (Object.assign(Object.assign({}, styles), { width: '200px' })),
|
|
15
|
+
singleValue: (styles) => (Object.assign(Object.assign({}, styles), { padding: 0 })),
|
|
16
|
+
valueContainer: (styles) => (Object.assign(Object.assign({}, styles), { padding: '1px 8px' })),
|
|
17
|
+
container: (styles) => (Object.assign(Object.assign({}, styles), { border: 'none' })),
|
|
18
|
+
};
|
package/dist/es/index.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
export * from './commands';
|
|
17
17
|
export { ManuscriptOutline } from './components/outline/ManuscriptOutline';
|
|
18
18
|
export { OutlineItemIcon } from './components/outline/Outline';
|
|
19
|
-
export {
|
|
19
|
+
export { TypeSelector } from './components/toolbar/type-selector/TypeSelector';
|
|
20
20
|
export * from './components/toolbar/ListMenuItem';
|
|
21
21
|
export * from './components/toolbar/InsertTableDialog';
|
|
22
22
|
export * from './menus';
|
package/dist/es/versions.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = '2.8.
|
|
1
|
+
export const VERSION = '2.8.2';
|
|
2
2
|
export const MATHJAX_VERSION = '3.2.2';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ManuscriptEditorView, ManuscriptNodeType } from '@manuscripts/transform';
|
|
2
|
+
import { EditorState, Transaction } from 'prosemirror-state';
|
|
3
|
+
import { Option } from './type-selector/TypeSelector';
|
|
4
|
+
export declare const optionName: (nodeType: ManuscriptNodeType) => string;
|
|
5
|
+
export declare const titleCase: (text: string) => string;
|
|
6
|
+
export declare const findSelectedOption: (options: Option[]) => Option | undefined;
|
|
7
|
+
export declare const demoteSectionToParagraph: (state: EditorState, dispatch: (tr: Transaction) => void, view?: ManuscriptEditorView) => void;
|
|
8
|
+
export declare const promoteParagraphToSection: (state: EditorState, dispatch: (tr: Transaction) => void, view?: ManuscriptEditorView) => void;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ManuscriptEditorView, ManuscriptNodeType } from '@manuscripts/transform';
|
|
2
|
+
import { EditorState, Transaction } from 'prosemirror-state';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
export interface Option {
|
|
5
|
+
action?: (state: EditorState, dispatch: (tr: Transaction) => void, view?: ManuscriptEditorView) => void;
|
|
6
|
+
isDisabled: boolean;
|
|
7
|
+
isSelected: boolean;
|
|
8
|
+
label: string;
|
|
9
|
+
nodeType: ManuscriptNodeType;
|
|
10
|
+
}
|
|
11
|
+
export declare const TypeSelector: React.FC<{
|
|
12
|
+
state: EditorState;
|
|
13
|
+
dispatch: (tr: Transaction) => void;
|
|
14
|
+
view?: ManuscriptEditorView;
|
|
15
|
+
}>;
|