markupeditor 0.9.40 → 0.9.41
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/config/behaviorconfig.json +1 -0
- package/dist/markup-editor.js +195 -24
- package/package.json +1 -1
package/dist/markup-editor.js
CHANGED
|
@@ -15149,6 +15149,16 @@ const tNodes = tableNodes({
|
|
|
15149
15149
|
attrs.style = (attrs.style || '') + `background-color: ${value};`;
|
|
15150
15150
|
},
|
|
15151
15151
|
},
|
|
15152
|
+
align: {
|
|
15153
|
+
default: null,
|
|
15154
|
+
getFromDOM(dom) {
|
|
15155
|
+
return dom.style.textAlign || null;
|
|
15156
|
+
},
|
|
15157
|
+
setDOMAttr(value, attrs) {
|
|
15158
|
+
if (value)
|
|
15159
|
+
attrs.style = (attrs.style || '') + `text-align: ${value};`;
|
|
15160
|
+
},
|
|
15161
|
+
},
|
|
15152
15162
|
}
|
|
15153
15163
|
});
|
|
15154
15164
|
tNodes.table.attrs = {class: {default: null}};
|
|
@@ -34840,7 +34850,7 @@ function _getSelectionState() {
|
|
|
34840
34850
|
state['thead'] = tableAttributes.thead;
|
|
34841
34851
|
state['tbody'] = tableAttributes.tbody;
|
|
34842
34852
|
state['header'] = tableAttributes.header;
|
|
34843
|
-
state['colspan'] = tableAttributes.colspan;
|
|
34853
|
+
state['colspan'] = (tableAttributes.colspan ?? 0) > 1; // A real boolean; Swift's SelectionState.colspan is typed Bool and cannot decode a raw JS number
|
|
34844
34854
|
state['rows'] = tableAttributes.rows;
|
|
34845
34855
|
state['cols'] = tableAttributes.cols;
|
|
34846
34856
|
state['row'] = tableAttributes.row;
|
|
@@ -35041,8 +35051,8 @@ function _getTableAttributes(state) {
|
|
|
35041
35051
|
case nodeTypes.table_header:
|
|
35042
35052
|
attributes.thead = true; // We selected the header
|
|
35043
35053
|
attributes.tbody = false;
|
|
35044
|
-
attributes.row =
|
|
35045
|
-
attributes.col = 1;
|
|
35054
|
+
attributes.row = 0; // Header cells are row 0 by definition, not a body row index
|
|
35055
|
+
attributes.col = $pos.index() + 1; // A header cell's own column position, whether colspanned or per-column
|
|
35046
35056
|
return true;
|
|
35047
35057
|
case nodeTypes.table_row:
|
|
35048
35058
|
attributes.row = $pos.index() + 1; // We are in some row, but could be the header row
|
|
@@ -35165,8 +35175,8 @@ function callbackInsertLink() {
|
|
|
35165
35175
|
|
|
35166
35176
|
/**
|
|
35167
35177
|
* Callback to signal that the user wants to insert an image.
|
|
35168
|
-
*
|
|
35169
|
-
* The messageHandler will need to bring up a dialog to identify
|
|
35178
|
+
*
|
|
35179
|
+
* The messageHandler will need to bring up a dialog to identify
|
|
35170
35180
|
* the image and then execute insertImage.
|
|
35171
35181
|
*/
|
|
35172
35182
|
function callbackInsertImage() {
|
|
@@ -35174,6 +35184,17 @@ function callbackInsertImage() {
|
|
|
35174
35184
|
_callback(JSON.stringify(messageDict));
|
|
35175
35185
|
}
|
|
35176
35186
|
|
|
35187
|
+
/**
|
|
35188
|
+
* Callback to signal that the user wants to insert or edit a table.
|
|
35189
|
+
*
|
|
35190
|
+
* The messageHandler will need to bring up a dialog to size a new table
|
|
35191
|
+
* (and then execute insertTable) or edit the table the selection is in.
|
|
35192
|
+
*/
|
|
35193
|
+
function callbackInsertTable() {
|
|
35194
|
+
let messageDict = { 'messageType' : 'insertTable' };
|
|
35195
|
+
_callback(JSON.stringify(messageDict));
|
|
35196
|
+
}
|
|
35197
|
+
|
|
35177
35198
|
/**
|
|
35178
35199
|
* Callback to signal that user-provided CSS and/or script files have
|
|
35179
35200
|
* been loaded.
|
|
@@ -36011,7 +36032,7 @@ function _copyImage(node) {
|
|
|
36011
36032
|
* @param {number} rows The number of rows in the table to be created.
|
|
36012
36033
|
* @param {number} cols The number of columns in the table to be created.
|
|
36013
36034
|
*/
|
|
36014
|
-
function insertTable(rows, cols) {
|
|
36035
|
+
function insertTable$1(rows, cols) {
|
|
36015
36036
|
const view = activeView();
|
|
36016
36037
|
if ((rows < 1) || (cols < 1)) return;
|
|
36017
36038
|
let command = insertTableCommand(rows, cols);
|
|
@@ -36021,6 +36042,7 @@ function insertTable(rows, cols) {
|
|
|
36021
36042
|
function insertTableCommand(rows, cols) {
|
|
36022
36043
|
const commandAdapter = (viewState, dispatch, view) => {
|
|
36023
36044
|
let state = view?.state ?? viewState;
|
|
36045
|
+
if (isTableSelected(state)) return false; // Tables cannot be nested
|
|
36024
36046
|
const nodeTypes = state.schema.nodes;
|
|
36025
36047
|
const table_rows = [];
|
|
36026
36048
|
for (let j = 0; j < rows; j++) {
|
|
@@ -36060,15 +36082,8 @@ function insertTableCommand(rows, cols) {
|
|
|
36060
36082
|
} return (pPos == undefined); // Keep going if pPos hasn't been defined
|
|
36061
36083
|
});
|
|
36062
36084
|
}
|
|
36063
|
-
// Set the selection in the first cell,
|
|
36064
|
-
|
|
36065
|
-
let empty = (view.state.doc.textContent.length == 0);
|
|
36066
|
-
let textSelection;
|
|
36067
|
-
if (empty) {
|
|
36068
|
-
textSelection = TextSelection.near(transaction.doc.resolve(pPos), -1);
|
|
36069
|
-
} else {
|
|
36070
|
-
textSelection = TextSelection.near(transaction.doc.resolve(pPos));
|
|
36071
|
-
}
|
|
36085
|
+
// Set the selection in the first cell, applied to the state and the view.
|
|
36086
|
+
let textSelection = TextSelection.near(transaction.doc.resolve(pPos));
|
|
36072
36087
|
transaction = transaction.setSelection(textSelection);
|
|
36073
36088
|
state = state.apply(transaction);
|
|
36074
36089
|
view.updateState(state);
|
|
@@ -36098,12 +36113,20 @@ function addRow(direction) {
|
|
|
36098
36113
|
return result;
|
|
36099
36114
|
}
|
|
36100
36115
|
function addRowCommand(direction) {
|
|
36101
|
-
const commandAdapter = (
|
|
36116
|
+
const commandAdapter = (viewState, dispatch, view) => {
|
|
36117
|
+
let state = view?.state ?? viewState;
|
|
36118
|
+
let result;
|
|
36102
36119
|
if (direction === 'BEFORE') {
|
|
36103
|
-
|
|
36120
|
+
result = addRowBefore(state, (tr) => { state = state.apply(tr); });
|
|
36104
36121
|
} else {
|
|
36105
|
-
|
|
36106
|
-
}
|
|
36122
|
+
result = addRowAfter(state, (tr) => { state = state.apply(tr); });
|
|
36123
|
+
} if (!result) return false;
|
|
36124
|
+
_propagateColumnAlign(state, (tr) => { state = state.apply(tr); });
|
|
36125
|
+
if (dispatch) {
|
|
36126
|
+
view.updateState(state);
|
|
36127
|
+
}
|
|
36128
|
+
return true;
|
|
36129
|
+
};
|
|
36107
36130
|
|
|
36108
36131
|
return commandAdapter;
|
|
36109
36132
|
}
|
|
@@ -36130,14 +36153,18 @@ function addColCommand(direction) {
|
|
|
36130
36153
|
let state = view?.state ?? viewState;
|
|
36131
36154
|
if (!isTableSelected(state)) return false;
|
|
36132
36155
|
const startSelection = new TextSelection(state.selection.$anchor, state.selection.$head);
|
|
36156
|
+
const wasColspannedHeader = _hasColspannedHeader(state);
|
|
36133
36157
|
let offset = 0;
|
|
36134
36158
|
if (direction === 'BEFORE') {
|
|
36135
36159
|
addColumnBefore(state, (tr) => { state = state.apply(tr); });
|
|
36136
36160
|
offset = 4; // An empty cell
|
|
36137
36161
|
} else {
|
|
36138
36162
|
addColumnAfter(state, (tr) => { state = state.apply(tr); });
|
|
36139
|
-
}
|
|
36140
|
-
|
|
36163
|
+
} // Re-merge only a colspanned caption header that this add just split into two cells.
|
|
36164
|
+
// A genuine per-column header must not be merged -- it should just gain one more cell.
|
|
36165
|
+
if (wasColspannedHeader) {
|
|
36166
|
+
_mergeHeaders(state, (tr) => { state = state.apply(tr); });
|
|
36167
|
+
}
|
|
36141
36168
|
if (dispatch) {
|
|
36142
36169
|
const $anchor = state.tr.doc.resolve(startSelection.from + offset);
|
|
36143
36170
|
const $head = state.tr.doc.resolve(startSelection.to + offset);
|
|
@@ -36180,7 +36207,8 @@ function addHeaderCommand(colspan = true) {
|
|
|
36180
36207
|
toggleHeaderRow(state, (tr) => { state = state.apply(tr); });
|
|
36181
36208
|
if (colspan) {
|
|
36182
36209
|
_mergeHeaders(state, (tr) => { state = state.apply(tr); });
|
|
36183
|
-
}
|
|
36210
|
+
} _propagateColumnAlign(state, (tr) => { state = state.apply(tr); });
|
|
36211
|
+
|
|
36184
36212
|
if (dispatch) {
|
|
36185
36213
|
// At this point, the state.selection is in the new header row we just added. By definition,
|
|
36186
36214
|
// the header is placed before the original selection, so we can add its size to the
|
|
@@ -36316,6 +36344,129 @@ function _mergeHeaders(state, dispatch) {
|
|
|
36316
36344
|
const newState = state.apply(transaction);
|
|
36317
36345
|
mergeCells(newState, dispatch);
|
|
36318
36346
|
}}
|
|
36347
|
+
/**
|
|
36348
|
+
* Return whether the table's header row is currently a single colspanned caption cell
|
|
36349
|
+
* (colspan > 1), as opposed to a genuine per-column header (multiple table_header cells,
|
|
36350
|
+
* each colspan 1) or no header at all. Used by addColCommand to decide whether adding a
|
|
36351
|
+
* column should re-merge the header row (maintaining a caption) or leave it alone (a real
|
|
36352
|
+
* per-column header simply gains one more per-column cell).
|
|
36353
|
+
*
|
|
36354
|
+
* @ignore
|
|
36355
|
+
*/
|
|
36356
|
+
function _hasColspannedHeader(state) {
|
|
36357
|
+
const nodeTypes = state.schema.nodes;
|
|
36358
|
+
const headers = [];
|
|
36359
|
+
let tableAttributes = _getTableAttributes(state);
|
|
36360
|
+
if (!tableAttributes.table) return false;
|
|
36361
|
+
state.tr.doc.nodesBetween(tableAttributes.from, tableAttributes.to, (node, pos) => {
|
|
36362
|
+
if (node.type == nodeTypes.table_header) {
|
|
36363
|
+
headers.push(node);
|
|
36364
|
+
return false;
|
|
36365
|
+
}
|
|
36366
|
+
return true;
|
|
36367
|
+
});
|
|
36368
|
+
return (headers.length === 1) && ((headers[0].attrs.colspan ?? 1) > 1);
|
|
36369
|
+
}
|
|
36370
|
+
/**
|
|
36371
|
+
* Propagate each column's existing alignment onto any cell in that column that doesn't have
|
|
36372
|
+
* one yet -- e.g., a cell in a row addRowCommand or addHeaderCommand just inserted, which
|
|
36373
|
+
* otherwise defaults to align: null and would silently drop the column's alignment on export.
|
|
36374
|
+
*
|
|
36375
|
+
* Columns are identified by a cell's index within its row, which assumes every row has the
|
|
36376
|
+
* same number of cells -- true for any table this editor can produce or import, since this
|
|
36377
|
+
* schema has no colspanned/rowspanned body cells (only a header row can be colspanned, and a
|
|
36378
|
+
* colspanned header cell is skipped here since it doesn't belong to a single column).
|
|
36379
|
+
*
|
|
36380
|
+
* @ignore
|
|
36381
|
+
*/
|
|
36382
|
+
function _propagateColumnAlign(state, dispatch) {
|
|
36383
|
+
const nodeTypes = state.schema.nodes;
|
|
36384
|
+
let tableAttributes = _getTableAttributes(state);
|
|
36385
|
+
if (!tableAttributes.table) return;
|
|
36386
|
+
const tr = state.tr;
|
|
36387
|
+
const columnAligns = {};
|
|
36388
|
+
tr.doc.nodesBetween(tableAttributes.from, tableAttributes.to, (node, pos) => {
|
|
36389
|
+
if ((node.type == nodeTypes.table_cell) || (node.type == nodeTypes.table_header)) {
|
|
36390
|
+
if ((node.attrs.colspan ?? 1) > 1) return false; // A colspanned caption cell doesn't belong to a single column
|
|
36391
|
+
const col = tr.doc.resolve(pos).index();
|
|
36392
|
+
if ((columnAligns[col] === undefined) && node.attrs.align) {
|
|
36393
|
+
columnAligns[col] = node.attrs.align;
|
|
36394
|
+
}
|
|
36395
|
+
return false;
|
|
36396
|
+
}
|
|
36397
|
+
return true;
|
|
36398
|
+
});
|
|
36399
|
+
if (Object.keys(columnAligns).length === 0) return;
|
|
36400
|
+
let changed = false;
|
|
36401
|
+
tr.doc.nodesBetween(tableAttributes.from, tableAttributes.to, (node, pos) => {
|
|
36402
|
+
if ((node.type == nodeTypes.table_cell) || (node.type == nodeTypes.table_header)) {
|
|
36403
|
+
if ((node.attrs.colspan ?? 1) > 1) return false;
|
|
36404
|
+
const col = tr.doc.resolve(pos).index();
|
|
36405
|
+
const align = columnAligns[col];
|
|
36406
|
+
if (align && !node.attrs.align) {
|
|
36407
|
+
tr.setNodeMarkup(pos, null, {...node.attrs, align});
|
|
36408
|
+
changed = true;
|
|
36409
|
+
}
|
|
36410
|
+
return false;
|
|
36411
|
+
}
|
|
36412
|
+
return true;
|
|
36413
|
+
});
|
|
36414
|
+
if (changed && dispatch) {
|
|
36415
|
+
dispatch(tr);
|
|
36416
|
+
}
|
|
36417
|
+
}
|
|
36418
|
+
/**
|
|
36419
|
+
* Justify (set text alignment on) the column containing the selection. Applies to every cell
|
|
36420
|
+
* in that column -- header and body alike -- not just the selected cell, since GFM's Markdown
|
|
36421
|
+
* alignment is a per-column, not per-cell, concept. A colspanned (single-cell caption) header
|
|
36422
|
+
* has no single column to target and is left untouched.
|
|
36423
|
+
*
|
|
36424
|
+
* @param {'left' | 'center' | 'right' | null} align The alignment to set, or null/falsy to clear it.
|
|
36425
|
+
*/
|
|
36426
|
+
function justifyColumn(align) {
|
|
36427
|
+
if (_tableSelected()) {
|
|
36428
|
+
const view = activeView();
|
|
36429
|
+
let command = justifyColumnCommand(align);
|
|
36430
|
+
let result = command(view.state, view.dispatch, view);
|
|
36431
|
+
stateChanged(view);
|
|
36432
|
+
view.focus();
|
|
36433
|
+
return result;
|
|
36434
|
+
}
|
|
36435
|
+
}
|
|
36436
|
+
function justifyColumnCommand(align) {
|
|
36437
|
+
// 'left' means "clear", matching an unaligned GFM column having no delimiter-row marker
|
|
36438
|
+
// at all -- an alias here rather than relying on every caller to pass null/undefined.
|
|
36439
|
+
const resolvedAlign = (align && align !== 'left') ? align : null;
|
|
36440
|
+
const commandAdapter = (state, dispatch, view) => {
|
|
36441
|
+
if (!isTableSelected(state)) return false;
|
|
36442
|
+
const nodeTypes = state.schema.nodes;
|
|
36443
|
+
const tableAttributes = _getTableAttributes(state);
|
|
36444
|
+
if (!tableAttributes.table) return false;
|
|
36445
|
+
if (tableAttributes.thead && (tableAttributes.colspan > 1)) return false; // Colspanned caption header selected; no single column to target
|
|
36446
|
+
const col = tableAttributes.col;
|
|
36447
|
+
if (!col) return false;
|
|
36448
|
+
const tr = state.tr;
|
|
36449
|
+
let changed = false;
|
|
36450
|
+
tr.doc.nodesBetween(tableAttributes.from, tableAttributes.to, (node, pos) => {
|
|
36451
|
+
if ((node.type == nodeTypes.table_cell) || (node.type == nodeTypes.table_header)) {
|
|
36452
|
+
if ((node.attrs.colspan ?? 1) > 1) return false; // Skip a colspanned caption cell; it doesn't belong to a single column
|
|
36453
|
+
if ((tr.doc.resolve(pos).index() + 1) === col) {
|
|
36454
|
+
tr.setNodeMarkup(pos, null, {...node.attrs, align: resolvedAlign});
|
|
36455
|
+
changed = true;
|
|
36456
|
+
}
|
|
36457
|
+
return false;
|
|
36458
|
+
}
|
|
36459
|
+
return true;
|
|
36460
|
+
});
|
|
36461
|
+
if (changed && dispatch) {
|
|
36462
|
+
dispatch(tr);
|
|
36463
|
+
}
|
|
36464
|
+
return changed;
|
|
36465
|
+
};
|
|
36466
|
+
|
|
36467
|
+
return commandAdapter;
|
|
36468
|
+
}
|
|
36469
|
+
|
|
36319
36470
|
function isHRuleSelected(state) {
|
|
36320
36471
|
let hRuleSelected = false;
|
|
36321
36472
|
state.doc.nodesBetween(state.selection.from, state.selection.to, (node) => {
|
|
@@ -37145,6 +37296,7 @@ var focusAfterLoad = true;
|
|
|
37145
37296
|
var selectImage = false;
|
|
37146
37297
|
var insertLink = false;
|
|
37147
37298
|
var insertImage = false;
|
|
37299
|
+
var insertTable = false;
|
|
37148
37300
|
var highlightCode = true;
|
|
37149
37301
|
var markdownShorthand = true;
|
|
37150
37302
|
var behaviorConfig = {
|
|
@@ -37152,6 +37304,7 @@ var behaviorConfig = {
|
|
|
37152
37304
|
selectImage: selectImage,
|
|
37153
37305
|
insertLink: insertLink,
|
|
37154
37306
|
insertImage: insertImage,
|
|
37307
|
+
insertTable: insertTable,
|
|
37155
37308
|
highlightCode: highlightCode,
|
|
37156
37309
|
markdownShorthand: markdownShorthand
|
|
37157
37310
|
};
|
|
@@ -37192,6 +37345,7 @@ var behaviorConfig = {
|
|
|
37192
37345
|
* "selectImage": false, // Whether to show a "Select..." button in the Insert Image dialog
|
|
37193
37346
|
* "insertLink": false, // Whether to defer to the MarkupDelegate rather than use the default LinkDialog
|
|
37194
37347
|
* "insertImage": false, // Whether to defer to the MarkupDelagate rather than use the default ImageDialog
|
|
37348
|
+
* "insertTable": false, // Whether to defer to the MarkupDelegate rather than use the default Table menu
|
|
37195
37349
|
* "highlightCode": true, // Whether to highlight code blocks and support language identificaton in UI
|
|
37196
37350
|
* "markdownShorthand": true // Whether typing markdown shorthand converts to formatting: block-level
|
|
37197
37351
|
* // (`> `, list markers, ``` ``` ```, `#`) and inline (**, *, _, `, ~~)
|
|
@@ -40339,6 +40493,19 @@ function hRuleItem(config) {
|
|
|
40339
40493
|
function tableMenuItems(config) {
|
|
40340
40494
|
let icons = config.toolbar.icons;
|
|
40341
40495
|
let help = config.toolbar.help;
|
|
40496
|
+
// If `behavior.insertTable` is true, the Table button just invokes the delegate's
|
|
40497
|
+
// `markupInsertTable` method instead of the dropdown below -- same reasoning as
|
|
40498
|
+
// LinkItem/ImageItem. Unlike Link/Image, the delegate decides what to show (an
|
|
40499
|
+
// insert-size picker or a table-editing menu) based on whether the selection is
|
|
40500
|
+
// already in a table, so a single enabled state covers both: "can insert a table
|
|
40501
|
+
// here" or "already in a table" (editing an existing table is always available).
|
|
40502
|
+
if (config.behavior.insertTable && config.delegate?.markupInsertTable) {
|
|
40503
|
+
return cmdItem(config.delegate.markupInsertTable, {
|
|
40504
|
+
enable: (state) => insertTableCommand(1, 1)(state) || isTableSelected(state),
|
|
40505
|
+
title: help.table + keyString('table', config.keymap),
|
|
40506
|
+
icon: icons.table
|
|
40507
|
+
})
|
|
40508
|
+
}
|
|
40342
40509
|
let items = [];
|
|
40343
40510
|
let { tableHeader, tableBorder } = config.toolbar.menus;
|
|
40344
40511
|
items.push(new TableCreateSubmenu({title: 'Insert table', label: 'Insert'}));
|
|
@@ -40846,7 +41013,9 @@ function buildKeymap(config, schema) {
|
|
|
40846
41013
|
// Insert
|
|
40847
41014
|
bind(keymap.link, new LinkItem(config).command);
|
|
40848
41015
|
bind(keymap.image, new ImageItem(config).command);
|
|
40849
|
-
|
|
41016
|
+
// Defers to the delegate when `behavior.insertTable` is set, same as Link/Image;
|
|
41017
|
+
// otherwise inserts a default-sized table, since there's no dialog to fall back to.
|
|
41018
|
+
bind(keymap.table, (config.behavior.insertTable && config.delegate?.markupInsertTable) ? config.delegate.markupInsertTable : insertTableCommand(2, 2));
|
|
40850
41019
|
// Styling
|
|
40851
41020
|
bind(keymap.p, setStyleCommand('P'));
|
|
40852
41021
|
bind(keymap.h1, setStyleCommand('H1'));
|
|
@@ -42777,6 +42946,7 @@ const MU = {
|
|
|
42777
42946
|
borderTable,
|
|
42778
42947
|
callbackInsertImage,
|
|
42779
42948
|
callbackInsertLink,
|
|
42949
|
+
callbackInsertTable,
|
|
42780
42950
|
callbackSelectImage,
|
|
42781
42951
|
cancelSearch,
|
|
42782
42952
|
canUndo,
|
|
@@ -42806,8 +42976,9 @@ const MU = {
|
|
|
42806
42976
|
insertImage: insertImage$1,
|
|
42807
42977
|
insertInternalLink,
|
|
42808
42978
|
insertLink: insertLink$1,
|
|
42809
|
-
insertTable,
|
|
42979
|
+
insertTable: insertTable$1,
|
|
42810
42980
|
insertHRule,
|
|
42981
|
+
justifyColumn,
|
|
42811
42982
|
loadUserFiles,
|
|
42812
42983
|
modifyImage,
|
|
42813
42984
|
openImageDialog,
|