@manuscripts/body-editor 1.7.16 → 1.7.17-LEAN-3415-1

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.
@@ -21,6 +21,7 @@ const prosemirror_state_1 = require("prosemirror-state");
21
21
  const prosemirror_view_1 = require("prosemirror-view");
22
22
  const track_changes_utils_1 = require("../lib/track-changes-utils");
23
23
  exports.affiliationsKey = new prosemirror_state_1.PluginKey('affiliations');
24
+ let id = 1;
24
25
  const buildPluginState = (doc) => {
25
26
  const contributors = [];
26
27
  const affiliations = [];
@@ -47,13 +48,13 @@ const buildPluginState = (doc) => {
47
48
  });
48
49
  const indexedAffiliationIds = new Map([...iAffiliations].map((id, i) => [id, i + 1]));
49
50
  return {
51
+ version: String(id++),
50
52
  indexedAffiliationIds,
51
53
  contributors,
52
54
  affiliations,
53
55
  };
54
56
  };
55
57
  exports.buildPluginState = buildPluginState;
56
- let count = 0;
57
58
  exports.default = () => {
58
59
  return new prosemirror_state_1.Plugin({
59
60
  key: exports.affiliationsKey,
@@ -98,19 +99,13 @@ exports.default = () => {
98
99
  props: {
99
100
  decorations: (state) => {
100
101
  const decorations = [];
101
- const allNodes = [];
102
- state.doc.descendants((node, pos) => {
103
- if ((0, transform_1.isAffiliationNode)(node) || (0, transform_1.isContributorNode)(node)) {
104
- allNodes.push([node, pos]);
105
- }
102
+ const aff = exports.affiliationsKey.getState(state);
103
+ const nodes = [...aff.contributors, ...aff.affiliations];
104
+ nodes.forEach(([node, pos]) => {
105
+ decorations.push(prosemirror_view_1.Decoration.node(pos, pos + node.nodeSize, {
106
+ version: aff.version,
107
+ }));
106
108
  });
107
- if (allNodes.length) {
108
- allNodes.forEach(([node, pos]) => {
109
- decorations.push(prosemirror_view_1.Decoration.node(pos, pos + node.nodeSize, {}, {
110
- refresh: count++,
111
- }));
112
- });
113
- }
114
109
  return prosemirror_view_1.DecorationSet.create(state.doc, decorations);
115
110
  },
116
111
  },
@@ -34,7 +34,6 @@ const lodash_1 = require("lodash");
34
34
  const prosemirror_view_1 = require("prosemirror-view");
35
35
  const isBibliographyElement = (node) => node.type === node.type.schema.nodes.bibliography_element;
36
36
  exports.isBibliographyElement = isBibliographyElement;
37
- let count = 0;
38
37
  const buildDecorations = (state, doc) => {
39
38
  const decorations = [];
40
39
  let hasMissingItems = false;
@@ -56,14 +55,14 @@ const buildDecorations = (state, doc) => {
56
55
  }));
57
56
  }
58
57
  decorations.push(prosemirror_view_1.Decoration.node(pos, pos + node.nodeSize, {
59
- 'data-updated': Date.now().toString(),
58
+ version: state.version,
60
59
  }));
61
60
  }
62
61
  if (hasMissingItems) {
63
62
  doc.descendants((node, pos) => {
64
63
  if ((0, exports.isBibliographyElement)(node)) {
65
- decorations.push(prosemirror_view_1.Decoration.node(pos, pos + node.nodeSize, {}, {
66
- missing: true,
64
+ decorations.push(prosemirror_view_1.Decoration.node(pos, pos + node.nodeSize, {
65
+ missing: 'true',
67
66
  }));
68
67
  decorations.push(prosemirror_view_1.Decoration.widget(pos, () => {
69
68
  const el = document.createElement('div');
@@ -76,8 +75,8 @@ const buildDecorations = (state, doc) => {
76
75
  }
77
76
  doc.descendants((node, pos) => {
78
77
  if ((0, exports.isBibliographyElement)(node)) {
79
- decorations.push(prosemirror_view_1.Decoration.node(pos, pos + node.nodeSize, {}, {
80
- refresh: count++,
78
+ decorations.push(prosemirror_view_1.Decoration.node(pos, pos + node.nodeSize, {
79
+ version: state.version,
81
80
  }));
82
81
  }
83
82
  });
@@ -46,6 +46,7 @@ exports.default = (props) => {
46
46
  },
47
47
  });
48
48
  };
49
+ let version = 1;
49
50
  const buildBibliographyPluginState = (doc, csl, $old) => {
50
51
  const nodes = [];
51
52
  doc.descendants((node, pos) => {
@@ -66,6 +67,7 @@ const buildBibliographyPluginState = (doc, csl, $old) => {
66
67
  if ($old &&
67
68
  (0, lodash_1.isEqual)(citations, $old.citations) &&
68
69
  (0, lodash_1.isEqual)(bibliographyItemMap, $old.bibliographyItems)) {
70
+ $new.version = $old.version;
69
71
  $new.citationCounts = $old.citationCounts;
70
72
  $new.provider = $old.provider;
71
73
  $new.renderedCitations = $old.renderedCitations;
@@ -83,6 +85,7 @@ const buildBibliographyPluginState = (doc, csl, $old) => {
83
85
  locale: csl.locale,
84
86
  });
85
87
  const citationTexts = provider.rebuildState((0, bibliography_utils_1.buildCitations)(nodes));
88
+ $new.version = String(version++);
86
89
  $new.citationCounts = citationCounts;
87
90
  $new.provider = provider;
88
91
  $new.renderedCitations = new Map(citationTexts.map((i) => [i[0], i[2]]));
@@ -15,7 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.selectedSuggestionKey = exports.SET_SUGGESTION_ID = void 0;
18
+ exports.selectedSuggestionKey = exports.CLEAR_SUGGESTION_ID = exports.SET_SUGGESTION_ID = void 0;
19
19
  const track_changes_plugin_1 = require("@manuscripts/track-changes-plugin");
20
20
  const lodash_1 = require("lodash");
21
21
  const prosemirror_state_1 = require("prosemirror-state");
@@ -81,7 +81,7 @@ const getTrackChangesData = (node) => {
81
81
  return (_a = trackChangesMark === null || trackChangesMark === void 0 ? void 0 : trackChangesMark.attrs) === null || _a === void 0 ? void 0 : _a.dataTracked;
82
82
  };
83
83
  exports.SET_SUGGESTION_ID = 'SET_SELECTED_SUGGESTION_ID';
84
- const CLEAR_SUGGESTION_ID = 'CLEAR_SUGGESTION_ID';
84
+ exports.CLEAR_SUGGESTION_ID = 'CLEAR_SUGGESTION_ID';
85
85
  exports.selectedSuggestionKey = new prosemirror_state_1.PluginKey('selected_suggestion');
86
86
  exports.default = (props) => new prosemirror_state_1.Plugin({
87
87
  key: exports.selectedSuggestionKey,
@@ -93,8 +93,7 @@ exports.default = (props) => new prosemirror_state_1.Plugin({
93
93
  if (suggestionId) {
94
94
  return buildSelectedSuggestionDecoration(suggestionId, state, props);
95
95
  }
96
- if (tr.getMeta(CLEAR_SUGGESTION_ID) ||
97
- tr.getMeta('track-changes-refresh-changes')) {
96
+ if (tr.getMeta(exports.CLEAR_SUGGESTION_ID)) {
98
97
  props.setEditorSelectedSuggestion(undefined);
99
98
  return prosemirror_view_1.DecorationSet.empty;
100
99
  }
@@ -106,7 +105,7 @@ exports.default = (props) => new prosemirror_state_1.Plugin({
106
105
  const nodeClicked = view.state.doc.nodeAt(pos);
107
106
  const trackChangesData = nodeClicked && getTrackChangesData(nodeClicked);
108
107
  if (!trackChangesData) {
109
- view.dispatch(view.state.tr.setMeta(CLEAR_SUGGESTION_ID, true));
108
+ view.dispatch(view.state.tr.setMeta(exports.CLEAR_SUGGESTION_ID, true));
110
109
  props.setEditorSelectedSuggestion(undefined);
111
110
  }
112
111
  },
@@ -0,0 +1,12 @@
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.ContextMenuWrapper = void 0;
7
+ const style_guide_1 = require("@manuscripts/style-guide");
8
+ const react_1 = __importDefault(require("react"));
9
+ const ContextMenuWrapper = (props) => {
10
+ return react_1.default.createElement(style_guide_1.ContextMenu, Object.assign({}, props));
11
+ };
12
+ exports.ContextMenuWrapper = ContextMenuWrapper;
@@ -30,13 +30,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
30
30
  exports.BibliographyElementBlockView = void 0;
31
31
  const track_changes_plugin_1 = require("@manuscripts/track-changes-plugin");
32
32
  const transform_1 = require("@manuscripts/transform");
33
- const assets_1 = require("../assets");
34
33
  const dompurify_1 = require("../lib/dompurify");
35
34
  const track_changes_utils_1 = require("../lib/track-changes-utils");
36
35
  const bibliography_1 = require("../plugins/bibliography");
37
36
  const comment_annotation_1 = require("../plugins/comment_annotation");
38
37
  const selected_suggestion_ui_1 = require("../plugins/selected-suggestion-ui");
39
38
  const block_view_1 = __importDefault(require("./block_view"));
39
+ const ContextMenuWrapper_1 = require("./ContextMenuWrapper");
40
40
  const creators_1 = require("./creators");
41
41
  const ReactSubView_1 = __importDefault(require("./ReactSubView"));
42
42
  const ReferencesEditor_1 = require("./ReferencesEditor");
@@ -44,7 +44,7 @@ class BibliographyElementBlockView extends block_view_1.default {
44
44
  constructor() {
45
45
  super(...arguments);
46
46
  this.decoder = new transform_1.Decoder(new Map());
47
- this.gutterButtons = () => [];
47
+ this.elementWithContextMenu = null;
48
48
  this.showPopper = (id) => {
49
49
  const bib = (0, bibliography_1.getBibliographyPluginState)(this.view.state);
50
50
  const componentProps = {
@@ -59,7 +59,44 @@ class BibliographyElementBlockView extends block_view_1.default {
59
59
  };
60
60
  this.stopEvent = () => true;
61
61
  this.ignoreMutation = () => true;
62
+ this.handleEdit = (citationId) => {
63
+ this.props.popper.destroy();
64
+ this.showPopper(citationId);
65
+ };
66
+ this.handleComment = (citationId) => {
67
+ this.props.setComment((0, transform_1.buildComment)(citationId));
68
+ };
69
+ this.showContextMenu = (element) => {
70
+ const componentProps = {
71
+ actions: [
72
+ {
73
+ label: 'Edit',
74
+ action: () => this.handleEdit(element.id),
75
+ icon: 'EditIcon',
76
+ },
77
+ {
78
+ label: 'Comment',
79
+ action: () => this.handleComment(element.id),
80
+ icon: 'AddComment',
81
+ },
82
+ ],
83
+ };
84
+ this.editor = (0, ReactSubView_1.default)(this.props, ContextMenuWrapper_1.ContextMenuWrapper, componentProps, this.node, this.getPos, this.view, 'context-menu');
85
+ this.props.popper.show(element, this.editor, 'right-start');
86
+ };
87
+ this.onClickHandler = (element, dataTracked) => {
88
+ this.elementWithContextMenu = null;
89
+ this.props.popper.destroy();
90
+ this.elementWithContextMenu = element;
91
+ this.view.dispatch(this.view.state.tr.setMeta(selected_suggestion_ui_1.CLEAR_SUGGESTION_ID, true));
92
+ if (dataTracked && dataTracked.status !== track_changes_plugin_1.CHANGE_STATUS.rejected) {
93
+ this.view.dispatch(this.view.state.tr
94
+ .setMeta(selected_suggestion_ui_1.CLEAR_SUGGESTION_ID, false)
95
+ .setMeta(selected_suggestion_ui_1.SET_SUGGESTION_ID, dataTracked.id));
96
+ }
97
+ };
62
98
  this.updateContents = () => __awaiter(this, void 0, void 0, function* () {
99
+ var _a;
63
100
  const bib = (0, bibliography_1.getBibliographyPluginState)(this.view.state);
64
101
  const commentsDecorationSet = comment_annotation_1.commentAnnotation.getState(this.view.state);
65
102
  const selectedSuggestion = selected_suggestion_ui_1.selectedSuggestionKey.getState(this.view.state);
@@ -93,28 +130,6 @@ class BibliographyElementBlockView extends block_view_1.default {
93
130
  const id = meta.entry_ids[i];
94
131
  const fragment = bibliography[i];
95
132
  const element = (0, dompurify_1.sanitize)(`<div id="${id}" class="bib-item"><div class="csl-bib-body">${fragment}</div></div>`).firstElementChild;
96
- const doubleButton = document.createElement('div');
97
- const editButton = document.createElement('button');
98
- const commentButton = document.createElement('button');
99
- doubleButton.className = 'bibliography-double-button';
100
- editButton.className = 'bibliography-edit-button';
101
- commentButton.className = 'bibliography-comment-button';
102
- commentButton.addEventListener('click', (e) => {
103
- e.preventDefault();
104
- this.props.setComment((0, transform_1.buildComment)(element.id));
105
- });
106
- editButton.addEventListener('click', (e) => {
107
- e.preventDefault();
108
- this.showPopper(element.id);
109
- });
110
- editButton.innerHTML = assets_1.editIcon;
111
- commentButton.innerHTML = assets_1.commentIcon;
112
- doubleButton.append(editButton, commentButton);
113
- if (can.seeReferencesButtons &&
114
- !element.querySelector('.bibliography-double-button')) {
115
- element.appendChild(doubleButton);
116
- }
117
- editButton.disabled = !can.editCitationsAndRefs;
118
133
  const commentElement = commentElementMap.get(element.id);
119
134
  if (commentElement) {
120
135
  element.appendChild(commentElement);
@@ -122,16 +137,17 @@ class BibliographyElementBlockView extends block_view_1.default {
122
137
  const dataTracked = dataTrackedMap.get(element.id);
123
138
  if (dataTracked) {
124
139
  element.classList.add('attrs-track-mark', ...(0, track_changes_utils_1.getChangeClasses)([dataTracked]));
125
- if (dataTracked.status === track_changes_plugin_1.CHANGE_STATUS.pending &&
126
- dataTracked.operation === track_changes_plugin_1.CHANGE_OPERATION.set_node_attributes) {
127
- element.appendChild((0, track_changes_utils_1.getAttrsTrackingButton)(dataTracked.id));
128
- }
129
- this.addClickListenerToBibItem(element, dataTracked);
130
140
  }
131
141
  if (selectedBibItemSuggestion &&
132
142
  selectedBibItemSuggestion === element.id) {
133
143
  element.classList.add('selected-suggestion');
134
144
  }
145
+ if (can.seeReferencesButtons) {
146
+ element.addEventListener('click', () => this.onClickHandler(element, dataTracked), false);
147
+ }
148
+ if (element.id === ((_a = this.elementWithContextMenu) === null || _a === void 0 ? void 0 : _a.id)) {
149
+ this.elementWithContextMenu = element;
150
+ }
135
151
  wrapper.append(element);
136
152
  }
137
153
  const oldContent = this.container.querySelector('.contents');
@@ -141,6 +157,9 @@ class BibliographyElementBlockView extends block_view_1.default {
141
157
  else {
142
158
  this.container.appendChild(wrapper);
143
159
  }
160
+ if (this.elementWithContextMenu) {
161
+ this.showContextMenu(this.elementWithContextMenu);
162
+ }
144
163
  });
145
164
  this.createElement = () => {
146
165
  this.container = document.createElement('div');
@@ -156,13 +175,6 @@ class BibliographyElementBlockView extends block_view_1.default {
156
175
  this.handleDelete = (item) => {
157
176
  return this.deleteNode(item._id);
158
177
  };
159
- this.addClickListenerToBibItem = (element, dataTracked) => {
160
- if (dataTracked.status !== track_changes_plugin_1.CHANGE_STATUS.rejected) {
161
- element.addEventListener('click', () => {
162
- this.view.dispatch(this.view.state.tr.setMeta(selected_suggestion_ui_1.SET_SUGGESTION_ID, dataTracked.id));
163
- });
164
- }
165
- };
166
178
  }
167
179
  }
168
180
  exports.BibliographyElementBlockView = BibliographyElementBlockView;
@@ -14,15 +14,9 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- var __importDefault = (this && this.__importDefault) || function (mod) {
18
- return (mod && mod.__esModule) ? mod : { "default": mod };
19
- };
20
17
  Object.defineProperty(exports, "__esModule", { value: true });
21
18
  exports.CitationView = void 0;
22
19
  const prosemirror_model_1 = require("prosemirror-model");
23
- const react_1 = require("react");
24
- const react_dom_1 = __importDefault(require("react-dom"));
25
- const TrackChangesReview_1 = require("../components/track-changes/TrackChangesReview");
26
20
  const dompurify_1 = require("../lib/dompurify");
27
21
  const track_changes_utils_1 = require("../lib/track-changes-utils");
28
22
  const bibliography_1 = require("../plugins/bibliography");
@@ -45,6 +39,7 @@ class CitationView extends base_node_view_1.BaseNodeView {
45
39
  return this;
46
40
  };
47
41
  this.updateContents = () => {
42
+ 11;
48
43
  const bib = (0, bibliography_1.getBibliographyPluginState)(this.view.state);
49
44
  const citation = this.getCitation();
50
45
  const classes = [
@@ -61,24 +56,8 @@ class CitationView extends base_node_view_1.BaseNodeView {
61
56
  this.dom.className = 'citation-wrapper';
62
57
  this.dom.innerHTML = '';
63
58
  this.dom.appendChild(element);
64
- if ((0, track_changes_utils_1.isPendingSetAttrs)(this.node) &&
65
- !(0, track_changes_utils_1.isDeleted)(this.node) &&
66
- citation.embeddedCitationItems.length) {
67
- this.dom.appendChild(this.renderTrackChangesReview());
68
- }
69
59
  this.setDomAttrs(this.node, this.dom, ['rids', 'contents', 'selectedText']);
70
60
  };
71
- this.renderTrackChangesReview = () => {
72
- const { popper } = this.props;
73
- const el = document.createElement('span');
74
- el.classList.add('track-changes-review');
75
- react_dom_1.default.render((0, react_1.createElement)(TrackChangesReview_1.TrackChangesReview, {
76
- node: this.node,
77
- popper,
78
- target: el,
79
- }), el);
80
- return el;
81
- };
82
61
  this.getCitation = () => {
83
62
  const citation = (0, bibliography_utils_1.getCitation)(this.node);
84
63
  if (!citation) {
@@ -28,8 +28,10 @@ const prosemirror_utils_1 = require("prosemirror-utils");
28
28
  const citation_sources_1 = require("../citation-sources");
29
29
  const track_changes_utils_1 = require("../lib/track-changes-utils");
30
30
  const bibliography_1 = require("../plugins/bibliography");
31
+ const selected_suggestion_ui_1 = require("../plugins/selected-suggestion-ui");
31
32
  const citation_1 = require("./citation");
32
33
  const CitationEditorWrapper_1 = require("./CitationEditorWrapper");
34
+ const ContextMenuWrapper_1 = require("./ContextMenuWrapper");
33
35
  const creators_1 = require("./creators");
34
36
  const ReactSubView_1 = __importDefault(require("./ReactSubView"));
35
37
  const createBibliographySection = (node) => transform_1.schema.nodes.bibliography_section.createAndFill({}, [
@@ -41,14 +43,28 @@ class CitationEditableView extends citation_1.CitationView {
41
43
  super(...arguments);
42
44
  this.decoder = new transform_1.Decoder(new Map());
43
45
  this.selectNode = () => {
46
+ var _a;
44
47
  if (!(0, track_changes_utils_1.isDeleted)(this.node)) {
45
- this.showPopper();
46
- this.dom.classList.add('ProseMirror-selectednode');
48
+ const dataTracked = this.node.attrs.dataTracked;
49
+ const isSelectedSuggestion = !!((_a = selected_suggestion_ui_1.selectedSuggestionKey
50
+ .getState(this.view.state)) === null || _a === void 0 ? void 0 : _a.find(this.getPos(), this.getPos() + this.node.nodeSize).length);
51
+ if (dataTracked && !isSelectedSuggestion) {
52
+ this.view.dispatch(this.view.state.tr.setMeta(selected_suggestion_ui_1.SET_SUGGESTION_ID, dataTracked[0].id));
53
+ }
54
+ else {
55
+ const citation = this.getCitation();
56
+ const rids = citation.embeddedCitationItems.map((i) => i.bibliographyItem);
57
+ if (!rids.length) {
58
+ this.showPopper();
59
+ }
60
+ else {
61
+ this.showContextMenu();
62
+ }
63
+ this.dom.classList.add('ProseMirror-selectednode');
64
+ }
47
65
  }
48
66
  };
49
67
  this.deselectNode = () => {
50
- var _a;
51
- (_a = this.editor) === null || _a === void 0 ? void 0 : _a.remove();
52
68
  this.dom.classList.remove('ProseMirror-selectednode');
53
69
  this.props.popper.destroy();
54
70
  };
@@ -57,7 +73,18 @@ class CitationEditableView extends citation_1.CitationView {
57
73
  (_a = this.editor) === null || _a === void 0 ? void 0 : _a.remove();
58
74
  this.props.popper.destroy();
59
75
  };
76
+ this.showContextMenu = () => {
77
+ const componentProps = {
78
+ actions: [
79
+ { label: 'Edit', action: this.handleEdit, icon: 'EditIcon' },
80
+ { label: 'Comment', action: this.handleComment, icon: 'AddComment' },
81
+ ],
82
+ };
83
+ this.editor = (0, ReactSubView_1.default)(this.props, ContextMenuWrapper_1.ContextMenuWrapper, componentProps, this.node, this.getPos, this.view, 'context-menu');
84
+ this.props.popper.show(this.dom, this.editor, 'right-start');
85
+ };
60
86
  this.showPopper = () => {
87
+ this.props.popper.destroy();
61
88
  const can = this.props.getCapabilities();
62
89
  const citation = this.getCitation();
63
90
  const rids = citation.embeddedCitationItems.map((i) => i.bibliographyItem);
@@ -90,6 +117,9 @@ class CitationEditableView extends citation_1.CitationView {
90
117
  }
91
118
  this.props.popper.show(this.dom, this.editor, 'right');
92
119
  };
120
+ this.handleEdit = () => {
121
+ this.showPopper();
122
+ };
93
123
  this.handleCancel = () => {
94
124
  const selection = prosemirror_state_1.TextSelection.create(this.view.state.tr.doc, this.getPos() + 1);
95
125
  this.view.dispatch(this.view.state.tr.setSelection(selection));
@@ -18,6 +18,7 @@ import { Plugin, PluginKey } from 'prosemirror-state';
18
18
  import { Decoration, DecorationSet } from 'prosemirror-view';
19
19
  import { getActualAttrs, isDeleted, isPendingInsert, } from '../lib/track-changes-utils';
20
20
  export const affiliationsKey = new PluginKey('affiliations');
21
+ let id = 1;
21
22
  export const buildPluginState = (doc) => {
22
23
  const contributors = [];
23
24
  const affiliations = [];
@@ -44,12 +45,12 @@ export const buildPluginState = (doc) => {
44
45
  });
45
46
  const indexedAffiliationIds = new Map([...iAffiliations].map((id, i) => [id, i + 1]));
46
47
  return {
48
+ version: String(id++),
47
49
  indexedAffiliationIds,
48
50
  contributors,
49
51
  affiliations,
50
52
  };
51
53
  };
52
- let count = 0;
53
54
  export default () => {
54
55
  return new Plugin({
55
56
  key: affiliationsKey,
@@ -94,19 +95,13 @@ export default () => {
94
95
  props: {
95
96
  decorations: (state) => {
96
97
  const decorations = [];
97
- const allNodes = [];
98
- state.doc.descendants((node, pos) => {
99
- if (isAffiliationNode(node) || isContributorNode(node)) {
100
- allNodes.push([node, pos]);
101
- }
98
+ const aff = affiliationsKey.getState(state);
99
+ const nodes = [...aff.contributors, ...aff.affiliations];
100
+ nodes.forEach(([node, pos]) => {
101
+ decorations.push(Decoration.node(pos, pos + node.nodeSize, {
102
+ version: aff.version,
103
+ }));
102
104
  });
103
- if (allNodes.length) {
104
- allNodes.forEach(([node, pos]) => {
105
- decorations.push(Decoration.node(pos, pos + node.nodeSize, {}, {
106
- refresh: count++,
107
- }));
108
- });
109
- }
110
105
  return DecorationSet.create(state.doc, decorations);
111
106
  },
112
107
  },
@@ -30,7 +30,6 @@ import { schema, } from '@manuscripts/transform';
30
30
  import { pickBy } from 'lodash';
31
31
  import { Decoration } from 'prosemirror-view';
32
32
  export const isBibliographyElement = (node) => node.type === node.type.schema.nodes.bibliography_element;
33
- let count = 0;
34
33
  export const buildDecorations = (state, doc) => {
35
34
  const decorations = [];
36
35
  let hasMissingItems = false;
@@ -52,14 +51,14 @@ export const buildDecorations = (state, doc) => {
52
51
  }));
53
52
  }
54
53
  decorations.push(Decoration.node(pos, pos + node.nodeSize, {
55
- 'data-updated': Date.now().toString(),
54
+ version: state.version,
56
55
  }));
57
56
  }
58
57
  if (hasMissingItems) {
59
58
  doc.descendants((node, pos) => {
60
59
  if (isBibliographyElement(node)) {
61
- decorations.push(Decoration.node(pos, pos + node.nodeSize, {}, {
62
- missing: true,
60
+ decorations.push(Decoration.node(pos, pos + node.nodeSize, {
61
+ missing: 'true',
63
62
  }));
64
63
  decorations.push(Decoration.widget(pos, () => {
65
64
  const el = document.createElement('div');
@@ -72,8 +71,8 @@ export const buildDecorations = (state, doc) => {
72
71
  }
73
72
  doc.descendants((node, pos) => {
74
73
  if (isBibliographyElement(node)) {
75
- decorations.push(Decoration.node(pos, pos + node.nodeSize, {}, {
76
- refresh: count++,
74
+ decorations.push(Decoration.node(pos, pos + node.nodeSize, {
75
+ version: state.version,
77
76
  }));
78
77
  }
79
78
  });
@@ -43,6 +43,7 @@ export default (props) => {
43
43
  },
44
44
  });
45
45
  };
46
+ let version = 1;
46
47
  const buildBibliographyPluginState = (doc, csl, $old) => {
47
48
  const nodes = [];
48
49
  doc.descendants((node, pos) => {
@@ -63,6 +64,7 @@ const buildBibliographyPluginState = (doc, csl, $old) => {
63
64
  if ($old &&
64
65
  isEqual(citations, $old.citations) &&
65
66
  isEqual(bibliographyItemMap, $old.bibliographyItems)) {
67
+ $new.version = $old.version;
66
68
  $new.citationCounts = $old.citationCounts;
67
69
  $new.provider = $old.provider;
68
70
  $new.renderedCitations = $old.renderedCitations;
@@ -80,6 +82,7 @@ const buildBibliographyPluginState = (doc, csl, $old) => {
80
82
  locale: csl.locale,
81
83
  });
82
84
  const citationTexts = provider.rebuildState(buildCitations(nodes));
85
+ $new.version = String(version++);
83
86
  $new.citationCounts = citationCounts;
84
87
  $new.provider = provider;
85
88
  $new.renderedCitations = new Map(citationTexts.map((i) => [i[0], i[2]]));
@@ -78,7 +78,7 @@ const getTrackChangesData = (node) => {
78
78
  return (_a = trackChangesMark === null || trackChangesMark === void 0 ? void 0 : trackChangesMark.attrs) === null || _a === void 0 ? void 0 : _a.dataTracked;
79
79
  };
80
80
  export const SET_SUGGESTION_ID = 'SET_SELECTED_SUGGESTION_ID';
81
- const CLEAR_SUGGESTION_ID = 'CLEAR_SUGGESTION_ID';
81
+ export const CLEAR_SUGGESTION_ID = 'CLEAR_SUGGESTION_ID';
82
82
  export const selectedSuggestionKey = new PluginKey('selected_suggestion');
83
83
  export default (props) => new Plugin({
84
84
  key: selectedSuggestionKey,
@@ -90,8 +90,7 @@ export default (props) => new Plugin({
90
90
  if (suggestionId) {
91
91
  return buildSelectedSuggestionDecoration(suggestionId, state, props);
92
92
  }
93
- if (tr.getMeta(CLEAR_SUGGESTION_ID) ||
94
- tr.getMeta('track-changes-refresh-changes')) {
93
+ if (tr.getMeta(CLEAR_SUGGESTION_ID)) {
95
94
  props.setEditorSelectedSuggestion(undefined);
96
95
  return DecorationSet.empty;
97
96
  }
@@ -0,0 +1,5 @@
1
+ import { ContextMenu } from '@manuscripts/style-guide';
2
+ import React from 'react';
3
+ export const ContextMenuWrapper = (props) => {
4
+ return React.createElement(ContextMenu, Object.assign({}, props));
5
+ };
@@ -22,15 +22,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
22
22
  step((generator = generator.apply(thisArg, _arguments || [])).next());
23
23
  });
24
24
  };
25
- import { CHANGE_OPERATION, CHANGE_STATUS, } from '@manuscripts/track-changes-plugin';
25
+ import { CHANGE_STATUS } from '@manuscripts/track-changes-plugin';
26
26
  import { buildComment, Decoder, } from '@manuscripts/transform';
27
- import { commentIcon, editIcon } from '../assets';
28
27
  import { sanitize } from '../lib/dompurify';
29
- import { getAttrsTrackingButton, getChangeClasses, } from '../lib/track-changes-utils';
28
+ import { getChangeClasses } from '../lib/track-changes-utils';
30
29
  import { getBibliographyPluginState } from '../plugins/bibliography';
31
30
  import { commentAnnotation } from '../plugins/comment_annotation';
32
- import { selectedSuggestionKey, SET_SUGGESTION_ID, } from '../plugins/selected-suggestion-ui';
31
+ import { CLEAR_SUGGESTION_ID, selectedSuggestionKey, SET_SUGGESTION_ID, } from '../plugins/selected-suggestion-ui';
33
32
  import BlockView from './block_view';
33
+ import { ContextMenuWrapper } from './ContextMenuWrapper';
34
34
  import { createNodeView } from './creators';
35
35
  import ReactSubView from './ReactSubView';
36
36
  import { ReferencesEditor } from './ReferencesEditor';
@@ -38,7 +38,7 @@ export class BibliographyElementBlockView extends BlockView {
38
38
  constructor() {
39
39
  super(...arguments);
40
40
  this.decoder = new Decoder(new Map());
41
- this.gutterButtons = () => [];
41
+ this.elementWithContextMenu = null;
42
42
  this.showPopper = (id) => {
43
43
  const bib = getBibliographyPluginState(this.view.state);
44
44
  const componentProps = {
@@ -53,7 +53,44 @@ export class BibliographyElementBlockView extends BlockView {
53
53
  };
54
54
  this.stopEvent = () => true;
55
55
  this.ignoreMutation = () => true;
56
+ this.handleEdit = (citationId) => {
57
+ this.props.popper.destroy();
58
+ this.showPopper(citationId);
59
+ };
60
+ this.handleComment = (citationId) => {
61
+ this.props.setComment(buildComment(citationId));
62
+ };
63
+ this.showContextMenu = (element) => {
64
+ const componentProps = {
65
+ actions: [
66
+ {
67
+ label: 'Edit',
68
+ action: () => this.handleEdit(element.id),
69
+ icon: 'EditIcon',
70
+ },
71
+ {
72
+ label: 'Comment',
73
+ action: () => this.handleComment(element.id),
74
+ icon: 'AddComment',
75
+ },
76
+ ],
77
+ };
78
+ this.editor = ReactSubView(this.props, ContextMenuWrapper, componentProps, this.node, this.getPos, this.view, 'context-menu');
79
+ this.props.popper.show(element, this.editor, 'right-start');
80
+ };
81
+ this.onClickHandler = (element, dataTracked) => {
82
+ this.elementWithContextMenu = null;
83
+ this.props.popper.destroy();
84
+ this.elementWithContextMenu = element;
85
+ this.view.dispatch(this.view.state.tr.setMeta(CLEAR_SUGGESTION_ID, true));
86
+ if (dataTracked && dataTracked.status !== CHANGE_STATUS.rejected) {
87
+ this.view.dispatch(this.view.state.tr
88
+ .setMeta(CLEAR_SUGGESTION_ID, false)
89
+ .setMeta(SET_SUGGESTION_ID, dataTracked.id));
90
+ }
91
+ };
56
92
  this.updateContents = () => __awaiter(this, void 0, void 0, function* () {
93
+ var _a;
57
94
  const bib = getBibliographyPluginState(this.view.state);
58
95
  const commentsDecorationSet = commentAnnotation.getState(this.view.state);
59
96
  const selectedSuggestion = selectedSuggestionKey.getState(this.view.state);
@@ -87,28 +124,6 @@ export class BibliographyElementBlockView extends BlockView {
87
124
  const id = meta.entry_ids[i];
88
125
  const fragment = bibliography[i];
89
126
  const element = sanitize(`<div id="${id}" class="bib-item"><div class="csl-bib-body">${fragment}</div></div>`).firstElementChild;
90
- const doubleButton = document.createElement('div');
91
- const editButton = document.createElement('button');
92
- const commentButton = document.createElement('button');
93
- doubleButton.className = 'bibliography-double-button';
94
- editButton.className = 'bibliography-edit-button';
95
- commentButton.className = 'bibliography-comment-button';
96
- commentButton.addEventListener('click', (e) => {
97
- e.preventDefault();
98
- this.props.setComment(buildComment(element.id));
99
- });
100
- editButton.addEventListener('click', (e) => {
101
- e.preventDefault();
102
- this.showPopper(element.id);
103
- });
104
- editButton.innerHTML = editIcon;
105
- commentButton.innerHTML = commentIcon;
106
- doubleButton.append(editButton, commentButton);
107
- if (can.seeReferencesButtons &&
108
- !element.querySelector('.bibliography-double-button')) {
109
- element.appendChild(doubleButton);
110
- }
111
- editButton.disabled = !can.editCitationsAndRefs;
112
127
  const commentElement = commentElementMap.get(element.id);
113
128
  if (commentElement) {
114
129
  element.appendChild(commentElement);
@@ -116,16 +131,17 @@ export class BibliographyElementBlockView extends BlockView {
116
131
  const dataTracked = dataTrackedMap.get(element.id);
117
132
  if (dataTracked) {
118
133
  element.classList.add('attrs-track-mark', ...getChangeClasses([dataTracked]));
119
- if (dataTracked.status === CHANGE_STATUS.pending &&
120
- dataTracked.operation === CHANGE_OPERATION.set_node_attributes) {
121
- element.appendChild(getAttrsTrackingButton(dataTracked.id));
122
- }
123
- this.addClickListenerToBibItem(element, dataTracked);
124
134
  }
125
135
  if (selectedBibItemSuggestion &&
126
136
  selectedBibItemSuggestion === element.id) {
127
137
  element.classList.add('selected-suggestion');
128
138
  }
139
+ if (can.seeReferencesButtons) {
140
+ element.addEventListener('click', () => this.onClickHandler(element, dataTracked), false);
141
+ }
142
+ if (element.id === ((_a = this.elementWithContextMenu) === null || _a === void 0 ? void 0 : _a.id)) {
143
+ this.elementWithContextMenu = element;
144
+ }
129
145
  wrapper.append(element);
130
146
  }
131
147
  const oldContent = this.container.querySelector('.contents');
@@ -135,6 +151,9 @@ export class BibliographyElementBlockView extends BlockView {
135
151
  else {
136
152
  this.container.appendChild(wrapper);
137
153
  }
154
+ if (this.elementWithContextMenu) {
155
+ this.showContextMenu(this.elementWithContextMenu);
156
+ }
138
157
  });
139
158
  this.createElement = () => {
140
159
  this.container = document.createElement('div');
@@ -150,13 +169,6 @@ export class BibliographyElementBlockView extends BlockView {
150
169
  this.handleDelete = (item) => {
151
170
  return this.deleteNode(item._id);
152
171
  };
153
- this.addClickListenerToBibItem = (element, dataTracked) => {
154
- if (dataTracked.status !== CHANGE_STATUS.rejected) {
155
- element.addEventListener('click', () => {
156
- this.view.dispatch(this.view.state.tr.setMeta(SET_SUGGESTION_ID, dataTracked.id));
157
- });
158
- }
159
- };
160
172
  }
161
173
  }
162
174
  export default createNodeView(BibliographyElementBlockView);
@@ -14,11 +14,8 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { DOMSerializer } from 'prosemirror-model';
17
- import { createElement } from 'react';
18
- import ReactDOM from 'react-dom';
19
- import { TrackChangesReview } from '../components/track-changes/TrackChangesReview';
20
17
  import { sanitize } from '../lib/dompurify';
21
- import { getChangeClasses, isDeleted, isPendingSetAttrs, } from '../lib/track-changes-utils';
18
+ import { getChangeClasses } from '../lib/track-changes-utils';
22
19
  import { getBibliographyPluginState } from '../plugins/bibliography';
23
20
  import { getCitation } from '../plugins/bibliography/bibliography-utils';
24
21
  import { BaseNodeView } from './base_node_view';
@@ -39,6 +36,7 @@ export class CitationView extends BaseNodeView {
39
36
  return this;
40
37
  };
41
38
  this.updateContents = () => {
39
+ 11;
42
40
  const bib = getBibliographyPluginState(this.view.state);
43
41
  const citation = this.getCitation();
44
42
  const classes = [
@@ -55,24 +53,8 @@ export class CitationView extends BaseNodeView {
55
53
  this.dom.className = 'citation-wrapper';
56
54
  this.dom.innerHTML = '';
57
55
  this.dom.appendChild(element);
58
- if (isPendingSetAttrs(this.node) &&
59
- !isDeleted(this.node) &&
60
- citation.embeddedCitationItems.length) {
61
- this.dom.appendChild(this.renderTrackChangesReview());
62
- }
63
56
  this.setDomAttrs(this.node, this.dom, ['rids', 'contents', 'selectedText']);
64
57
  };
65
- this.renderTrackChangesReview = () => {
66
- const { popper } = this.props;
67
- const el = document.createElement('span');
68
- el.classList.add('track-changes-review');
69
- ReactDOM.render(createElement(TrackChangesReview, {
70
- node: this.node,
71
- popper,
72
- target: el,
73
- }), el);
74
- return el;
75
- };
76
58
  this.getCitation = () => {
77
59
  const citation = getCitation(this.node);
78
60
  if (!citation) {
@@ -22,8 +22,10 @@ import { findChildren, findChildrenByType } from 'prosemirror-utils';
22
22
  import { crossref } from '../citation-sources';
23
23
  import { isDeleted } from '../lib/track-changes-utils';
24
24
  import { getBibliographyPluginState } from '../plugins/bibliography';
25
+ import { selectedSuggestionKey, SET_SUGGESTION_ID, } from '../plugins/selected-suggestion-ui';
25
26
  import { CitationView } from './citation';
26
27
  import { CitationEditorWrapper } from './CitationEditorWrapper';
28
+ import { ContextMenuWrapper } from './ContextMenuWrapper';
27
29
  import { createEditableNodeView } from './creators';
28
30
  import ReactSubView from './ReactSubView';
29
31
  const createBibliographySection = (node) => schema.nodes.bibliography_section.createAndFill({}, [
@@ -35,14 +37,28 @@ export class CitationEditableView extends CitationView {
35
37
  super(...arguments);
36
38
  this.decoder = new Decoder(new Map());
37
39
  this.selectNode = () => {
40
+ var _a;
38
41
  if (!isDeleted(this.node)) {
39
- this.showPopper();
40
- this.dom.classList.add('ProseMirror-selectednode');
42
+ const dataTracked = this.node.attrs.dataTracked;
43
+ const isSelectedSuggestion = !!((_a = selectedSuggestionKey
44
+ .getState(this.view.state)) === null || _a === void 0 ? void 0 : _a.find(this.getPos(), this.getPos() + this.node.nodeSize).length);
45
+ if (dataTracked && !isSelectedSuggestion) {
46
+ this.view.dispatch(this.view.state.tr.setMeta(SET_SUGGESTION_ID, dataTracked[0].id));
47
+ }
48
+ else {
49
+ const citation = this.getCitation();
50
+ const rids = citation.embeddedCitationItems.map((i) => i.bibliographyItem);
51
+ if (!rids.length) {
52
+ this.showPopper();
53
+ }
54
+ else {
55
+ this.showContextMenu();
56
+ }
57
+ this.dom.classList.add('ProseMirror-selectednode');
58
+ }
41
59
  }
42
60
  };
43
61
  this.deselectNode = () => {
44
- var _a;
45
- (_a = this.editor) === null || _a === void 0 ? void 0 : _a.remove();
46
62
  this.dom.classList.remove('ProseMirror-selectednode');
47
63
  this.props.popper.destroy();
48
64
  };
@@ -51,7 +67,18 @@ export class CitationEditableView extends CitationView {
51
67
  (_a = this.editor) === null || _a === void 0 ? void 0 : _a.remove();
52
68
  this.props.popper.destroy();
53
69
  };
70
+ this.showContextMenu = () => {
71
+ const componentProps = {
72
+ actions: [
73
+ { label: 'Edit', action: this.handleEdit, icon: 'EditIcon' },
74
+ { label: 'Comment', action: this.handleComment, icon: 'AddComment' },
75
+ ],
76
+ };
77
+ this.editor = ReactSubView(this.props, ContextMenuWrapper, componentProps, this.node, this.getPos, this.view, 'context-menu');
78
+ this.props.popper.show(this.dom, this.editor, 'right-start');
79
+ };
54
80
  this.showPopper = () => {
81
+ this.props.popper.destroy();
55
82
  const can = this.props.getCapabilities();
56
83
  const citation = this.getCitation();
57
84
  const rids = citation.embeddedCitationItems.map((i) => i.bibliographyItem);
@@ -84,6 +111,9 @@ export class CitationEditableView extends CitationView {
84
111
  }
85
112
  this.props.popper.show(this.dom, this.editor, 'right');
86
113
  };
114
+ this.handleEdit = () => {
115
+ this.showPopper();
116
+ };
87
117
  this.handleCancel = () => {
88
118
  const selection = TextSelection.create(this.view.state.tr.doc, this.getPos() + 1);
89
119
  this.view.dispatch(this.view.state.tr.setSelection(selection));
@@ -16,6 +16,7 @@
16
16
  import { AffiliationNode, ContributorNode, ManuscriptNode } from '@manuscripts/transform';
17
17
  import { Plugin, PluginKey } from 'prosemirror-state';
18
18
  interface PluginState {
19
+ version: string;
19
20
  indexedAffiliationIds: Map<string, number>;
20
21
  contributors: Array<[ContributorNode, number]>;
21
22
  affiliations: Array<[AffiliationNode, number]>;
@@ -22,6 +22,7 @@ import { PopperManager } from '../../lib/popper';
22
22
  import { CitationNodes } from './bibliography-utils';
23
23
  export declare const bibliographyKey: PluginKey<PluginState>;
24
24
  export interface PluginState {
25
+ version: string;
25
26
  citationNodes: CitationNodes;
26
27
  citations: CiteProc.Citation[];
27
28
  bibliographyItems: Map<string, BibliographyItem>;
@@ -19,6 +19,7 @@ interface SelectedSuggestionProps {
19
19
  setEditorSelectedSuggestion: (id?: string) => void;
20
20
  }
21
21
  export declare const SET_SUGGESTION_ID = "SET_SELECTED_SUGGESTION_ID";
22
+ export declare const CLEAR_SUGGESTION_ID = "CLEAR_SUGGESTION_ID";
22
23
  export declare const selectedSuggestionKey: PluginKey<DecorationSet>;
23
24
  declare const _default: (props: SelectedSuggestionProps) => Plugin<DecorationSet>;
24
25
  export default _default;
@@ -0,0 +1,3 @@
1
+ import { ContextMenuProps } from '@manuscripts/style-guide';
2
+ import React from 'react';
3
+ export declare const ContextMenuWrapper: React.FC<ContextMenuProps>;
@@ -24,15 +24,18 @@ export declare class BibliographyElementBlockView<PropsType extends Bibliography
24
24
  private container;
25
25
  private editor;
26
26
  private decoder;
27
- gutterButtons: () => HTMLElement[];
27
+ private elementWithContextMenu;
28
28
  showPopper: (id: string) => void;
29
29
  stopEvent: () => boolean;
30
30
  ignoreMutation: () => boolean;
31
+ private handleEdit;
32
+ private handleComment;
33
+ showContextMenu: (element: Element) => void;
34
+ private onClickHandler;
31
35
  updateContents: () => Promise<void>;
32
36
  createElement: () => void;
33
37
  private handleSave;
34
38
  private handleDelete;
35
- private addClickListenerToBibItem;
36
39
  }
37
40
  declare const _default: (props: BaseNodeProps, dispatch?: import("..").Dispatch | undefined) => import("../types").NodeViewCreator<BibliographyElementBlockView<any>>;
38
41
  export default _default;
@@ -19,7 +19,6 @@ export declare class CitationView<PropsType extends BaseNodeProps> extends BaseN
19
19
  ignoreMutation: () => boolean;
20
20
  initialise: () => this;
21
21
  updateContents: () => void;
22
- renderTrackChangesReview: () => HTMLSpanElement;
23
22
  getCitation: () => import("@manuscripts/json-schema/dist/types").Citation;
24
23
  }
25
24
  declare const _default: (props: BaseNodeProps, dispatch?: import("..").Dispatch | undefined) => import("../types").NodeViewCreator<CitationView<any>>;
@@ -21,7 +21,9 @@ export declare class CitationEditableView extends CitationView<EditableBlockProp
21
21
  selectNode: () => void;
22
22
  deselectNode: () => void;
23
23
  destroy: () => void;
24
+ showContextMenu: () => void;
24
25
  showPopper: () => void;
26
+ private handleEdit;
25
27
  private handleCancel;
26
28
  private handleSave;
27
29
  private handleUncite;
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": "1.7.16",
4
+ "version": "1.7.17-LEAN-3415-1",
5
5
  "repository": "github:Atypon-OpenSource/manuscripts-body-editor",
6
6
  "license": "Apache-2.0",
7
7
  "main": "dist/cjs",
@@ -31,7 +31,7 @@
31
31
  "@manuscripts/assets": "^0.6.4",
32
32
  "@manuscripts/json-schema": "^2.2.5",
33
33
  "@manuscripts/library": "^1.3.4",
34
- "@manuscripts/style-guide": "^1.7.10",
34
+ "@manuscripts/style-guide": "1.7.10-LEAN-3415-1",
35
35
  "@manuscripts/track-changes-plugin": "^1.7.6",
36
36
  "@manuscripts/transform": "^2.1.5",
37
37
  "astrocite-eutils": "^0.16.4",
@@ -741,6 +741,7 @@ Next levels are shifted 'up' by 1
741
741
  .citation-wrapper {
742
742
  position: relative;
743
743
  }
744
+
744
745
  .track-changes-review {
745
746
  display: none;
746
747
  height: 36px;
package/styles/Editor.css CHANGED
@@ -671,6 +671,10 @@
671
671
  cursor: ns-resize;
672
672
  opacity: 0;
673
673
  transition: 0.2s all;
674
+ }
675
+
676
+ .ProseMirror .block-section_title .block-gutter,
677
+ .ProseMirror .block-paragraph .block-gutter {
674
678
  margin-top: 16px;
675
679
  }
676
680
 
package/styles/popper.css CHANGED
@@ -136,7 +136,8 @@
136
136
  .popper .cross-reference-editor,
137
137
  .popper .citation-references,
138
138
  .popper .track-changes-review-panel,
139
- .popper .table-footnote-editor {
139
+ .popper .table-footnote-editor,
140
+ .popper .context-menu {
140
141
  background: #fff;
141
142
  border: 1px solid #e2e2e2;
142
143
  border-radius: 8px;
@@ -144,6 +145,11 @@
144
145
  height: auto;
145
146
  }
146
147
 
148
+ .popper .context-menu {
149
+ width: auto;
150
+ top: -16px;
151
+ }
152
+
147
153
  .popper .conflict-dropdown {
148
154
  background: #fff;
149
155
  box-shadow: 0 4px 9px 0 rgba(84, 83, 83, 0.3);