@manuscripts/body-editor 3.13.18 → 3.13.19

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/icons.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ORCIDIcon = exports.tickIcon = exports.fileMainDocumentIcon = exports.linkIcon = exports.translateIcon = exports.draggableIcon = exports.fileCorruptedIcon = exports.imageDefaultIcon = exports.imageLeftIcon = exports.imageRightIcon = exports.plusIcon = exports.lockIcon = exports.scrollIcon = exports.sectionCategoryIcon = exports.editIcon = exports.deleteIcon = exports.cameraIcon = exports.alertIcon = exports.arrowUp = exports.arrowDown = exports.addAuthorIcon = void 0;
3
+ exports.ORCIDIcon = exports.tickIcon = exports.fileMainDocumentIcon = exports.leadingHalfLeftIcon = exports.leadingWallpaperIcon = exports.leadingLargeFloatIcon = exports.leadingSmallFloatIcon = exports.leadingHeroImageIcon = exports.linkIcon = exports.translateIcon = exports.draggableIcon = exports.fileCorruptedIcon = exports.imageDefaultIcon = exports.imageLeftIcon = exports.imageRightIcon = exports.plusIcon = exports.lockIcon = exports.scrollIcon = exports.sectionCategoryIcon = exports.editIcon = exports.deleteIcon = exports.cameraIcon = exports.alertIcon = exports.arrowUp = exports.arrowDown = exports.addAuthorIcon = void 0;
4
4
  const style_guide_1 = require("@manuscripts/style-guide");
5
5
  const react_1 = require("react");
6
6
  const server_1 = require("react-dom/server");
@@ -23,6 +23,11 @@ exports.fileCorruptedIcon = renderIcon(style_guide_1.FileCorruptedIcon);
23
23
  exports.draggableIcon = renderIcon(style_guide_1.DraggableIcon);
24
24
  exports.translateIcon = renderIcon(style_guide_1.TranslateIcon);
25
25
  exports.linkIcon = renderIcon(style_guide_1.LinkIcon);
26
+ exports.leadingHeroImageIcon = renderIcon(style_guide_1.LeadingHeroImageIcon);
27
+ exports.leadingSmallFloatIcon = renderIcon(style_guide_1.LeadingSmallFloatIcon);
28
+ exports.leadingLargeFloatIcon = renderIcon(style_guide_1.LeadingLargeFloatIcon);
29
+ exports.leadingWallpaperIcon = renderIcon(style_guide_1.LeadingWallpaperIcon);
30
+ exports.leadingHalfLeftIcon = renderIcon(style_guide_1.LeadingHalfLeftIcon);
26
31
  exports.fileMainDocumentIcon = renderIcon(style_guide_1.FileMainDocumentIcon);
27
32
  exports.tickIcon = renderIcon(style_guide_1.TickIcon);
28
33
  exports.ORCIDIcon = renderIcon(style_guide_1.ORCIDIcon);
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MATHJAX_VERSION = exports.VERSION = void 0;
4
- exports.VERSION = '3.13.18';
4
+ exports.VERSION = '3.13.19';
5
5
  exports.MATHJAX_VERSION = '3.2.2';
@@ -23,21 +23,67 @@ const icons_1 = require("../icons");
23
23
  const navigation_utils_1 = require("../lib/navigation-utils");
24
24
  const block_view_1 = __importDefault(require("./block_view"));
25
25
  const creators_1 = require("./creators");
26
+ const LAYOUT_OPTIONS_CONFIG = [
27
+ {
28
+ label: 'Hero Image',
29
+ icon: icons_1.leadingHeroImageIcon,
30
+ attr: 'leading',
31
+ },
32
+ {
33
+ label: 'Float Image - Small',
34
+ icon: icons_1.leadingSmallFloatIcon,
35
+ attr: 'leading-small-float',
36
+ },
37
+ {
38
+ label: 'Float Image - Large',
39
+ icon: icons_1.leadingLargeFloatIcon,
40
+ attr: 'leading-large-float',
41
+ },
42
+ {
43
+ label: 'Wallpaper Image',
44
+ icon: icons_1.leadingWallpaperIcon,
45
+ attr: 'leading-wallpaper',
46
+ },
47
+ {
48
+ label: 'Half Image',
49
+ icon: icons_1.leadingHalfLeftIcon,
50
+ attr: 'leading-half-left',
51
+ },
52
+ ];
26
53
  class HeroImageView extends block_view_1.default {
27
54
  constructor() {
28
55
  super(...arguments);
29
56
  this.collapsed = false;
57
+ this.layoutPanelCollapsed = false;
30
58
  this.ignoreMutation = () => true;
31
59
  this.createElement = () => {
32
60
  this.container = document.createElement('div');
33
61
  this.container.classList.add('block', 'hero-image-container');
34
62
  this.dom.appendChild(this.container);
63
+ this.abortController = new AbortController();
35
64
  this.container.appendChild(this.createPanel());
65
+ this.container.appendChild(this.createLayoutPanel());
66
+ this.layoutOptions = this.createLayoutOptions();
67
+ this.layoutOptions.addEventListener('change', this.onSelectOption, {
68
+ signal: this.abortController.signal,
69
+ });
70
+ this.container.appendChild(this.layoutOptions);
36
71
  this.contentDOM = document.createElement('figure');
37
72
  this.contentDOM.classList.add('figure-block', 'hero-image-figure');
38
73
  this.contentDOM.setAttribute('id', this.node.attrs.id);
39
74
  this.container.appendChild(this.contentDOM);
40
75
  };
76
+ this.onSelectOption = (e) => {
77
+ const target = e.target;
78
+ if (target && target.type === 'radio' && target.name === 'layout-option') {
79
+ const { state, dispatch } = this.view;
80
+ const tr = state.tr.setNodeMarkup(this.getPos(), undefined, {
81
+ ...this.node.attrs,
82
+ type: target.value,
83
+ });
84
+ dispatch(tr);
85
+ }
86
+ };
41
87
  }
42
88
  createPanel() {
43
89
  const panel = document.createElement('div');
@@ -57,11 +103,72 @@ class HeroImageView extends block_view_1.default {
57
103
  heroImageToggleBtn.classList.toggle('collapsed', this.collapsed);
58
104
  };
59
105
  heroImageToggleBtn.onclick = handleToggle;
60
- heroImageToggleBtn.addEventListener('keydown', (0, navigation_utils_1.handleEnterKey)(handleToggle));
106
+ heroImageToggleBtn.addEventListener('keydown', (0, navigation_utils_1.handleEnterKey)(handleToggle), {
107
+ signal: this.abortController.signal,
108
+ });
61
109
  panel.appendChild(label);
62
110
  panel.appendChild(heroImageToggleBtn);
63
111
  return panel;
64
112
  }
113
+ createLayoutPanel() {
114
+ const panel = document.createElement('div');
115
+ panel.classList.add('panel-header', 'layout-header');
116
+ const label = document.createElement('span');
117
+ label.textContent = 'Layout';
118
+ label.contentEditable = 'false';
119
+ const toggleBtn = document.createElement('button');
120
+ toggleBtn.classList.add('toggle-btn', 'button-reset');
121
+ toggleBtn.innerHTML = icons_1.arrowUp;
122
+ toggleBtn.classList.toggle('collapsed', this.layoutPanelCollapsed);
123
+ const handleToggle = () => {
124
+ this.layoutPanelCollapsed = !this.layoutPanelCollapsed;
125
+ if (this.layoutOptions) {
126
+ this.layoutOptions.style.display = this.layoutPanelCollapsed
127
+ ? 'none'
128
+ : '';
129
+ }
130
+ toggleBtn.classList.toggle('collapsed', this.layoutPanelCollapsed);
131
+ };
132
+ toggleBtn.onclick = handleToggle;
133
+ toggleBtn.addEventListener('keydown', (0, navigation_utils_1.handleEnterKey)(handleToggle), {
134
+ signal: this.abortController.signal,
135
+ });
136
+ panel.appendChild(label);
137
+ panel.appendChild(toggleBtn);
138
+ return panel;
139
+ }
140
+ createLayoutOptions() {
141
+ const can = this.props.getCapabilities();
142
+ const optionsGroup = document.createElement('div');
143
+ optionsGroup.className = 'layout-options-group';
144
+ LAYOUT_OPTIONS_CONFIG.forEach(({ label, icon, attr }) => {
145
+ const image = document.createElement('span');
146
+ image.className = 'layout-option-image';
147
+ image.innerHTML = icon;
148
+ const input = document.createElement('input');
149
+ input.type = 'radio';
150
+ input.name = 'layout-option';
151
+ input.value = attr;
152
+ input.disabled = !can.editMetadata;
153
+ input.checked = attr === this.node.attrs.type;
154
+ const inputLabel = document.createElement('span');
155
+ inputLabel.className = 'layout-option-label';
156
+ inputLabel.textContent = label;
157
+ const wrapper = document.createElement('span');
158
+ wrapper.className = 'layout-option-label-wrapper';
159
+ wrapper.append(input, inputLabel);
160
+ const container = document.createElement('div');
161
+ container.className = 'layout-option';
162
+ container.append(image, wrapper);
163
+ optionsGroup.appendChild(container);
164
+ });
165
+ optionsGroup.contentEditable = 'false';
166
+ return optionsGroup;
167
+ }
168
+ destroy() {
169
+ this.abortController?.abort();
170
+ super.destroy();
171
+ }
65
172
  }
66
173
  exports.HeroImageView = HeroImageView;
67
174
  exports.default = (0, creators_1.createNodeView)(HeroImageView);
package/dist/es/icons.js CHANGED
@@ -1,4 +1,4 @@
1
- import { AddAuthorIcon, AlertIcon, ArrowDownCircleIcon, ArrowUpIcon, CameraIcon, DeleteIcon, DraggableIcon, EditIcon, FileCorruptedIcon, FileMainDocumentIcon, ImageDefaultIcon, ImageLeftIcon, ImageRightIcon, LinkIcon, LockIcon, ORCIDIcon as ORCID, PlusIcon, ScrollIcon, SectionCategoryIcon, TickIcon, TranslateIcon, } from '@manuscripts/style-guide';
1
+ import { AddAuthorIcon, AlertIcon, ArrowDownCircleIcon, ArrowUpIcon, CameraIcon, DeleteIcon, DraggableIcon, EditIcon, FileCorruptedIcon, FileMainDocumentIcon, ImageDefaultIcon, ImageLeftIcon, ImageRightIcon, LinkIcon, LockIcon, LeadingHalfLeftIcon, LeadingHeroImageIcon, LeadingLargeFloatIcon, LeadingSmallFloatIcon, LeadingWallpaperIcon, ORCIDIcon as ORCID, PlusIcon, ScrollIcon, SectionCategoryIcon, TickIcon, TranslateIcon, } from '@manuscripts/style-guide';
2
2
  import { createElement } from 'react';
3
3
  import { renderToStaticMarkup } from 'react-dom/server';
4
4
  const renderIcon = (c) => renderToStaticMarkup(createElement(c));
@@ -20,6 +20,11 @@ export const fileCorruptedIcon = renderIcon(FileCorruptedIcon);
20
20
  export const draggableIcon = renderIcon(DraggableIcon);
21
21
  export const translateIcon = renderIcon(TranslateIcon);
22
22
  export const linkIcon = renderIcon(LinkIcon);
23
+ export const leadingHeroImageIcon = renderIcon(LeadingHeroImageIcon);
24
+ export const leadingSmallFloatIcon = renderIcon(LeadingSmallFloatIcon);
25
+ export const leadingLargeFloatIcon = renderIcon(LeadingLargeFloatIcon);
26
+ export const leadingWallpaperIcon = renderIcon(LeadingWallpaperIcon);
27
+ export const leadingHalfLeftIcon = renderIcon(LeadingHalfLeftIcon);
23
28
  export const fileMainDocumentIcon = renderIcon(FileMainDocumentIcon);
24
29
  export const tickIcon = renderIcon(TickIcon);
25
30
  export const ORCIDIcon = renderIcon(ORCID);
@@ -1,2 +1,2 @@
1
- export const VERSION = '3.13.18';
1
+ export const VERSION = '3.13.19';
2
2
  export const MATHJAX_VERSION = '3.2.2';
@@ -13,25 +13,71 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { arrowUp } from '../icons';
16
+ import { arrowUp, leadingHalfLeftIcon, leadingHeroImageIcon, leadingLargeFloatIcon, leadingSmallFloatIcon, leadingWallpaperIcon, } from '../icons';
17
17
  import { handleEnterKey } from '../lib/navigation-utils';
18
18
  import BlockView from './block_view';
19
19
  import { createNodeView } from './creators';
20
+ const LAYOUT_OPTIONS_CONFIG = [
21
+ {
22
+ label: 'Hero Image',
23
+ icon: leadingHeroImageIcon,
24
+ attr: 'leading',
25
+ },
26
+ {
27
+ label: 'Float Image - Small',
28
+ icon: leadingSmallFloatIcon,
29
+ attr: 'leading-small-float',
30
+ },
31
+ {
32
+ label: 'Float Image - Large',
33
+ icon: leadingLargeFloatIcon,
34
+ attr: 'leading-large-float',
35
+ },
36
+ {
37
+ label: 'Wallpaper Image',
38
+ icon: leadingWallpaperIcon,
39
+ attr: 'leading-wallpaper',
40
+ },
41
+ {
42
+ label: 'Half Image',
43
+ icon: leadingHalfLeftIcon,
44
+ attr: 'leading-half-left',
45
+ },
46
+ ];
20
47
  export class HeroImageView extends BlockView {
21
48
  constructor() {
22
49
  super(...arguments);
23
50
  this.collapsed = false;
51
+ this.layoutPanelCollapsed = false;
24
52
  this.ignoreMutation = () => true;
25
53
  this.createElement = () => {
26
54
  this.container = document.createElement('div');
27
55
  this.container.classList.add('block', 'hero-image-container');
28
56
  this.dom.appendChild(this.container);
57
+ this.abortController = new AbortController();
29
58
  this.container.appendChild(this.createPanel());
59
+ this.container.appendChild(this.createLayoutPanel());
60
+ this.layoutOptions = this.createLayoutOptions();
61
+ this.layoutOptions.addEventListener('change', this.onSelectOption, {
62
+ signal: this.abortController.signal,
63
+ });
64
+ this.container.appendChild(this.layoutOptions);
30
65
  this.contentDOM = document.createElement('figure');
31
66
  this.contentDOM.classList.add('figure-block', 'hero-image-figure');
32
67
  this.contentDOM.setAttribute('id', this.node.attrs.id);
33
68
  this.container.appendChild(this.contentDOM);
34
69
  };
70
+ this.onSelectOption = (e) => {
71
+ const target = e.target;
72
+ if (target && target.type === 'radio' && target.name === 'layout-option') {
73
+ const { state, dispatch } = this.view;
74
+ const tr = state.tr.setNodeMarkup(this.getPos(), undefined, {
75
+ ...this.node.attrs,
76
+ type: target.value,
77
+ });
78
+ dispatch(tr);
79
+ }
80
+ };
35
81
  }
36
82
  createPanel() {
37
83
  const panel = document.createElement('div');
@@ -51,10 +97,71 @@ export class HeroImageView extends BlockView {
51
97
  heroImageToggleBtn.classList.toggle('collapsed', this.collapsed);
52
98
  };
53
99
  heroImageToggleBtn.onclick = handleToggle;
54
- heroImageToggleBtn.addEventListener('keydown', handleEnterKey(handleToggle));
100
+ heroImageToggleBtn.addEventListener('keydown', handleEnterKey(handleToggle), {
101
+ signal: this.abortController.signal,
102
+ });
55
103
  panel.appendChild(label);
56
104
  panel.appendChild(heroImageToggleBtn);
57
105
  return panel;
58
106
  }
107
+ createLayoutPanel() {
108
+ const panel = document.createElement('div');
109
+ panel.classList.add('panel-header', 'layout-header');
110
+ const label = document.createElement('span');
111
+ label.textContent = 'Layout';
112
+ label.contentEditable = 'false';
113
+ const toggleBtn = document.createElement('button');
114
+ toggleBtn.classList.add('toggle-btn', 'button-reset');
115
+ toggleBtn.innerHTML = arrowUp;
116
+ toggleBtn.classList.toggle('collapsed', this.layoutPanelCollapsed);
117
+ const handleToggle = () => {
118
+ this.layoutPanelCollapsed = !this.layoutPanelCollapsed;
119
+ if (this.layoutOptions) {
120
+ this.layoutOptions.style.display = this.layoutPanelCollapsed
121
+ ? 'none'
122
+ : '';
123
+ }
124
+ toggleBtn.classList.toggle('collapsed', this.layoutPanelCollapsed);
125
+ };
126
+ toggleBtn.onclick = handleToggle;
127
+ toggleBtn.addEventListener('keydown', handleEnterKey(handleToggle), {
128
+ signal: this.abortController.signal,
129
+ });
130
+ panel.appendChild(label);
131
+ panel.appendChild(toggleBtn);
132
+ return panel;
133
+ }
134
+ createLayoutOptions() {
135
+ const can = this.props.getCapabilities();
136
+ const optionsGroup = document.createElement('div');
137
+ optionsGroup.className = 'layout-options-group';
138
+ LAYOUT_OPTIONS_CONFIG.forEach(({ label, icon, attr }) => {
139
+ const image = document.createElement('span');
140
+ image.className = 'layout-option-image';
141
+ image.innerHTML = icon;
142
+ const input = document.createElement('input');
143
+ input.type = 'radio';
144
+ input.name = 'layout-option';
145
+ input.value = attr;
146
+ input.disabled = !can.editMetadata;
147
+ input.checked = attr === this.node.attrs.type;
148
+ const inputLabel = document.createElement('span');
149
+ inputLabel.className = 'layout-option-label';
150
+ inputLabel.textContent = label;
151
+ const wrapper = document.createElement('span');
152
+ wrapper.className = 'layout-option-label-wrapper';
153
+ wrapper.append(input, inputLabel);
154
+ const container = document.createElement('div');
155
+ container.className = 'layout-option';
156
+ container.append(image, wrapper);
157
+ optionsGroup.appendChild(container);
158
+ });
159
+ optionsGroup.contentEditable = 'false';
160
+ return optionsGroup;
161
+ }
162
+ destroy() {
163
+ this.abortController?.abort();
164
+ super.destroy();
165
+ }
59
166
  }
60
167
  export default createNodeView(HeroImageView);
@@ -16,6 +16,11 @@ export declare const fileCorruptedIcon: string;
16
16
  export declare const draggableIcon: string;
17
17
  export declare const translateIcon: string;
18
18
  export declare const linkIcon: string;
19
+ export declare const leadingHeroImageIcon: string;
20
+ export declare const leadingSmallFloatIcon: string;
21
+ export declare const leadingLargeFloatIcon: string;
22
+ export declare const leadingWallpaperIcon: string;
23
+ export declare const leadingHalfLeftIcon: string;
19
24
  export declare const fileMainDocumentIcon: string;
20
25
  export declare const tickIcon: string;
21
26
  export declare const ORCIDIcon: string;
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "3.13.18";
1
+ export declare const VERSION = "3.13.19";
2
2
  export declare const MATHJAX_VERSION = "3.2.2";
@@ -13,15 +13,22 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { FigureElementNode } from '@manuscripts/transform';
16
+ import { HeroImageNode } from '@manuscripts/transform';
17
17
  import { Trackable } from '../types';
18
18
  import BlockView from './block_view';
19
- export declare class HeroImageView extends BlockView<Trackable<FigureElementNode>> {
19
+ export declare class HeroImageView extends BlockView<Trackable<HeroImageNode>> {
20
20
  private container;
21
+ private layoutOptions;
22
+ private abortController;
21
23
  private collapsed;
24
+ private layoutPanelCollapsed;
22
25
  ignoreMutation: () => boolean;
23
26
  createElement: () => void;
24
27
  createPanel(): HTMLDivElement;
28
+ createLayoutPanel(): HTMLDivElement;
29
+ createLayoutOptions(): HTMLDivElement;
30
+ private onSelectOption;
31
+ destroy(): void;
25
32
  }
26
33
  declare const _default: (props: import("../configs/ManuscriptsEditor").EditorProps, dispatch?: import("..").Dispatch) => import("../types").NodeViewCreator<HeroImageView>;
27
34
  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": "3.13.18",
4
+ "version": "3.13.19",
5
5
  "repository": "github:Atypon-OpenSource/manuscripts-body-editor",
6
6
  "license": "Apache-2.0",
7
7
  "main": "dist/cjs",
@@ -41,7 +41,7 @@
41
41
  "@citation-js/plugin-pubmed": "0.3.0",
42
42
  "@citation-js/plugin-ris": "0.7.18",
43
43
  "@iarna/word-count": "1.1.2",
44
- "@manuscripts/style-guide": "3.6.5",
44
+ "@manuscripts/style-guide": "3.6.6",
45
45
  "@manuscripts/track-changes-plugin": "2.4.0",
46
46
  "@manuscripts/transform": "4.4.6",
47
47
  "@popperjs/core": "2.11.8",
package/src/icons.ts CHANGED
@@ -29,6 +29,11 @@ import {
29
29
  ImageRightIcon,
30
30
  LinkIcon,
31
31
  LockIcon,
32
+ LeadingHalfLeftIcon,
33
+ LeadingHeroImageIcon,
34
+ LeadingLargeFloatIcon,
35
+ LeadingSmallFloatIcon,
36
+ LeadingWallpaperIcon,
32
37
  ORCIDIcon as ORCID,
33
38
  PlusIcon,
34
39
  ScrollIcon,
@@ -59,6 +64,11 @@ export const fileCorruptedIcon = renderIcon(FileCorruptedIcon)
59
64
  export const draggableIcon = renderIcon(DraggableIcon)
60
65
  export const translateIcon = renderIcon(TranslateIcon)
61
66
  export const linkIcon = renderIcon(LinkIcon)
67
+ export const leadingHeroImageIcon = renderIcon(LeadingHeroImageIcon)
68
+ export const leadingSmallFloatIcon = renderIcon(LeadingSmallFloatIcon)
69
+ export const leadingLargeFloatIcon = renderIcon(LeadingLargeFloatIcon)
70
+ export const leadingWallpaperIcon = renderIcon(LeadingWallpaperIcon)
71
+ export const leadingHalfLeftIcon = renderIcon(LeadingHalfLeftIcon)
62
72
  export const fileMainDocumentIcon = renderIcon(FileMainDocumentIcon)
63
73
  export const tickIcon = renderIcon(TickIcon)
64
74
  export const ORCIDIcon = renderIcon(ORCID)
package/src/versions.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  //THIS FILE WAS GENERATED BY versions.mjs
2
- export const VERSION = '3.13.18';
2
+ export const VERSION = '3.13.19';
3
3
  export const MATHJAX_VERSION = '3.2.2';
@@ -14,17 +14,55 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import { FigureElementNode } from '@manuscripts/transform'
18
-
19
- import { arrowUp } from '../icons'
17
+ import { HeroImageNode } from '@manuscripts/transform'
18
+
19
+ import {
20
+ arrowUp,
21
+ leadingHalfLeftIcon,
22
+ leadingHeroImageIcon,
23
+ leadingLargeFloatIcon,
24
+ leadingSmallFloatIcon,
25
+ leadingWallpaperIcon,
26
+ } from '../icons'
20
27
  import { handleEnterKey } from '../lib/navigation-utils'
21
28
  import { Trackable } from '../types'
22
29
  import BlockView from './block_view'
23
30
  import { createNodeView } from './creators'
24
31
 
25
- export class HeroImageView extends BlockView<Trackable<FigureElementNode>> {
32
+ const LAYOUT_OPTIONS_CONFIG = [
33
+ {
34
+ label: 'Hero Image',
35
+ icon: leadingHeroImageIcon,
36
+ attr: 'leading',
37
+ },
38
+ {
39
+ label: 'Float Image - Small',
40
+ icon: leadingSmallFloatIcon,
41
+ attr: 'leading-small-float',
42
+ },
43
+ {
44
+ label: 'Float Image - Large',
45
+ icon: leadingLargeFloatIcon,
46
+ attr: 'leading-large-float',
47
+ },
48
+ {
49
+ label: 'Wallpaper Image',
50
+ icon: leadingWallpaperIcon,
51
+ attr: 'leading-wallpaper',
52
+ },
53
+ {
54
+ label: 'Half Image',
55
+ icon: leadingHalfLeftIcon,
56
+ attr: 'leading-half-left',
57
+ },
58
+ ]
59
+
60
+ export class HeroImageView extends BlockView<Trackable<HeroImageNode>> {
26
61
  private container: HTMLElement
62
+ private layoutOptions: HTMLElement
63
+ private abortController: AbortController
27
64
  private collapsed = false
65
+ private layoutPanelCollapsed = false
28
66
 
29
67
  public ignoreMutation = () => true
30
68
 
@@ -33,7 +71,17 @@ export class HeroImageView extends BlockView<Trackable<FigureElementNode>> {
33
71
  this.container.classList.add('block', 'hero-image-container')
34
72
  this.dom.appendChild(this.container)
35
73
 
74
+ this.abortController = new AbortController()
75
+
36
76
  this.container.appendChild(this.createPanel())
77
+ this.container.appendChild(this.createLayoutPanel())
78
+
79
+ this.layoutOptions = this.createLayoutOptions()
80
+ this.layoutOptions.addEventListener('change', this.onSelectOption, {
81
+ signal: this.abortController.signal,
82
+ })
83
+
84
+ this.container.appendChild(this.layoutOptions)
37
85
 
38
86
  this.contentDOM = document.createElement('figure')
39
87
  this.contentDOM.classList.add('figure-block', 'hero-image-figure')
@@ -65,12 +113,103 @@ export class HeroImageView extends BlockView<Trackable<FigureElementNode>> {
65
113
  }
66
114
 
67
115
  heroImageToggleBtn.onclick = handleToggle
68
- heroImageToggleBtn.addEventListener('keydown', handleEnterKey(handleToggle))
116
+ heroImageToggleBtn.addEventListener(
117
+ 'keydown',
118
+ handleEnterKey(handleToggle),
119
+ {
120
+ signal: this.abortController.signal,
121
+ }
122
+ )
69
123
 
70
124
  panel.appendChild(label)
71
125
  panel.appendChild(heroImageToggleBtn)
72
126
  return panel
73
127
  }
128
+
129
+ createLayoutPanel() {
130
+ const panel = document.createElement('div')
131
+ panel.classList.add('panel-header', 'layout-header')
132
+
133
+ const label = document.createElement('span')
134
+ label.textContent = 'Layout'
135
+ label.contentEditable = 'false'
136
+
137
+ const toggleBtn = document.createElement('button')
138
+ toggleBtn.classList.add('toggle-btn', 'button-reset')
139
+
140
+ toggleBtn.innerHTML = arrowUp
141
+ toggleBtn.classList.toggle('collapsed', this.layoutPanelCollapsed)
142
+
143
+ const handleToggle = () => {
144
+ this.layoutPanelCollapsed = !this.layoutPanelCollapsed
145
+ if (this.layoutOptions) {
146
+ this.layoutOptions.style.display = this.layoutPanelCollapsed
147
+ ? 'none'
148
+ : ''
149
+ }
150
+ toggleBtn.classList.toggle('collapsed', this.layoutPanelCollapsed)
151
+ }
152
+
153
+ toggleBtn.onclick = handleToggle
154
+ toggleBtn.addEventListener('keydown', handleEnterKey(handleToggle), {
155
+ signal: this.abortController.signal,
156
+ })
157
+
158
+ panel.appendChild(label)
159
+ panel.appendChild(toggleBtn)
160
+ return panel
161
+ }
162
+
163
+ createLayoutOptions() {
164
+ const can = this.props.getCapabilities()
165
+ const optionsGroup = document.createElement('div')
166
+ optionsGroup.className = 'layout-options-group'
167
+
168
+ LAYOUT_OPTIONS_CONFIG.forEach(({ label, icon, attr }) => {
169
+ const image = document.createElement('span')
170
+ image.className = 'layout-option-image'
171
+ image.innerHTML = icon
172
+
173
+ const input = document.createElement('input')
174
+ input.type = 'radio'
175
+ input.name = 'layout-option'
176
+ input.value = attr
177
+ input.disabled = !can.editMetadata
178
+ input.checked = attr === this.node.attrs.type
179
+
180
+ const inputLabel = document.createElement('span')
181
+ inputLabel.className = 'layout-option-label'
182
+ inputLabel.textContent = label
183
+
184
+ const wrapper = document.createElement('span')
185
+ wrapper.className = 'layout-option-label-wrapper'
186
+ wrapper.append(input, inputLabel)
187
+
188
+ const container = document.createElement('div')
189
+ container.className = 'layout-option'
190
+ container.append(image, wrapper)
191
+ optionsGroup.appendChild(container)
192
+ })
193
+ optionsGroup.contentEditable = 'false'
194
+ return optionsGroup
195
+ }
196
+
197
+ private onSelectOption = (e: Event) => {
198
+ const target = e.target as HTMLInputElement
199
+ if (target && target.type === 'radio' && target.name === 'layout-option') {
200
+ const { state, dispatch } = this.view
201
+ const tr = state.tr.setNodeMarkup(this.getPos(), undefined, {
202
+ ...this.node.attrs,
203
+ type: target.value,
204
+ })
205
+ dispatch(tr)
206
+ }
207
+ }
208
+
209
+ destroy() {
210
+ this.abortController?.abort()
211
+ super.destroy()
212
+ }
74
213
  }
75
214
 
76
215
  export default createNodeView(HeroImageView)
@@ -1754,6 +1754,63 @@ th:hover > .table-context-menu-button,
1754
1754
  border-bottom: 1px solid #c9c9c9;
1755
1755
  }
1756
1756
 
1757
+ .ProseMirror .layout-header {
1758
+ font-size: 18px;
1759
+ font-weight: 400;
1760
+ padding: 4px 3px 4px 0;
1761
+ margin: 8px;
1762
+ background: unset;
1763
+ border: none;
1764
+ }
1765
+
1766
+ .ProseMirror .layout-options-group {
1767
+ display: flex;
1768
+ justify-content: center;
1769
+ gap: 16px;
1770
+ padding: 16px 0;
1771
+ margin: 0 8px;
1772
+ border-bottom: 1px solid #e2e2e2;
1773
+ }
1774
+
1775
+ .ProseMirror .layout-option-image {
1776
+ width: 120px;
1777
+ height: 114px;
1778
+ margin-bottom: 8px;
1779
+ border-radius: 5px 5px 9px 9px;
1780
+ border: 1px solid #e2e2e2;
1781
+ background: #fafafa;
1782
+ display: flex;
1783
+ align-items: center;
1784
+ justify-content: center;
1785
+ }
1786
+
1787
+ .ProseMirror .layout-options-group .layout-option-label-wrapper {
1788
+ display: flex;
1789
+ width: 120px;
1790
+ align-items: flex-start;
1791
+ gap: 6px;
1792
+ }
1793
+
1794
+ .ProseMirror .layout-options-group input[type="radio"] {
1795
+ accent-color: #1a9bc7;
1796
+ width: 16px;
1797
+ height: 16px;
1798
+ }
1799
+
1800
+ .ProseMirror .layout-options-group input[type="radio"]:focus-visible {
1801
+ outline: 1px solid var(--focus-outline-color);
1802
+ outline-offset: 2px;
1803
+ }
1804
+
1805
+ .ProseMirror .layout-option-label {
1806
+ color: #353535;
1807
+ font-size: 14px;
1808
+ font-style: normal;
1809
+ font-weight: 400;
1810
+ line-height: 24px;
1811
+ user-select: none;
1812
+ }
1813
+
1757
1814
  .hero-image-container .figure-block {
1758
1815
  border: none !important;
1759
1816
  padding: 8px;