@productcloudos/editor 1.0.4 → 1.0.6
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/pc-editor.esm.js +588 -226
- package/dist/pc-editor.esm.js.map +1 -1
- package/dist/pc-editor.js +588 -225
- package/dist/pc-editor.js.map +1 -1
- package/dist/pc-editor.min.js +1 -1
- package/dist/pc-editor.min.js.map +1 -1
- package/dist/types/lib/core/PCEditor.d.ts +31 -5
- package/dist/types/lib/core/PCEditor.d.ts.map +1 -1
- package/dist/types/lib/import/PDFParser.d.ts.map +1 -1
- package/dist/types/lib/index.d.ts +1 -0
- package/dist/types/lib/index.d.ts.map +1 -1
- package/dist/types/lib/objects/EmbeddedObjectFactory.d.ts.map +1 -1
- package/dist/types/lib/objects/ImageObject.d.ts +11 -0
- package/dist/types/lib/objects/ImageObject.d.ts.map +1 -1
- package/dist/types/lib/objects/TextBoxObject.d.ts +5 -2
- package/dist/types/lib/objects/TextBoxObject.d.ts.map +1 -1
- package/dist/types/lib/objects/table/TableCell.d.ts.map +1 -1
- package/dist/types/lib/objects/table/TableObject.d.ts.map +1 -1
- package/dist/types/lib/objects/table/TableRow.d.ts.map +1 -1
- package/dist/types/lib/objects/table/types.d.ts +15 -15
- package/dist/types/lib/objects/table/types.d.ts.map +1 -1
- package/dist/types/lib/panes/DocumentInfoPane.d.ts +3 -1
- package/dist/types/lib/panes/DocumentInfoPane.d.ts.map +1 -1
- package/dist/types/lib/panes/DocumentSettingsPane.d.ts +2 -0
- package/dist/types/lib/panes/DocumentSettingsPane.d.ts.map +1 -1
- package/dist/types/lib/panes/FormattingPane.d.ts.map +1 -1
- package/dist/types/lib/panes/HyperlinkPane.d.ts +1 -0
- package/dist/types/lib/panes/HyperlinkPane.d.ts.map +1 -1
- package/dist/types/lib/panes/ImagePane.d.ts +1 -0
- package/dist/types/lib/panes/ImagePane.d.ts.map +1 -1
- package/dist/types/lib/panes/TablePane.d.ts +5 -0
- package/dist/types/lib/panes/TablePane.d.ts.map +1 -1
- package/dist/types/lib/panes/TextBoxPane.d.ts +1 -0
- package/dist/types/lib/panes/TextBoxPane.d.ts.map +1 -1
- package/dist/types/lib/rendering/CanvasManager.d.ts +1 -0
- package/dist/types/lib/rendering/CanvasManager.d.ts.map +1 -1
- package/dist/types/lib/rendering/FlowingTextRenderer.d.ts.map +1 -1
- package/dist/types/lib/rendering/PDFGenerator.d.ts.map +1 -1
- package/dist/types/lib/text/FlowingTextContent.d.ts.map +1 -1
- package/dist/types/lib/text/TextFormatting.d.ts +12 -1
- package/dist/types/lib/text/TextFormatting.d.ts.map +1 -1
- package/dist/types/lib/types/index.d.ts +2 -0
- package/dist/types/lib/types/index.d.ts.map +1 -1
- package/dist/types/lib/undo/transaction/MutationUndo.d.ts.map +1 -1
- package/dist/types/lib/utils/logger.d.ts +20 -0
- package/dist/types/lib/utils/logger.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/pc-editor.js
CHANGED
|
@@ -839,12 +839,13 @@ class TextFormattingManager extends EventEmitter {
|
|
|
839
839
|
}
|
|
840
840
|
/**
|
|
841
841
|
* Get formatting at a specific character position.
|
|
842
|
-
* Returns the position-specific formatting
|
|
842
|
+
* Returns the position-specific formatting merged with defaults,
|
|
843
|
+
* ensuring all properties are consistently present.
|
|
843
844
|
*/
|
|
844
845
|
getFormattingAt(position) {
|
|
845
846
|
const override = this.formatting.get(position);
|
|
846
847
|
if (override) {
|
|
847
|
-
return { ...override };
|
|
848
|
+
return { ...this._defaultFormatting, ...override };
|
|
848
849
|
}
|
|
849
850
|
return { ...this._defaultFormatting };
|
|
850
851
|
}
|
|
@@ -931,6 +932,43 @@ class TextFormattingManager extends EventEmitter {
|
|
|
931
932
|
getAllFormatting() {
|
|
932
933
|
return new Map(this.formatting);
|
|
933
934
|
}
|
|
935
|
+
/**
|
|
936
|
+
* Get formatting as compressed runs for serialization.
|
|
937
|
+
* Only outputs entries where formatting changes from the previous character.
|
|
938
|
+
* Skips leading default formatting to minimize output size.
|
|
939
|
+
* @param textLength Length of the text to serialize formatting for
|
|
940
|
+
*/
|
|
941
|
+
getCompressedRuns(textLength) {
|
|
942
|
+
const runs = [];
|
|
943
|
+
const defaultFormat = this._defaultFormatting;
|
|
944
|
+
let lastFormat = null;
|
|
945
|
+
for (let i = 0; i < textLength; i++) {
|
|
946
|
+
const currentFormat = this.getFormattingAt(i);
|
|
947
|
+
const formatChanged = lastFormat === null ||
|
|
948
|
+
currentFormat.fontFamily !== lastFormat.fontFamily ||
|
|
949
|
+
currentFormat.fontSize !== lastFormat.fontSize ||
|
|
950
|
+
currentFormat.fontWeight !== lastFormat.fontWeight ||
|
|
951
|
+
currentFormat.fontStyle !== lastFormat.fontStyle ||
|
|
952
|
+
currentFormat.color !== lastFormat.color ||
|
|
953
|
+
currentFormat.backgroundColor !== lastFormat.backgroundColor;
|
|
954
|
+
if (formatChanged) {
|
|
955
|
+
const isDefault = currentFormat.fontFamily === defaultFormat.fontFamily &&
|
|
956
|
+
currentFormat.fontSize === defaultFormat.fontSize &&
|
|
957
|
+
currentFormat.fontWeight === defaultFormat.fontWeight &&
|
|
958
|
+
currentFormat.fontStyle === defaultFormat.fontStyle &&
|
|
959
|
+
currentFormat.color === defaultFormat.color &&
|
|
960
|
+
currentFormat.backgroundColor === defaultFormat.backgroundColor;
|
|
961
|
+
if (!isDefault || runs.length > 0) {
|
|
962
|
+
runs.push({
|
|
963
|
+
index: i,
|
|
964
|
+
formatting: { ...currentFormat }
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
lastFormat = currentFormat;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
return runs;
|
|
971
|
+
}
|
|
934
972
|
/**
|
|
935
973
|
* Restore formatting from a map (for deserialization).
|
|
936
974
|
*/
|
|
@@ -3834,6 +3872,35 @@ class ImageObject extends BaseEmbeddedObject {
|
|
|
3834
3872
|
get hasError() {
|
|
3835
3873
|
return this._error;
|
|
3836
3874
|
}
|
|
3875
|
+
/**
|
|
3876
|
+
* Get the loaded HTMLImageElement, if available.
|
|
3877
|
+
* Used by PDFGenerator to convert unsupported formats to PNG.
|
|
3878
|
+
*/
|
|
3879
|
+
get imageElement() {
|
|
3880
|
+
return this._loaded ? this._image : null;
|
|
3881
|
+
}
|
|
3882
|
+
/**
|
|
3883
|
+
* Convert the image to a PNG data URL via canvas.
|
|
3884
|
+
* Used when the original format (e.g., SVG, WebP, GIF) is not supported by pdf-lib.
|
|
3885
|
+
* Returns null if the image is not loaded or conversion fails.
|
|
3886
|
+
*/
|
|
3887
|
+
toPngDataUrl() {
|
|
3888
|
+
if (!this._loaded || !this._image)
|
|
3889
|
+
return null;
|
|
3890
|
+
try {
|
|
3891
|
+
const canvas = document.createElement('canvas');
|
|
3892
|
+
canvas.width = this._image.naturalWidth || this._size.width;
|
|
3893
|
+
canvas.height = this._image.naturalHeight || this._size.height;
|
|
3894
|
+
const ctx = canvas.getContext('2d');
|
|
3895
|
+
if (!ctx)
|
|
3896
|
+
return null;
|
|
3897
|
+
ctx.drawImage(this._image, 0, 0, canvas.width, canvas.height);
|
|
3898
|
+
return canvas.toDataURL('image/png');
|
|
3899
|
+
}
|
|
3900
|
+
catch {
|
|
3901
|
+
return null;
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3837
3904
|
loadImage() {
|
|
3838
3905
|
if (!this._src) {
|
|
3839
3906
|
this._error = true;
|
|
@@ -4729,12 +4796,10 @@ class TextBoxObject extends BaseEmbeddedObject {
|
|
|
4729
4796
|
this.editing = false;
|
|
4730
4797
|
}
|
|
4731
4798
|
toData() {
|
|
4732
|
-
// Serialize formatting
|
|
4733
|
-
const
|
|
4734
|
-
const
|
|
4735
|
-
|
|
4736
|
-
formattingEntries.push([key, { ...value }]);
|
|
4737
|
-
});
|
|
4799
|
+
// Serialize formatting as compressed runs (only at change boundaries)
|
|
4800
|
+
const text = this._flowingContent.getText();
|
|
4801
|
+
const compressedRuns = this._flowingContent.getFormattingManager().getCompressedRuns(text.length);
|
|
4802
|
+
const formattingEntries = compressedRuns.map(run => [run.index, { ...run.formatting }]);
|
|
4738
4803
|
// Get substitution fields as array
|
|
4739
4804
|
const fields = this._flowingContent.getSubstitutionFieldManager().getFieldsArray();
|
|
4740
4805
|
return {
|
|
@@ -4793,12 +4858,19 @@ class TextBoxObject extends BaseEmbeddedObject {
|
|
|
4793
4858
|
this._border = { ...boxData.border };
|
|
4794
4859
|
if (boxData.padding !== undefined)
|
|
4795
4860
|
this._padding = boxData.padding;
|
|
4796
|
-
// Restore formatting runs
|
|
4797
|
-
if (boxData.formattingRuns) {
|
|
4861
|
+
// Restore formatting runs (run-based: each entry applies from its index to the next)
|
|
4862
|
+
if (boxData.formattingRuns && boxData.formattingRuns.length > 0) {
|
|
4798
4863
|
const formattingManager = this._flowingContent.getFormattingManager();
|
|
4799
4864
|
formattingManager.clear();
|
|
4800
|
-
|
|
4801
|
-
|
|
4865
|
+
const textLength = this._flowingContent.getText().length;
|
|
4866
|
+
for (let i = 0; i < boxData.formattingRuns.length; i++) {
|
|
4867
|
+
const [startIndex, style] = boxData.formattingRuns[i];
|
|
4868
|
+
const nextIndex = i + 1 < boxData.formattingRuns.length
|
|
4869
|
+
? boxData.formattingRuns[i + 1][0]
|
|
4870
|
+
: textLength;
|
|
4871
|
+
if (startIndex < nextIndex) {
|
|
4872
|
+
formattingManager.applyFormatting(startIndex, nextIndex, style);
|
|
4873
|
+
}
|
|
4802
4874
|
}
|
|
4803
4875
|
}
|
|
4804
4876
|
// Restore substitution fields
|
|
@@ -4948,15 +5020,19 @@ class TextBoxObject extends BaseEmbeddedObject {
|
|
|
4948
5020
|
}
|
|
4949
5021
|
/**
|
|
4950
5022
|
* Check if a point is within this text box region.
|
|
5023
|
+
* Uses the full object bounds (including padding/border) so that
|
|
5024
|
+
* double-click to enter edit mode works on the entire text box area,
|
|
5025
|
+
* not just the inner text area.
|
|
4951
5026
|
* @param point Point in canvas coordinates
|
|
4952
|
-
* @param
|
|
5027
|
+
* @param _pageIndex The page index (ignored for text boxes)
|
|
4953
5028
|
*/
|
|
4954
|
-
containsPointInRegion(point,
|
|
4955
|
-
|
|
4956
|
-
if (!bounds)
|
|
5029
|
+
containsPointInRegion(point, _pageIndex) {
|
|
5030
|
+
if (!this._renderedPosition)
|
|
4957
5031
|
return false;
|
|
4958
|
-
return point.x >=
|
|
4959
|
-
point.
|
|
5032
|
+
return point.x >= this._renderedPosition.x &&
|
|
5033
|
+
point.x <= this._renderedPosition.x + this._size.width &&
|
|
5034
|
+
point.y >= this._renderedPosition.y &&
|
|
5035
|
+
point.y <= this._renderedPosition.y + this._size.height;
|
|
4960
5036
|
}
|
|
4961
5037
|
/**
|
|
4962
5038
|
* Trigger a reflow of text in this text box.
|
|
@@ -5041,6 +5117,41 @@ function getVerticalPadding(padding) {
|
|
|
5041
5117
|
return padding.top + padding.bottom;
|
|
5042
5118
|
}
|
|
5043
5119
|
|
|
5120
|
+
/**
|
|
5121
|
+
* Logger - Centralized logging for PC Editor.
|
|
5122
|
+
*
|
|
5123
|
+
* When enabled, logs informational messages to the console.
|
|
5124
|
+
* When disabled, only errors are logged.
|
|
5125
|
+
* Controlled via EditorOptions.enableLogging or Logger.setEnabled().
|
|
5126
|
+
*/
|
|
5127
|
+
let _enabled = false;
|
|
5128
|
+
const Logger = {
|
|
5129
|
+
/** Enable or disable logging. When disabled, only errors are logged. */
|
|
5130
|
+
setEnabled(enabled) {
|
|
5131
|
+
_enabled = enabled;
|
|
5132
|
+
},
|
|
5133
|
+
/** Check if logging is enabled. */
|
|
5134
|
+
isEnabled() {
|
|
5135
|
+
return _enabled;
|
|
5136
|
+
},
|
|
5137
|
+
/** Log an informational message. Only outputs when logging is enabled. */
|
|
5138
|
+
log(...args) {
|
|
5139
|
+
if (_enabled) {
|
|
5140
|
+
console.log(...args);
|
|
5141
|
+
}
|
|
5142
|
+
},
|
|
5143
|
+
/** Log a warning. Only outputs when logging is enabled. */
|
|
5144
|
+
warn(...args) {
|
|
5145
|
+
if (_enabled) {
|
|
5146
|
+
console.warn(...args);
|
|
5147
|
+
}
|
|
5148
|
+
},
|
|
5149
|
+
/** Log an error. Always outputs regardless of logging state. */
|
|
5150
|
+
error(...args) {
|
|
5151
|
+
console.error(...args);
|
|
5152
|
+
}
|
|
5153
|
+
};
|
|
5154
|
+
|
|
5044
5155
|
/**
|
|
5045
5156
|
* TableCell - A cell within a table that contains editable text.
|
|
5046
5157
|
* Implements EditableTextRegion for unified text interaction.
|
|
@@ -5091,7 +5202,7 @@ class TableCell extends EventEmitter {
|
|
|
5091
5202
|
});
|
|
5092
5203
|
// Prevent embedded objects in table cells (only substitution fields allowed)
|
|
5093
5204
|
this._flowingContent.insertEmbeddedObject = () => {
|
|
5094
|
-
|
|
5205
|
+
Logger.warn('[pc-editor:TableCell] Embedded objects are not allowed in table cells. Use insertSubstitutionField instead.');
|
|
5095
5206
|
};
|
|
5096
5207
|
// Set initial content
|
|
5097
5208
|
if (config.content) {
|
|
@@ -5408,7 +5519,7 @@ class TableCell extends EventEmitter {
|
|
|
5408
5519
|
this._reflowDirty = false;
|
|
5409
5520
|
this._lastReflowWidth = width;
|
|
5410
5521
|
this._cachedContentHeight = null; // Clear cached height since lines changed
|
|
5411
|
-
|
|
5522
|
+
Logger.log('[pc-editor:TableCell.reflow] cellId:', this._id, 'text:', JSON.stringify(this._flowingContent.getText()), 'lines:', this._flowedLines.length);
|
|
5412
5523
|
}
|
|
5413
5524
|
/**
|
|
5414
5525
|
* Mark this cell as needing reflow.
|
|
@@ -5446,7 +5557,7 @@ class TableCell extends EventEmitter {
|
|
|
5446
5557
|
return this._editing && this._flowingContent.hasFocus();
|
|
5447
5558
|
}
|
|
5448
5559
|
handleKeyDown(e) {
|
|
5449
|
-
|
|
5560
|
+
Logger.log('[pc-editor:TableCell.handleKeyDown] Key:', e.key, '_editing:', this._editing, 'flowingContent.hasFocus:', this._flowingContent.hasFocus());
|
|
5450
5561
|
if (!this._editing)
|
|
5451
5562
|
return false;
|
|
5452
5563
|
// Let parent table handle Tab navigation
|
|
@@ -5454,9 +5565,9 @@ class TableCell extends EventEmitter {
|
|
|
5454
5565
|
return false; // Not handled - parent will handle
|
|
5455
5566
|
}
|
|
5456
5567
|
// Delegate to FlowingTextContent
|
|
5457
|
-
|
|
5568
|
+
Logger.log('[pc-editor:TableCell.handleKeyDown] Delegating to FlowingTextContent.handleKeyDown');
|
|
5458
5569
|
const handled = this._flowingContent.handleKeyDown(e);
|
|
5459
|
-
|
|
5570
|
+
Logger.log('[pc-editor:TableCell.handleKeyDown] FlowingTextContent handled:', handled);
|
|
5460
5571
|
return handled;
|
|
5461
5572
|
}
|
|
5462
5573
|
onCursorBlink(handler) {
|
|
@@ -5528,28 +5639,49 @@ class TableCell extends EventEmitter {
|
|
|
5528
5639
|
// Serialization
|
|
5529
5640
|
// ============================================
|
|
5530
5641
|
toData() {
|
|
5531
|
-
const
|
|
5532
|
-
const
|
|
5533
|
-
|
|
5534
|
-
formattingRuns.push([index, { ...style }]);
|
|
5535
|
-
});
|
|
5642
|
+
const text = this._flowingContent.getText();
|
|
5643
|
+
const compressedRuns = this._flowingContent.getFormattingManager().getCompressedRuns(text.length);
|
|
5644
|
+
const formattingRuns = compressedRuns.map(run => [run.index, run.formatting]);
|
|
5536
5645
|
// Get substitution fields for serialization
|
|
5537
5646
|
const fields = this._flowingContent.getSubstitutionFieldManager().getFieldsArray();
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5647
|
+
// Only include non-default values to minimize export size
|
|
5648
|
+
const defaults = DEFAULT_TABLE_STYLE;
|
|
5649
|
+
const defaultBorder = DEFAULT_CELL_BORDER_SIDE;
|
|
5650
|
+
const isDefaultBorderSide = (side) => side.width === defaultBorder.width && side.color === defaultBorder.color && side.style === defaultBorder.style;
|
|
5651
|
+
const isDefaultBorder = isDefaultBorderSide(this._border.top) &&
|
|
5652
|
+
isDefaultBorderSide(this._border.right) &&
|
|
5653
|
+
isDefaultBorderSide(this._border.bottom) &&
|
|
5654
|
+
isDefaultBorderSide(this._border.left);
|
|
5655
|
+
const isDefaultPadding = this._padding.top === defaults.cellPadding &&
|
|
5656
|
+
this._padding.right === defaults.cellPadding &&
|
|
5657
|
+
this._padding.bottom === defaults.cellPadding &&
|
|
5658
|
+
this._padding.left === defaults.cellPadding;
|
|
5659
|
+
const data = {};
|
|
5660
|
+
if (this._rowSpan !== 1)
|
|
5661
|
+
data.rowSpan = this._rowSpan;
|
|
5662
|
+
if (this._colSpan !== 1)
|
|
5663
|
+
data.colSpan = this._colSpan;
|
|
5664
|
+
if (this._backgroundColor !== defaults.backgroundColor)
|
|
5665
|
+
data.backgroundColor = this._backgroundColor;
|
|
5666
|
+
if (!isDefaultBorder)
|
|
5667
|
+
data.border = this._border;
|
|
5668
|
+
if (!isDefaultPadding)
|
|
5669
|
+
data.padding = this._padding;
|
|
5670
|
+
if (this._verticalAlign !== 'top')
|
|
5671
|
+
data.verticalAlign = this._verticalAlign;
|
|
5672
|
+
if (text)
|
|
5673
|
+
data.content = text;
|
|
5674
|
+
if (this._fontFamily !== defaults.fontFamily)
|
|
5675
|
+
data.fontFamily = this._fontFamily;
|
|
5676
|
+
if (this._fontSize !== defaults.fontSize)
|
|
5677
|
+
data.fontSize = this._fontSize;
|
|
5678
|
+
if (this._color !== defaults.color)
|
|
5679
|
+
data.color = this._color;
|
|
5680
|
+
if (formattingRuns.length > 0)
|
|
5681
|
+
data.formattingRuns = formattingRuns;
|
|
5682
|
+
if (fields.length > 0)
|
|
5683
|
+
data.substitutionFields = fields;
|
|
5684
|
+
return data;
|
|
5553
5685
|
}
|
|
5554
5686
|
static fromData(data) {
|
|
5555
5687
|
const cell = new TableCell({
|
|
@@ -5565,14 +5697,19 @@ class TableCell extends EventEmitter {
|
|
|
5565
5697
|
fontSize: data.fontSize,
|
|
5566
5698
|
color: data.color
|
|
5567
5699
|
});
|
|
5568
|
-
// Restore formatting runs
|
|
5569
|
-
if (data.formattingRuns) {
|
|
5700
|
+
// Restore formatting runs (run-based: each entry applies from its index to the next)
|
|
5701
|
+
if (data.formattingRuns && data.formattingRuns.length > 0) {
|
|
5570
5702
|
const formattingManager = cell._flowingContent.getFormattingManager();
|
|
5571
|
-
const
|
|
5572
|
-
for (
|
|
5573
|
-
|
|
5703
|
+
const textLength = (data.content || '').length;
|
|
5704
|
+
for (let i = 0; i < data.formattingRuns.length; i++) {
|
|
5705
|
+
const [startIndex, style] = data.formattingRuns[i];
|
|
5706
|
+
const nextIndex = i + 1 < data.formattingRuns.length
|
|
5707
|
+
? data.formattingRuns[i + 1][0]
|
|
5708
|
+
: textLength;
|
|
5709
|
+
if (startIndex < nextIndex) {
|
|
5710
|
+
formattingManager.applyFormatting(startIndex, nextIndex, style);
|
|
5711
|
+
}
|
|
5574
5712
|
}
|
|
5575
|
-
formattingManager.setAllFormatting(formattingMap);
|
|
5576
5713
|
}
|
|
5577
5714
|
// Restore substitution fields
|
|
5578
5715
|
if (data.substitutionFields && Array.isArray(data.substitutionFields)) {
|
|
@@ -5806,13 +5943,17 @@ class TableRow extends EventEmitter {
|
|
|
5806
5943
|
// Serialization
|
|
5807
5944
|
// ============================================
|
|
5808
5945
|
toData() {
|
|
5809
|
-
|
|
5810
|
-
id: this._id,
|
|
5811
|
-
height: this._height,
|
|
5812
|
-
minHeight: this._minHeight,
|
|
5813
|
-
isHeader: this._isHeader,
|
|
5946
|
+
const data = {
|
|
5814
5947
|
cells: this._cells.map(cell => cell.toData())
|
|
5815
5948
|
};
|
|
5949
|
+
// Only include non-default values
|
|
5950
|
+
if (this._height !== null)
|
|
5951
|
+
data.height = this._height;
|
|
5952
|
+
if (this._minHeight !== DEFAULT_TABLE_STYLE.minRowHeight)
|
|
5953
|
+
data.minHeight = this._minHeight;
|
|
5954
|
+
if (this._isHeader)
|
|
5955
|
+
data.isHeader = this._isHeader;
|
|
5956
|
+
return data;
|
|
5816
5957
|
}
|
|
5817
5958
|
static fromData(data) {
|
|
5818
5959
|
const row = new TableRow({
|
|
@@ -6146,7 +6287,7 @@ class TableObject extends BaseEmbeddedObject {
|
|
|
6146
6287
|
set position(value) {
|
|
6147
6288
|
// Tables only support block positioning - ignore any attempt to set other modes
|
|
6148
6289
|
if (value !== 'block') {
|
|
6149
|
-
|
|
6290
|
+
Logger.warn(`[pc-editor:TableObject] Tables only support 'block' positioning. Ignoring attempt to set '${value}'.`);
|
|
6150
6291
|
}
|
|
6151
6292
|
// Always set to block
|
|
6152
6293
|
super.position = 'block';
|
|
@@ -6685,24 +6826,24 @@ class TableObject extends BaseEmbeddedObject {
|
|
|
6685
6826
|
createRowLoop(startRowIndex, endRowIndex, fieldPath) {
|
|
6686
6827
|
// Validate range
|
|
6687
6828
|
if (startRowIndex < 0 || endRowIndex >= this._rows.length) {
|
|
6688
|
-
|
|
6829
|
+
Logger.warn('[pc-editor:TableObject.createRowLoop] Invalid row range');
|
|
6689
6830
|
return null;
|
|
6690
6831
|
}
|
|
6691
6832
|
if (startRowIndex > endRowIndex) {
|
|
6692
|
-
|
|
6833
|
+
Logger.warn('[pc-editor:TableObject.createRowLoop] Start index must be <= end index');
|
|
6693
6834
|
return null;
|
|
6694
6835
|
}
|
|
6695
6836
|
// Check for overlap with existing loops
|
|
6696
6837
|
for (const existingLoop of this._rowLoops.values()) {
|
|
6697
6838
|
if (this.loopRangesOverlap(startRowIndex, endRowIndex, existingLoop.startRowIndex, existingLoop.endRowIndex)) {
|
|
6698
|
-
|
|
6839
|
+
Logger.warn('[pc-editor:TableObject.createRowLoop] Loop range overlaps with existing loop');
|
|
6699
6840
|
return null;
|
|
6700
6841
|
}
|
|
6701
6842
|
}
|
|
6702
6843
|
// Check that loop rows are not header rows
|
|
6703
6844
|
for (let i = startRowIndex; i <= endRowIndex; i++) {
|
|
6704
6845
|
if (this._rows[i]?.isHeader) {
|
|
6705
|
-
|
|
6846
|
+
Logger.warn('[pc-editor:TableObject.createRowLoop] Loop rows cannot be header rows');
|
|
6706
6847
|
return null;
|
|
6707
6848
|
}
|
|
6708
6849
|
}
|
|
@@ -7508,7 +7649,7 @@ class TableObject extends BaseEmbeddedObject {
|
|
|
7508
7649
|
return this._editing;
|
|
7509
7650
|
}
|
|
7510
7651
|
handleKeyDown(e) {
|
|
7511
|
-
|
|
7652
|
+
Logger.log('[pc-editor:TableObject.handleKeyDown] Key:', e.key, '_editing:', this._editing, '_focusedCell:', this._focusedCell);
|
|
7512
7653
|
if (!this._editing)
|
|
7513
7654
|
return false;
|
|
7514
7655
|
// Handle Tab navigation
|
|
@@ -7617,6 +7758,9 @@ class TableObject extends BaseEmbeddedObject {
|
|
|
7617
7758
|
result.mergedCell.markReflowDirty();
|
|
7618
7759
|
}
|
|
7619
7760
|
this.clearSelection();
|
|
7761
|
+
// Focus the anchor (top-left) cell of the merged range
|
|
7762
|
+
const normalized = TableCellMerger.normalizeRange(mergeRange);
|
|
7763
|
+
this.focusCell(normalized.start.row, normalized.start.col);
|
|
7620
7764
|
this.emit('cells-merged', { range: mergeRange });
|
|
7621
7765
|
this.emit('content-changed', {});
|
|
7622
7766
|
}
|
|
@@ -8296,14 +8440,20 @@ class EmbeddedObjectFactory {
|
|
|
8296
8440
|
border: data.data.border,
|
|
8297
8441
|
padding: data.data.padding
|
|
8298
8442
|
});
|
|
8299
|
-
// Restore formatting runs
|
|
8443
|
+
// Restore formatting runs (run-based: each entry applies from its index to the next)
|
|
8300
8444
|
if (data.data.formattingRuns && Array.isArray(data.data.formattingRuns)) {
|
|
8301
|
-
const
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8445
|
+
const runs = data.data.formattingRuns;
|
|
8446
|
+
if (runs.length > 0) {
|
|
8447
|
+
const formattingManager = textBox.flowingContent.getFormattingManager();
|
|
8448
|
+
const textLength = textBox.flowingContent.getText().length;
|
|
8449
|
+
for (let i = 0; i < runs.length; i++) {
|
|
8450
|
+
const [startIndex, style] = runs[i];
|
|
8451
|
+
const nextIndex = i + 1 < runs.length ? runs[i + 1][0] : textLength;
|
|
8452
|
+
if (startIndex < nextIndex) {
|
|
8453
|
+
formattingManager.applyFormatting(startIndex, nextIndex, style);
|
|
8454
|
+
}
|
|
8455
|
+
}
|
|
8305
8456
|
}
|
|
8306
|
-
formattingManager.setAllFormatting(formattingMap);
|
|
8307
8457
|
}
|
|
8308
8458
|
// Restore substitution fields if present
|
|
8309
8459
|
if (data.data.substitutionFields && Array.isArray(data.data.substitutionFields)) {
|
|
@@ -9280,9 +9430,9 @@ class FlowingTextContent extends EventEmitter {
|
|
|
9280
9430
|
* @returns true if the event was handled, false otherwise
|
|
9281
9431
|
*/
|
|
9282
9432
|
handleKeyDown(e) {
|
|
9283
|
-
|
|
9433
|
+
Logger.log('[pc-editor:FlowingTextContent.handleKeyDown] Key:', e.key, '_hasFocus:', this._hasFocus);
|
|
9284
9434
|
if (!this._hasFocus) {
|
|
9285
|
-
|
|
9435
|
+
Logger.log('[pc-editor:FlowingTextContent.handleKeyDown] No focus, returning false');
|
|
9286
9436
|
return false;
|
|
9287
9437
|
}
|
|
9288
9438
|
switch (e.key) {
|
|
@@ -9797,46 +9947,19 @@ class FlowingTextContent extends EventEmitter {
|
|
|
9797
9947
|
toData() {
|
|
9798
9948
|
// Serialize text content
|
|
9799
9949
|
const text = this.textState.getText();
|
|
9800
|
-
// Serialize text formatting as runs
|
|
9801
|
-
|
|
9802
|
-
const formattingRuns =
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
|
|
9809
|
-
|
|
9810
|
-
|
|
9811
|
-
currentFormat.fontWeight !== lastFormat.fontWeight ||
|
|
9812
|
-
currentFormat.fontStyle !== lastFormat.fontStyle ||
|
|
9813
|
-
currentFormat.color !== lastFormat.color ||
|
|
9814
|
-
currentFormat.backgroundColor !== lastFormat.backgroundColor;
|
|
9815
|
-
if (formatChanged) {
|
|
9816
|
-
// Only output if different from default (to further reduce size)
|
|
9817
|
-
const isDefault = currentFormat.fontFamily === defaultFormat.fontFamily &&
|
|
9818
|
-
currentFormat.fontSize === defaultFormat.fontSize &&
|
|
9819
|
-
currentFormat.fontWeight === defaultFormat.fontWeight &&
|
|
9820
|
-
currentFormat.fontStyle === defaultFormat.fontStyle &&
|
|
9821
|
-
currentFormat.color === defaultFormat.color &&
|
|
9822
|
-
currentFormat.backgroundColor === defaultFormat.backgroundColor;
|
|
9823
|
-
// Always output first run if it's not default, or output when format changes
|
|
9824
|
-
if (!isDefault || formattingRuns.length > 0) {
|
|
9825
|
-
formattingRuns.push({
|
|
9826
|
-
index: i,
|
|
9827
|
-
formatting: {
|
|
9828
|
-
fontFamily: currentFormat.fontFamily,
|
|
9829
|
-
fontSize: currentFormat.fontSize,
|
|
9830
|
-
fontWeight: currentFormat.fontWeight,
|
|
9831
|
-
fontStyle: currentFormat.fontStyle,
|
|
9832
|
-
color: currentFormat.color,
|
|
9833
|
-
backgroundColor: currentFormat.backgroundColor
|
|
9834
|
-
}
|
|
9835
|
-
});
|
|
9836
|
-
}
|
|
9837
|
-
lastFormat = currentFormat;
|
|
9950
|
+
// Serialize text formatting as compressed runs (only at change boundaries)
|
|
9951
|
+
const compressedRuns = this.formatting.getCompressedRuns(text.length);
|
|
9952
|
+
const formattingRuns = compressedRuns.map(run => ({
|
|
9953
|
+
index: run.index,
|
|
9954
|
+
formatting: {
|
|
9955
|
+
fontFamily: run.formatting.fontFamily,
|
|
9956
|
+
fontSize: run.formatting.fontSize,
|
|
9957
|
+
fontWeight: run.formatting.fontWeight,
|
|
9958
|
+
fontStyle: run.formatting.fontStyle,
|
|
9959
|
+
color: run.formatting.color,
|
|
9960
|
+
backgroundColor: run.formatting.backgroundColor
|
|
9838
9961
|
}
|
|
9839
|
-
}
|
|
9962
|
+
}));
|
|
9840
9963
|
// Serialize paragraph formatting
|
|
9841
9964
|
const paragraphFormatting = this.paragraphFormatting.toJSON();
|
|
9842
9965
|
// Serialize substitution fields
|
|
@@ -9932,7 +10055,7 @@ class FlowingTextContent extends EventEmitter {
|
|
|
9932
10055
|
content.getEmbeddedObjectManager().insert(object, ref.textIndex);
|
|
9933
10056
|
}
|
|
9934
10057
|
else {
|
|
9935
|
-
|
|
10058
|
+
Logger.warn(`[pc-editor:FlowingTextContent] Failed to create embedded object of type: ${ref.object.objectType}`);
|
|
9936
10059
|
}
|
|
9937
10060
|
}
|
|
9938
10061
|
}
|
|
@@ -9986,7 +10109,7 @@ class FlowingTextContent extends EventEmitter {
|
|
|
9986
10109
|
this.embeddedObjects.insert(object, ref.textIndex);
|
|
9987
10110
|
}
|
|
9988
10111
|
else {
|
|
9989
|
-
|
|
10112
|
+
Logger.warn(`[pc-editor:FlowingTextContent] Failed to create embedded object of type: ${ref.object.objectType}`);
|
|
9990
10113
|
}
|
|
9991
10114
|
}
|
|
9992
10115
|
}
|
|
@@ -12525,7 +12648,7 @@ class FlowingTextRenderer extends EventEmitter {
|
|
|
12525
12648
|
updateResizeHandleTargets(selectedObjects) {
|
|
12526
12649
|
// Clear existing resize handle targets
|
|
12527
12650
|
this._hitTestManager.clearCategory('resize-handles');
|
|
12528
|
-
|
|
12651
|
+
Logger.log('[pc-editor:FlowingTextRenderer] updateResizeHandleTargets selectedObjects:', selectedObjects.length);
|
|
12529
12652
|
// Register resize handles for each selected object
|
|
12530
12653
|
for (const object of selectedObjects) {
|
|
12531
12654
|
if (!object.resizable)
|
|
@@ -12538,7 +12661,7 @@ class FlowingTextRenderer extends EventEmitter {
|
|
|
12538
12661
|
// For regular objects, use renderedPosition
|
|
12539
12662
|
const pos = object.renderedPosition;
|
|
12540
12663
|
const pageIndex = object.renderedPageIndex;
|
|
12541
|
-
|
|
12664
|
+
Logger.log('[pc-editor:FlowingTextRenderer] updateResizeHandleTargets object:', object.id, 'pageIndex:', pageIndex, 'pos:', pos);
|
|
12542
12665
|
if (pos && pageIndex >= 0) {
|
|
12543
12666
|
this.registerObjectResizeHandles(object, pageIndex, pos);
|
|
12544
12667
|
}
|
|
@@ -14194,7 +14317,8 @@ class CanvasManager extends EventEmitter {
|
|
|
14194
14317
|
const pageIndex = this.document.pages.findIndex(p => p.id === pageId);
|
|
14195
14318
|
// Get the slice for this page (for multi-page tables)
|
|
14196
14319
|
const slice = table.getRenderedSlice(pageIndex);
|
|
14197
|
-
const tablePosition = slice?.position ||
|
|
14320
|
+
const tablePosition = slice?.position ||
|
|
14321
|
+
(table.renderedPageIndex === pageIndex ? table.renderedPosition : null);
|
|
14198
14322
|
const sliceHeight = slice?.height || table.height;
|
|
14199
14323
|
// Check if point is within the table slice on this page
|
|
14200
14324
|
const isInsideTable = tablePosition &&
|
|
@@ -14227,6 +14351,7 @@ class CanvasManager extends EventEmitter {
|
|
|
14227
14351
|
end: cellAddr
|
|
14228
14352
|
});
|
|
14229
14353
|
this.render();
|
|
14354
|
+
this.emit('table-cell-selection-changed', { table });
|
|
14230
14355
|
e.preventDefault();
|
|
14231
14356
|
return;
|
|
14232
14357
|
}
|
|
@@ -14506,7 +14631,8 @@ class CanvasManager extends EventEmitter {
|
|
|
14506
14631
|
const currentPageIndex = this.document.pages.findIndex(p => p.id === pageId);
|
|
14507
14632
|
// Get the slice for the current page (for multi-page tables)
|
|
14508
14633
|
const slice = table.getRenderedSlice(currentPageIndex);
|
|
14509
|
-
const tablePosition = slice?.position ||
|
|
14634
|
+
const tablePosition = slice?.position ||
|
|
14635
|
+
(table.renderedPageIndex === currentPageIndex ? table.renderedPosition : null);
|
|
14510
14636
|
const sliceHeight = slice?.height || table.height;
|
|
14511
14637
|
if (tablePosition) {
|
|
14512
14638
|
// Check if point is within the table slice on this page
|
|
@@ -14536,6 +14662,7 @@ class CanvasManager extends EventEmitter {
|
|
|
14536
14662
|
end: cellAddr
|
|
14537
14663
|
});
|
|
14538
14664
|
this.render();
|
|
14665
|
+
this.emit('table-cell-selection-changed', { table });
|
|
14539
14666
|
}
|
|
14540
14667
|
}
|
|
14541
14668
|
}
|
|
@@ -15079,40 +15206,46 @@ class CanvasManager extends EventEmitter {
|
|
|
15079
15206
|
canvas.style.cursor = 'move';
|
|
15080
15207
|
return;
|
|
15081
15208
|
}
|
|
15082
|
-
// Show text cursor for
|
|
15083
|
-
if (object instanceof TextBoxObject) {
|
|
15084
|
-
canvas.style.cursor =
|
|
15085
|
-
return;
|
|
15209
|
+
// Show text cursor for objects in edit mode, arrow otherwise
|
|
15210
|
+
if (object instanceof TextBoxObject && this.editingTextBox === object) {
|
|
15211
|
+
canvas.style.cursor = CanvasManager.TEXT_CURSOR;
|
|
15086
15212
|
}
|
|
15213
|
+
else if (object instanceof TableObject && this._focusedControl === object) {
|
|
15214
|
+
canvas.style.cursor = CanvasManager.TEXT_CURSOR;
|
|
15215
|
+
}
|
|
15216
|
+
else {
|
|
15217
|
+
canvas.style.cursor = 'default';
|
|
15218
|
+
}
|
|
15219
|
+
return;
|
|
15087
15220
|
}
|
|
15088
15221
|
}
|
|
15089
15222
|
// Check for table cells (show text cursor)
|
|
15090
15223
|
const tableCellHit = hitTestManager.queryByType(pageIndex, point, 'table-cell');
|
|
15091
15224
|
if (tableCellHit && tableCellHit.data.type === 'table-cell') {
|
|
15092
|
-
canvas.style.cursor =
|
|
15225
|
+
canvas.style.cursor = CanvasManager.TEXT_CURSOR;
|
|
15093
15226
|
return;
|
|
15094
15227
|
}
|
|
15095
15228
|
// Check for text regions (body, header, footer - show text cursor)
|
|
15096
15229
|
const textRegionHit = hitTestManager.queryByType(pageIndex, point, 'text-region');
|
|
15097
15230
|
if (textRegionHit && textRegionHit.data.type === 'text-region') {
|
|
15098
|
-
canvas.style.cursor =
|
|
15231
|
+
canvas.style.cursor = CanvasManager.TEXT_CURSOR;
|
|
15099
15232
|
return;
|
|
15100
15233
|
}
|
|
15101
15234
|
// Also check if point is within any editable region (body, header, footer)
|
|
15102
15235
|
// This catches cases where text region hit targets may not cover empty space
|
|
15103
15236
|
const bodyRegion = this.regionManager.getBodyRegion();
|
|
15104
15237
|
if (bodyRegion && bodyRegion.containsPointInRegion(point, pageIndex)) {
|
|
15105
|
-
canvas.style.cursor =
|
|
15238
|
+
canvas.style.cursor = CanvasManager.TEXT_CURSOR;
|
|
15106
15239
|
return;
|
|
15107
15240
|
}
|
|
15108
15241
|
const headerRegion = this.regionManager.getHeaderRegion();
|
|
15109
15242
|
if (headerRegion && headerRegion.containsPointInRegion(point, pageIndex)) {
|
|
15110
|
-
canvas.style.cursor =
|
|
15243
|
+
canvas.style.cursor = CanvasManager.TEXT_CURSOR;
|
|
15111
15244
|
return;
|
|
15112
15245
|
}
|
|
15113
15246
|
const footerRegion = this.regionManager.getFooterRegion();
|
|
15114
15247
|
if (footerRegion && footerRegion.containsPointInRegion(point, pageIndex)) {
|
|
15115
|
-
canvas.style.cursor =
|
|
15248
|
+
canvas.style.cursor = CanvasManager.TEXT_CURSOR;
|
|
15116
15249
|
return;
|
|
15117
15250
|
}
|
|
15118
15251
|
canvas.style.cursor = 'default';
|
|
@@ -15130,7 +15263,8 @@ class CanvasManager extends EventEmitter {
|
|
|
15130
15263
|
const { table, dividerType, index } = target.data;
|
|
15131
15264
|
// Get the table position from slice info
|
|
15132
15265
|
const slice = table.getRenderedSlice(pageIndex);
|
|
15133
|
-
const tablePosition = slice?.position ||
|
|
15266
|
+
const tablePosition = slice?.position ||
|
|
15267
|
+
(table.renderedPageIndex === pageIndex ? table.renderedPosition : null);
|
|
15134
15268
|
if (tablePosition) {
|
|
15135
15269
|
// Calculate the divider position based on type and index
|
|
15136
15270
|
let position;
|
|
@@ -15184,7 +15318,7 @@ class CanvasManager extends EventEmitter {
|
|
|
15184
15318
|
this.emit('element-removed', { elementId: objectId });
|
|
15185
15319
|
}
|
|
15186
15320
|
selectElement(elementId) {
|
|
15187
|
-
|
|
15321
|
+
Logger.log('[pc-editor:CanvasManager] Selecting element:', elementId);
|
|
15188
15322
|
this.selectedElements.add(elementId);
|
|
15189
15323
|
// Update embedded object's selected state
|
|
15190
15324
|
const flowingContents = [
|
|
@@ -15196,12 +15330,12 @@ class CanvasManager extends EventEmitter {
|
|
|
15196
15330
|
const embeddedObjects = flowingContent.getEmbeddedObjects();
|
|
15197
15331
|
for (const [, obj] of embeddedObjects.entries()) {
|
|
15198
15332
|
if (obj.id === elementId) {
|
|
15199
|
-
|
|
15333
|
+
Logger.log('[pc-editor:CanvasManager] Found embedded object to select:', obj.id);
|
|
15200
15334
|
obj.selected = true;
|
|
15201
15335
|
}
|
|
15202
15336
|
}
|
|
15203
15337
|
}
|
|
15204
|
-
|
|
15338
|
+
Logger.log('[pc-editor:CanvasManager] Selected elements after selection:', Array.from(this.selectedElements));
|
|
15205
15339
|
this.render();
|
|
15206
15340
|
this.updateResizeHandleHitTargets();
|
|
15207
15341
|
this.emit('selection-change', { selectedElements: Array.from(this.selectedElements) });
|
|
@@ -15251,10 +15385,10 @@ class CanvasManager extends EventEmitter {
|
|
|
15251
15385
|
this.flowingTextRenderer.updateResizeHandleTargets(selectedObjects);
|
|
15252
15386
|
}
|
|
15253
15387
|
clearSelection() {
|
|
15254
|
-
|
|
15388
|
+
Logger.log('[pc-editor:CanvasManager] clearSelection called, current selected elements:', Array.from(this.selectedElements));
|
|
15255
15389
|
// Clear selected state on all embedded objects
|
|
15256
15390
|
this.selectedElements.forEach(elementId => {
|
|
15257
|
-
|
|
15391
|
+
Logger.log('[pc-editor:CanvasManager] Clearing selection for element:', elementId);
|
|
15258
15392
|
// Check embedded objects in all flowing content sources (body, header, footer)
|
|
15259
15393
|
const flowingContents = [
|
|
15260
15394
|
this.document.bodyFlowingContent,
|
|
@@ -15265,7 +15399,7 @@ class CanvasManager extends EventEmitter {
|
|
|
15265
15399
|
const embeddedObjects = flowingContent.getEmbeddedObjects();
|
|
15266
15400
|
for (const [, embeddedObj] of embeddedObjects.entries()) {
|
|
15267
15401
|
if (embeddedObj.id === elementId) {
|
|
15268
|
-
|
|
15402
|
+
Logger.log('[pc-editor:CanvasManager] Clearing selection on embedded object:', elementId);
|
|
15269
15403
|
embeddedObj.selected = false;
|
|
15270
15404
|
}
|
|
15271
15405
|
}
|
|
@@ -15273,7 +15407,7 @@ class CanvasManager extends EventEmitter {
|
|
|
15273
15407
|
});
|
|
15274
15408
|
this.selectedElements.clear();
|
|
15275
15409
|
this.selectedSectionId = null;
|
|
15276
|
-
|
|
15410
|
+
Logger.log('[pc-editor:CanvasManager] About to render after clearing selection...');
|
|
15277
15411
|
this.render();
|
|
15278
15412
|
this.updateResizeHandleHitTargets();
|
|
15279
15413
|
this.emit('selection-change', { selectedElements: [] });
|
|
@@ -15522,7 +15656,7 @@ class CanvasManager extends EventEmitter {
|
|
|
15522
15656
|
});
|
|
15523
15657
|
// Handle substitution field clicks
|
|
15524
15658
|
this.flowingTextRenderer.on('substitution-field-clicked', (data) => {
|
|
15525
|
-
|
|
15659
|
+
Logger.log('[pc-editor:CanvasManager] substitution-field-clicked Field:', data.field?.fieldName, 'Section:', data.section);
|
|
15526
15660
|
// Emit event for external handling (e.g., showing field properties panel)
|
|
15527
15661
|
this.emit('substitution-field-clicked', data);
|
|
15528
15662
|
});
|
|
@@ -15858,7 +15992,9 @@ class CanvasManager extends EventEmitter {
|
|
|
15858
15992
|
if (obj instanceof TableObject) {
|
|
15859
15993
|
// For multi-page tables, check if this page has a rendered slice
|
|
15860
15994
|
const slice = obj.getRenderedSlice(pageIndex);
|
|
15861
|
-
|
|
15995
|
+
// Only use renderedPosition if the table was actually rendered on this page
|
|
15996
|
+
const tablePosition = slice?.position ||
|
|
15997
|
+
(obj.renderedPageIndex === pageIndex ? obj.renderedPosition : null);
|
|
15862
15998
|
if (tablePosition) {
|
|
15863
15999
|
// Check if point is inside the table slice on this page
|
|
15864
16000
|
const sliceHeight = slice?.height || obj.height;
|
|
@@ -15997,14 +16133,15 @@ class CanvasManager extends EventEmitter {
|
|
|
15997
16133
|
this.editingTextBox = textBox;
|
|
15998
16134
|
this._editingTextBoxPageId = pageId || null;
|
|
15999
16135
|
if (textBox) {
|
|
16000
|
-
// Use the unified focus system to handle focus/blur and cursor blink
|
|
16001
|
-
// This blurs the previous control, hiding its cursor
|
|
16002
|
-
this.setFocus(textBox);
|
|
16003
16136
|
// Clear selection in main flowing content
|
|
16004
16137
|
this.document.bodyFlowingContent.clearSelection();
|
|
16005
|
-
// Select the text box
|
|
16138
|
+
// Select the text box visually (this calls setFocus(null) internally,
|
|
16139
|
+
// so we must set focus to the text box AFTER this call)
|
|
16006
16140
|
this.clearSelection();
|
|
16007
16141
|
this.selectInlineElement({ type: 'embedded-object', object: textBox, textIndex: textBox.textIndex });
|
|
16142
|
+
// Now set focus to the text box for editing — must be AFTER selectInlineElement
|
|
16143
|
+
// because selectBaseEmbeddedObject calls setFocus(null) which would undo it
|
|
16144
|
+
this.setFocus(textBox);
|
|
16008
16145
|
this.emit('textbox-editing-started', { textBox });
|
|
16009
16146
|
}
|
|
16010
16147
|
else {
|
|
@@ -16101,6 +16238,10 @@ class CanvasManager extends EventEmitter {
|
|
|
16101
16238
|
}
|
|
16102
16239
|
CanvasManager.CELL_SELECTION_THRESHOLD = 5; // Minimum pixels to drag before cell selection starts
|
|
16103
16240
|
CanvasManager.RELATIVE_DRAG_THRESHOLD = 3; // Minimum pixels to drag before moving starts
|
|
16241
|
+
// Custom text cursor as a black I-beam SVG data URI.
|
|
16242
|
+
// The native 'text' cursor can render as white on Windows browsers,
|
|
16243
|
+
// making it invisible over the white canvas background.
|
|
16244
|
+
CanvasManager.TEXT_CURSOR = "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='20' viewBox='0 0 16 20'%3E%3Cpath d='M5 1h2v1h2V1h2v2h-2v6h2v2h-2v6h2v2h-2v-1H7v1H5v-2h2v-6H5V9h2V3H5z' fill='%23000'/%3E%3C/svg%3E\") 8 10, text";
|
|
16104
16245
|
|
|
16105
16246
|
/**
|
|
16106
16247
|
* DataBinder handles binding data to documents.
|
|
@@ -16743,7 +16884,15 @@ class PDFGenerator {
|
|
|
16743
16884
|
// Check if it's a data URL we can embed
|
|
16744
16885
|
if (src.startsWith('data:')) {
|
|
16745
16886
|
try {
|
|
16746
|
-
|
|
16887
|
+
let embeddedImage = await this.embedImageFromDataUrl(pdfDoc, src);
|
|
16888
|
+
// If the format isn't directly supported (e.g., SVG, WebP, GIF),
|
|
16889
|
+
// convert to PNG via canvas and try again
|
|
16890
|
+
if (!embeddedImage) {
|
|
16891
|
+
const pngDataUrl = image.toPngDataUrl();
|
|
16892
|
+
if (pngDataUrl) {
|
|
16893
|
+
embeddedImage = await this.embedImageFromDataUrl(pdfDoc, pngDataUrl);
|
|
16894
|
+
}
|
|
16895
|
+
}
|
|
16747
16896
|
if (embeddedImage) {
|
|
16748
16897
|
// Calculate draw position/size based on fit mode
|
|
16749
16898
|
const drawParams = this.calculateImageDrawParams(embeddedImage.width, embeddedImage.height, image.width, image.height, image.fit);
|
|
@@ -16766,7 +16915,7 @@ class PDFGenerator {
|
|
|
16766
16915
|
}
|
|
16767
16916
|
}
|
|
16768
16917
|
catch (e) {
|
|
16769
|
-
|
|
16918
|
+
Logger.warn('[pc-editor:PDFGenerator] Failed to embed image:', e);
|
|
16770
16919
|
}
|
|
16771
16920
|
}
|
|
16772
16921
|
// Fallback: draw placeholder rectangle for images we can't embed
|
|
@@ -18794,7 +18943,7 @@ class MutationUndo {
|
|
|
18794
18943
|
this.undoTableStructure(mutation);
|
|
18795
18944
|
break;
|
|
18796
18945
|
default:
|
|
18797
|
-
|
|
18946
|
+
Logger.warn('[pc-editor:MutationUndo] Unknown mutation type for undo:', mutation.type);
|
|
18798
18947
|
}
|
|
18799
18948
|
}
|
|
18800
18949
|
/**
|
|
@@ -18844,7 +18993,7 @@ class MutationUndo {
|
|
|
18844
18993
|
this.redoTableStructure(mutation);
|
|
18845
18994
|
break;
|
|
18846
18995
|
default:
|
|
18847
|
-
|
|
18996
|
+
Logger.warn('[pc-editor:MutationUndo] Unknown mutation type for redo:', mutation.type);
|
|
18848
18997
|
}
|
|
18849
18998
|
}
|
|
18850
18999
|
// --- Text Mutations ---
|
|
@@ -20176,13 +20325,13 @@ class PDFParser {
|
|
|
20176
20325
|
}
|
|
20177
20326
|
catch {
|
|
20178
20327
|
// Skip images that fail to extract
|
|
20179
|
-
|
|
20328
|
+
Logger.warn(`[pc-editor:PDFParser] Failed to extract image: ${imageName}`);
|
|
20180
20329
|
}
|
|
20181
20330
|
}
|
|
20182
20331
|
}
|
|
20183
20332
|
}
|
|
20184
20333
|
catch (error) {
|
|
20185
|
-
|
|
20334
|
+
Logger.warn('[pc-editor:PDFParser] Image extraction failed:', error);
|
|
20186
20335
|
}
|
|
20187
20336
|
return images;
|
|
20188
20337
|
}
|
|
@@ -21209,6 +21358,8 @@ class PCEditor extends EventEmitter {
|
|
|
21209
21358
|
}
|
|
21210
21359
|
this.container = container;
|
|
21211
21360
|
this.options = this.mergeOptions(options);
|
|
21361
|
+
// Initialize logging
|
|
21362
|
+
Logger.setEnabled(this.options.enableLogging ?? false);
|
|
21212
21363
|
this.document = new Document();
|
|
21213
21364
|
// Apply constructor options to document settings
|
|
21214
21365
|
this.document.updateSettings({
|
|
@@ -21233,7 +21384,8 @@ class PCEditor extends EventEmitter {
|
|
|
21233
21384
|
showControlCharacters: options?.showControlCharacters ?? false,
|
|
21234
21385
|
defaultFont: options?.defaultFont || 'Arial',
|
|
21235
21386
|
defaultFontSize: options?.defaultFontSize || 12,
|
|
21236
|
-
theme: options?.theme || 'light'
|
|
21387
|
+
theme: options?.theme || 'light',
|
|
21388
|
+
enableLogging: options?.enableLogging ?? false
|
|
21237
21389
|
};
|
|
21238
21390
|
}
|
|
21239
21391
|
initialize() {
|
|
@@ -21378,6 +21530,10 @@ class PCEditor extends EventEmitter {
|
|
|
21378
21530
|
this.canvasManager.on('tablecell-cursor-changed', (data) => {
|
|
21379
21531
|
this.emit('tablecell-cursor-changed', data);
|
|
21380
21532
|
});
|
|
21533
|
+
// Forward table cell selection changes (multi-cell drag/shift-click)
|
|
21534
|
+
this.canvasManager.on('table-cell-selection-changed', (data) => {
|
|
21535
|
+
this.emit('table-cell-selection-changed', data);
|
|
21536
|
+
});
|
|
21381
21537
|
this.canvasManager.on('repeating-section-clicked', (data) => {
|
|
21382
21538
|
// Repeating section clicked - update selection state
|
|
21383
21539
|
if (data.section && data.section.id) {
|
|
@@ -21640,6 +21796,7 @@ class PCEditor extends EventEmitter {
|
|
|
21640
21796
|
* This changes which section receives keyboard input and cursor positioning.
|
|
21641
21797
|
*/
|
|
21642
21798
|
setActiveSection(section) {
|
|
21799
|
+
Logger.log('[pc-editor] setActiveSection', section);
|
|
21643
21800
|
if (this._activeEditingSection !== section) {
|
|
21644
21801
|
this._activeEditingSection = section;
|
|
21645
21802
|
// Delegate to canvas manager which handles the section change and emits events
|
|
@@ -21730,6 +21887,7 @@ class PCEditor extends EventEmitter {
|
|
|
21730
21887
|
}
|
|
21731
21888
|
}
|
|
21732
21889
|
loadDocument(documentData) {
|
|
21890
|
+
Logger.log('[pc-editor] loadDocument');
|
|
21733
21891
|
if (!this._isReady) {
|
|
21734
21892
|
throw new Error('Editor is not ready');
|
|
21735
21893
|
}
|
|
@@ -21756,6 +21914,7 @@ class PCEditor extends EventEmitter {
|
|
|
21756
21914
|
return this.document.toData();
|
|
21757
21915
|
}
|
|
21758
21916
|
bindData(data) {
|
|
21917
|
+
Logger.log('[pc-editor] bindData');
|
|
21759
21918
|
if (!this._isReady) {
|
|
21760
21919
|
throw new Error('Editor is not ready');
|
|
21761
21920
|
}
|
|
@@ -21764,6 +21923,7 @@ class PCEditor extends EventEmitter {
|
|
|
21764
21923
|
this.emit('data-bound', { data });
|
|
21765
21924
|
}
|
|
21766
21925
|
async exportPDF(options) {
|
|
21926
|
+
Logger.log('[pc-editor] exportPDF');
|
|
21767
21927
|
if (!this._isReady) {
|
|
21768
21928
|
throw new Error('Editor is not ready');
|
|
21769
21929
|
}
|
|
@@ -21805,6 +21965,7 @@ class PCEditor extends EventEmitter {
|
|
|
21805
21965
|
* @returns JSON string representation of the document
|
|
21806
21966
|
*/
|
|
21807
21967
|
saveDocument() {
|
|
21968
|
+
Logger.log('[pc-editor] saveDocument');
|
|
21808
21969
|
if (!this._isReady) {
|
|
21809
21970
|
throw new Error('Editor is not ready');
|
|
21810
21971
|
}
|
|
@@ -21816,6 +21977,7 @@ class PCEditor extends EventEmitter {
|
|
|
21816
21977
|
* @param filename Optional filename (defaults to 'document.pceditor.json')
|
|
21817
21978
|
*/
|
|
21818
21979
|
saveDocumentToFile(filename = 'document.pceditor.json') {
|
|
21980
|
+
Logger.log('[pc-editor] saveDocumentToFile', filename);
|
|
21819
21981
|
const jsonString = this.saveDocument();
|
|
21820
21982
|
const blob = new Blob([jsonString], { type: 'application/json' });
|
|
21821
21983
|
const url = URL.createObjectURL(blob);
|
|
@@ -21833,6 +21995,7 @@ class PCEditor extends EventEmitter {
|
|
|
21833
21995
|
* @param jsonString JSON string representation of the document
|
|
21834
21996
|
*/
|
|
21835
21997
|
loadDocumentFromJSON(jsonString) {
|
|
21998
|
+
Logger.log('[pc-editor] loadDocumentFromJSON');
|
|
21836
21999
|
if (!this._isReady) {
|
|
21837
22000
|
throw new Error('Editor is not ready');
|
|
21838
22001
|
}
|
|
@@ -21853,6 +22016,7 @@ class PCEditor extends EventEmitter {
|
|
|
21853
22016
|
* @returns Promise that resolves when loading is complete
|
|
21854
22017
|
*/
|
|
21855
22018
|
async loadDocumentFromFile(file) {
|
|
22019
|
+
Logger.log('[pc-editor] loadDocumentFromFile', file.name);
|
|
21856
22020
|
if (!this._isReady) {
|
|
21857
22021
|
throw new Error('Editor is not ready');
|
|
21858
22022
|
}
|
|
@@ -21941,22 +22105,25 @@ class PCEditor extends EventEmitter {
|
|
|
21941
22105
|
// Version compatibility check
|
|
21942
22106
|
const [major] = doc.version.split('.').map(Number);
|
|
21943
22107
|
if (major > 1) {
|
|
21944
|
-
|
|
22108
|
+
Logger.warn(`[pc-editor] Document version ${doc.version} may not be fully compatible with this editor`);
|
|
21945
22109
|
}
|
|
21946
22110
|
}
|
|
21947
22111
|
selectElement(elementId) {
|
|
22112
|
+
Logger.log('[pc-editor] selectElement', elementId);
|
|
21948
22113
|
if (!this._isReady) {
|
|
21949
22114
|
throw new Error('Editor is not ready');
|
|
21950
22115
|
}
|
|
21951
22116
|
this.canvasManager.selectElement(elementId);
|
|
21952
22117
|
}
|
|
21953
22118
|
clearSelection() {
|
|
22119
|
+
Logger.log('[pc-editor] clearSelection');
|
|
21954
22120
|
if (!this._isReady) {
|
|
21955
22121
|
throw new Error('Editor is not ready');
|
|
21956
22122
|
}
|
|
21957
22123
|
this.canvasManager.clearSelection();
|
|
21958
22124
|
}
|
|
21959
22125
|
removeEmbeddedObject(objectId) {
|
|
22126
|
+
Logger.log('[pc-editor] removeEmbeddedObject', objectId);
|
|
21960
22127
|
if (!this._isReady) {
|
|
21961
22128
|
throw new Error('Editor is not ready');
|
|
21962
22129
|
}
|
|
@@ -21966,6 +22133,7 @@ class PCEditor extends EventEmitter {
|
|
|
21966
22133
|
* Undo the last operation.
|
|
21967
22134
|
*/
|
|
21968
22135
|
undo() {
|
|
22136
|
+
Logger.log('[pc-editor] undo');
|
|
21969
22137
|
if (!this._isReady)
|
|
21970
22138
|
return;
|
|
21971
22139
|
const success = this.transactionManager.undo();
|
|
@@ -21978,6 +22146,7 @@ class PCEditor extends EventEmitter {
|
|
|
21978
22146
|
* Redo the last undone operation.
|
|
21979
22147
|
*/
|
|
21980
22148
|
redo() {
|
|
22149
|
+
Logger.log('[pc-editor] redo');
|
|
21981
22150
|
if (!this._isReady)
|
|
21982
22151
|
return;
|
|
21983
22152
|
const success = this.transactionManager.redo();
|
|
@@ -22011,16 +22180,19 @@ class PCEditor extends EventEmitter {
|
|
|
22011
22180
|
this.transactionManager.setMaxHistory(count);
|
|
22012
22181
|
}
|
|
22013
22182
|
zoomIn() {
|
|
22183
|
+
Logger.log('[pc-editor] zoomIn');
|
|
22014
22184
|
if (!this._isReady)
|
|
22015
22185
|
return;
|
|
22016
22186
|
this.canvasManager.zoomIn();
|
|
22017
22187
|
}
|
|
22018
22188
|
zoomOut() {
|
|
22189
|
+
Logger.log('[pc-editor] zoomOut');
|
|
22019
22190
|
if (!this._isReady)
|
|
22020
22191
|
return;
|
|
22021
22192
|
this.canvasManager.zoomOut();
|
|
22022
22193
|
}
|
|
22023
22194
|
setZoom(level) {
|
|
22195
|
+
Logger.log('[pc-editor] setZoom', level);
|
|
22024
22196
|
if (!this._isReady)
|
|
22025
22197
|
return;
|
|
22026
22198
|
this.canvasManager.setZoom(level);
|
|
@@ -22057,6 +22229,7 @@ class PCEditor extends EventEmitter {
|
|
|
22057
22229
|
return this.canvasManager.getContentOffset();
|
|
22058
22230
|
}
|
|
22059
22231
|
fitToWidth() {
|
|
22232
|
+
Logger.log('[pc-editor] fitToWidth');
|
|
22060
22233
|
if (!this._isReady)
|
|
22061
22234
|
return;
|
|
22062
22235
|
this.canvasManager.fitToWidth();
|
|
@@ -22065,23 +22238,27 @@ class PCEditor extends EventEmitter {
|
|
|
22065
22238
|
* Force a re-render of the canvas.
|
|
22066
22239
|
*/
|
|
22067
22240
|
render() {
|
|
22241
|
+
Logger.log('[pc-editor] render');
|
|
22068
22242
|
if (!this._isReady)
|
|
22069
22243
|
return;
|
|
22070
22244
|
this.canvasManager.render();
|
|
22071
22245
|
}
|
|
22072
22246
|
fitToPage() {
|
|
22247
|
+
Logger.log('[pc-editor] fitToPage');
|
|
22073
22248
|
if (!this._isReady)
|
|
22074
22249
|
return;
|
|
22075
22250
|
this.canvasManager.fitToPage();
|
|
22076
22251
|
}
|
|
22077
22252
|
// Layout control methods
|
|
22078
22253
|
setAutoFlow(enabled) {
|
|
22254
|
+
Logger.log('[pc-editor] setAutoFlow', enabled);
|
|
22079
22255
|
if (!this._isReady) {
|
|
22080
22256
|
throw new Error('Editor is not ready');
|
|
22081
22257
|
}
|
|
22082
22258
|
this.layoutEngine.setAutoFlow(enabled);
|
|
22083
22259
|
}
|
|
22084
22260
|
reflowDocument() {
|
|
22261
|
+
Logger.log('[pc-editor] reflowDocument');
|
|
22085
22262
|
if (!this._isReady) {
|
|
22086
22263
|
throw new Error('Editor is not ready');
|
|
22087
22264
|
}
|
|
@@ -22123,6 +22300,7 @@ class PCEditor extends EventEmitter {
|
|
|
22123
22300
|
};
|
|
22124
22301
|
}
|
|
22125
22302
|
addPage() {
|
|
22303
|
+
Logger.log('[pc-editor] addPage');
|
|
22126
22304
|
if (!this._isReady) {
|
|
22127
22305
|
throw new Error('Editor is not ready');
|
|
22128
22306
|
}
|
|
@@ -22134,6 +22312,7 @@ class PCEditor extends EventEmitter {
|
|
|
22134
22312
|
this.canvasManager.setDocument(this.document);
|
|
22135
22313
|
}
|
|
22136
22314
|
removePage(pageId) {
|
|
22315
|
+
Logger.log('[pc-editor] removePage', pageId);
|
|
22137
22316
|
if (!this._isReady) {
|
|
22138
22317
|
throw new Error('Editor is not ready');
|
|
22139
22318
|
}
|
|
@@ -22208,7 +22387,7 @@ class PCEditor extends EventEmitter {
|
|
|
22208
22387
|
}
|
|
22209
22388
|
// Use the unified focus system to get the currently focused control
|
|
22210
22389
|
const focusedControl = this.canvasManager.getFocusedControl();
|
|
22211
|
-
|
|
22390
|
+
Logger.log('[pc-editor:handleKeyDown] Key:', e.key, 'focusedControl:', focusedControl?.constructor?.name);
|
|
22212
22391
|
if (!focusedControl)
|
|
22213
22392
|
return;
|
|
22214
22393
|
// Vertical navigation needs layout context - handle specially
|
|
@@ -22232,9 +22411,9 @@ class PCEditor extends EventEmitter {
|
|
|
22232
22411
|
}
|
|
22233
22412
|
}
|
|
22234
22413
|
// Delegate to the focused control's handleKeyDown
|
|
22235
|
-
|
|
22414
|
+
Logger.log('[pc-editor:handleKeyDown] Calling focusedControl.handleKeyDown');
|
|
22236
22415
|
const handled = focusedControl.handleKeyDown(e);
|
|
22237
|
-
|
|
22416
|
+
Logger.log('[pc-editor:handleKeyDown] handled:', handled);
|
|
22238
22417
|
if (handled) {
|
|
22239
22418
|
this.canvasManager.render();
|
|
22240
22419
|
// Handle text box-specific post-processing
|
|
@@ -22598,6 +22777,7 @@ class PCEditor extends EventEmitter {
|
|
|
22598
22777
|
* This is useful when UI controls have stolen focus.
|
|
22599
22778
|
*/
|
|
22600
22779
|
applyFormattingWithFallback(start, end, formatting) {
|
|
22780
|
+
Logger.log('[pc-editor] applyFormattingWithFallback', start, end, formatting);
|
|
22601
22781
|
// Try current context first
|
|
22602
22782
|
let flowingContent = this.getEditingFlowingContent();
|
|
22603
22783
|
// Fall back to saved context
|
|
@@ -22798,6 +22978,7 @@ class PCEditor extends EventEmitter {
|
|
|
22798
22978
|
* Works for body text, text boxes, and table cells.
|
|
22799
22979
|
*/
|
|
22800
22980
|
setUnifiedAlignment(alignment) {
|
|
22981
|
+
Logger.log('[pc-editor] setUnifiedAlignment', alignment);
|
|
22801
22982
|
const flowingContent = this.getEditingFlowingContent();
|
|
22802
22983
|
if (!flowingContent) {
|
|
22803
22984
|
throw new Error('No text is being edited');
|
|
@@ -22814,6 +22995,7 @@ class PCEditor extends EventEmitter {
|
|
|
22814
22995
|
this.canvasManager.render();
|
|
22815
22996
|
}
|
|
22816
22997
|
insertText(text) {
|
|
22998
|
+
Logger.log('[pc-editor] insertText', text);
|
|
22817
22999
|
if (!this._isReady) {
|
|
22818
23000
|
throw new Error('Editor is not ready');
|
|
22819
23001
|
}
|
|
@@ -22830,6 +23012,7 @@ class PCEditor extends EventEmitter {
|
|
|
22830
23012
|
return flowingContent ? flowingContent.getText() : '';
|
|
22831
23013
|
}
|
|
22832
23014
|
setFlowingText(text) {
|
|
23015
|
+
Logger.log('[pc-editor] setFlowingText');
|
|
22833
23016
|
if (!this._isReady) {
|
|
22834
23017
|
throw new Error('Editor is not ready');
|
|
22835
23018
|
}
|
|
@@ -22843,6 +23026,7 @@ class PCEditor extends EventEmitter {
|
|
|
22843
23026
|
* Works for body, header, footer, text boxes, and table cells.
|
|
22844
23027
|
*/
|
|
22845
23028
|
setCursorPosition(position) {
|
|
23029
|
+
Logger.log('[pc-editor] setCursorPosition', position);
|
|
22846
23030
|
if (!this._isReady) {
|
|
22847
23031
|
throw new Error('Editor is not ready');
|
|
22848
23032
|
}
|
|
@@ -22888,6 +23072,7 @@ class PCEditor extends EventEmitter {
|
|
|
22888
23072
|
* Works for body, header, footer, text boxes, and table cells.
|
|
22889
23073
|
*/
|
|
22890
23074
|
insertEmbeddedObject(object, position = 'inline') {
|
|
23075
|
+
Logger.log('[pc-editor] insertEmbeddedObject', object.id, position);
|
|
22891
23076
|
if (!this._isReady) {
|
|
22892
23077
|
throw new Error('Editor is not ready');
|
|
22893
23078
|
}
|
|
@@ -22914,6 +23099,7 @@ class PCEditor extends EventEmitter {
|
|
|
22914
23099
|
* Works for body, header, footer, text boxes, and table cells.
|
|
22915
23100
|
*/
|
|
22916
23101
|
insertSubstitutionField(fieldName, config) {
|
|
23102
|
+
Logger.log('[pc-editor] insertSubstitutionField', fieldName);
|
|
22917
23103
|
if (!this._isReady) {
|
|
22918
23104
|
throw new Error('Editor is not ready');
|
|
22919
23105
|
}
|
|
@@ -22939,6 +23125,7 @@ class PCEditor extends EventEmitter {
|
|
|
22939
23125
|
* @param displayFormat Optional format string (e.g., "Page %d" where %d is replaced by page number)
|
|
22940
23126
|
*/
|
|
22941
23127
|
insertPageNumberField(displayFormat) {
|
|
23128
|
+
Logger.log('[pc-editor] insertPageNumberField');
|
|
22942
23129
|
if (!this._isReady) {
|
|
22943
23130
|
throw new Error('Editor is not ready');
|
|
22944
23131
|
}
|
|
@@ -22964,6 +23151,7 @@ class PCEditor extends EventEmitter {
|
|
|
22964
23151
|
* @param displayFormat Optional format string (e.g., "of %d" where %d is replaced by page count)
|
|
22965
23152
|
*/
|
|
22966
23153
|
insertPageCountField(displayFormat) {
|
|
23154
|
+
Logger.log('[pc-editor] insertPageCountField');
|
|
22967
23155
|
if (!this._isReady) {
|
|
22968
23156
|
throw new Error('Editor is not ready');
|
|
22969
23157
|
}
|
|
@@ -22989,6 +23177,7 @@ class PCEditor extends EventEmitter {
|
|
|
22989
23177
|
* or table cells are not recommended as these don't span pages.
|
|
22990
23178
|
*/
|
|
22991
23179
|
insertPageBreak() {
|
|
23180
|
+
Logger.log('[pc-editor] insertPageBreak');
|
|
22992
23181
|
if (!this._isReady) {
|
|
22993
23182
|
throw new Error('Editor is not ready');
|
|
22994
23183
|
}
|
|
@@ -23137,7 +23326,7 @@ class PCEditor extends EventEmitter {
|
|
|
23137
23326
|
// Find the text box in all flowing contents
|
|
23138
23327
|
const textBox = this.findTextBoxById(textBoxId);
|
|
23139
23328
|
if (!textBox) {
|
|
23140
|
-
|
|
23329
|
+
Logger.warn(`[pc-editor:updateTextBox] Text box not found: ${textBoxId}`);
|
|
23141
23330
|
return false;
|
|
23142
23331
|
}
|
|
23143
23332
|
// Apply updates
|
|
@@ -23200,7 +23389,7 @@ class PCEditor extends EventEmitter {
|
|
|
23200
23389
|
// Find the image in all flowing contents
|
|
23201
23390
|
const image = this.findImageById(imageId);
|
|
23202
23391
|
if (!image) {
|
|
23203
|
-
|
|
23392
|
+
Logger.warn(`[pc-editor:updateImage] Image not found: ${imageId}`);
|
|
23204
23393
|
return false;
|
|
23205
23394
|
}
|
|
23206
23395
|
// Apply updates
|
|
@@ -23234,7 +23423,7 @@ class PCEditor extends EventEmitter {
|
|
|
23234
23423
|
return false;
|
|
23235
23424
|
const image = this.findImageById(imageId);
|
|
23236
23425
|
if (!image) {
|
|
23237
|
-
|
|
23426
|
+
Logger.warn(`[pc-editor:setImageSource] Image not found: ${imageId}`);
|
|
23238
23427
|
return false;
|
|
23239
23428
|
}
|
|
23240
23429
|
image.setSource(dataUrl, options);
|
|
@@ -23314,6 +23503,39 @@ class PCEditor extends EventEmitter {
|
|
|
23314
23503
|
table.removeColumn(colIndex);
|
|
23315
23504
|
this.canvasManager.render();
|
|
23316
23505
|
}
|
|
23506
|
+
/**
|
|
23507
|
+
* Merge selected cells in a table.
|
|
23508
|
+
* Uses the table's current cell selection range.
|
|
23509
|
+
* @param table The table containing the cells to merge
|
|
23510
|
+
* @returns true if cells were merged successfully
|
|
23511
|
+
*/
|
|
23512
|
+
tableMergeCells(table) {
|
|
23513
|
+
Logger.log('[pc-editor] tableMergeCells');
|
|
23514
|
+
if (!this._isReady)
|
|
23515
|
+
return false;
|
|
23516
|
+
const result = table.mergeCells();
|
|
23517
|
+
if (result.success) {
|
|
23518
|
+
this.canvasManager.render();
|
|
23519
|
+
}
|
|
23520
|
+
return result.success;
|
|
23521
|
+
}
|
|
23522
|
+
/**
|
|
23523
|
+
* Split a merged cell back into individual cells.
|
|
23524
|
+
* @param table The table containing the merged cell
|
|
23525
|
+
* @param row Row index of the merged cell
|
|
23526
|
+
* @param col Column index of the merged cell
|
|
23527
|
+
* @returns true if the cell was split successfully
|
|
23528
|
+
*/
|
|
23529
|
+
tableSplitCell(table, row, col) {
|
|
23530
|
+
Logger.log('[pc-editor] tableSplitCell', row, col);
|
|
23531
|
+
if (!this._isReady)
|
|
23532
|
+
return false;
|
|
23533
|
+
const result = table.splitCell(row, col);
|
|
23534
|
+
if (result.success) {
|
|
23535
|
+
this.canvasManager.render();
|
|
23536
|
+
}
|
|
23537
|
+
return result.success;
|
|
23538
|
+
}
|
|
23317
23539
|
/**
|
|
23318
23540
|
* Begin a compound operation. Groups multiple mutations into a single undo entry.
|
|
23319
23541
|
* Call endCompoundOperation after making changes.
|
|
@@ -23361,7 +23583,7 @@ class PCEditor extends EventEmitter {
|
|
|
23361
23583
|
* @deprecated Use insertEmbeddedObject instead
|
|
23362
23584
|
*/
|
|
23363
23585
|
insertInlineElement(_elementData, _position = 'inline') {
|
|
23364
|
-
|
|
23586
|
+
Logger.warn('[pc-editor] insertInlineElement is deprecated and no longer functional. Use insertEmbeddedObject instead.');
|
|
23365
23587
|
}
|
|
23366
23588
|
/**
|
|
23367
23589
|
* Apply merge data to substitute all substitution fields with their values.
|
|
@@ -24086,18 +24308,37 @@ class PCEditor extends EventEmitter {
|
|
|
24086
24308
|
return this.document.bodyFlowingContent.getParagraphBoundaries();
|
|
24087
24309
|
}
|
|
24088
24310
|
/**
|
|
24089
|
-
* Create a repeating section
|
|
24090
|
-
*
|
|
24091
|
-
*
|
|
24092
|
-
*
|
|
24311
|
+
* Create a repeating section.
|
|
24312
|
+
*
|
|
24313
|
+
* If a table is currently being edited (focused), creates a table row loop
|
|
24314
|
+
* based on the focused cell's row. In this case, startIndex and endIndex
|
|
24315
|
+
* are ignored — the focused cell's row determines the loop range.
|
|
24316
|
+
*
|
|
24317
|
+
* Otherwise, creates a body text repeating section at the given paragraph boundaries.
|
|
24318
|
+
*
|
|
24319
|
+
* @param startIndex Text index at paragraph start (ignored for table row loops)
|
|
24320
|
+
* @param endIndex Text index at closing paragraph start (ignored for table row loops)
|
|
24093
24321
|
* @param fieldPath The field path to the array to loop over (e.g., "items")
|
|
24094
|
-
* @returns The created section, or null if
|
|
24322
|
+
* @returns The created section/loop, or null if creation failed
|
|
24095
24323
|
*/
|
|
24096
24324
|
createRepeatingSection(startIndex, endIndex, fieldPath) {
|
|
24097
24325
|
if (!this._isReady) {
|
|
24098
24326
|
throw new Error('Editor is not ready');
|
|
24099
24327
|
}
|
|
24328
|
+
// If a table is focused, create a row loop instead of a text repeating section
|
|
24329
|
+
const focusedTable = this.getFocusedTable();
|
|
24330
|
+
if (focusedTable && focusedTable.focusedCell) {
|
|
24331
|
+
Logger.log('[pc-editor] createRepeatingSection → table row loop', fieldPath);
|
|
24332
|
+
const row = focusedTable.focusedCell.row;
|
|
24333
|
+
const loop = focusedTable.createRowLoop(row, row, fieldPath);
|
|
24334
|
+
if (loop) {
|
|
24335
|
+
this.canvasManager.render();
|
|
24336
|
+
this.emit('table-row-loop-added', { table: focusedTable, loop });
|
|
24337
|
+
}
|
|
24338
|
+
return null; // Row loops are not RepeatingSections, return null
|
|
24339
|
+
}
|
|
24100
24340
|
// Repeating sections only work in body (document-level)
|
|
24341
|
+
Logger.log('[pc-editor] createRepeatingSection', startIndex, endIndex, fieldPath);
|
|
24101
24342
|
const section = this.document.bodyFlowingContent.createRepeatingSection(startIndex, endIndex, fieldPath);
|
|
24102
24343
|
if (section) {
|
|
24103
24344
|
this.canvasManager.render();
|
|
@@ -24475,7 +24716,7 @@ class PCEditor extends EventEmitter {
|
|
|
24475
24716
|
createEmbeddedObjectFromData(data) {
|
|
24476
24717
|
const object = EmbeddedObjectFactory.tryCreate(data);
|
|
24477
24718
|
if (!object) {
|
|
24478
|
-
|
|
24719
|
+
Logger.warn('[pc-editor] Unknown object type:', data.objectType);
|
|
24479
24720
|
}
|
|
24480
24721
|
return object;
|
|
24481
24722
|
}
|
|
@@ -24504,6 +24745,13 @@ class PCEditor extends EventEmitter {
|
|
|
24504
24745
|
return false;
|
|
24505
24746
|
}
|
|
24506
24747
|
}
|
|
24748
|
+
/**
|
|
24749
|
+
* Enable or disable verbose logging.
|
|
24750
|
+
* When disabled (default), only errors are logged to the console.
|
|
24751
|
+
*/
|
|
24752
|
+
setLogging(enabled) {
|
|
24753
|
+
Logger.setEnabled(enabled);
|
|
24754
|
+
}
|
|
24507
24755
|
destroy() {
|
|
24508
24756
|
this.disableTextInput();
|
|
24509
24757
|
if (this.canvasManager) {
|
|
@@ -25646,33 +25894,38 @@ class DocumentInfoPane extends BasePane {
|
|
|
25646
25894
|
}
|
|
25647
25895
|
createContent() {
|
|
25648
25896
|
const container = document.createElement('div');
|
|
25649
|
-
container.className = 'pc-pane-
|
|
25897
|
+
container.className = 'pc-pane-label-value-grid';
|
|
25650
25898
|
// Page count
|
|
25651
|
-
|
|
25652
|
-
this.pageCountEl =
|
|
25653
|
-
container.appendChild(
|
|
25899
|
+
container.appendChild(this.createLabel('Pages:'));
|
|
25900
|
+
this.pageCountEl = this.createValue('0');
|
|
25901
|
+
container.appendChild(this.pageCountEl);
|
|
25902
|
+
container.appendChild(this.createSpacer());
|
|
25654
25903
|
// Page size
|
|
25655
|
-
|
|
25656
|
-
this.pageSizeEl =
|
|
25657
|
-
container.appendChild(
|
|
25904
|
+
container.appendChild(this.createLabel('Size:'));
|
|
25905
|
+
this.pageSizeEl = this.createValue('-');
|
|
25906
|
+
container.appendChild(this.pageSizeEl);
|
|
25907
|
+
container.appendChild(this.createSpacer());
|
|
25658
25908
|
// Page orientation
|
|
25659
|
-
|
|
25660
|
-
this.pageOrientationEl =
|
|
25661
|
-
container.appendChild(
|
|
25909
|
+
container.appendChild(this.createLabel('Orientation:'));
|
|
25910
|
+
this.pageOrientationEl = this.createValue('-');
|
|
25911
|
+
container.appendChild(this.pageOrientationEl);
|
|
25912
|
+
container.appendChild(this.createSpacer());
|
|
25662
25913
|
return container;
|
|
25663
25914
|
}
|
|
25664
|
-
|
|
25665
|
-
const
|
|
25666
|
-
|
|
25667
|
-
|
|
25668
|
-
|
|
25669
|
-
|
|
25670
|
-
|
|
25671
|
-
|
|
25672
|
-
|
|
25673
|
-
|
|
25674
|
-
|
|
25675
|
-
|
|
25915
|
+
createLabel(text) {
|
|
25916
|
+
const label = document.createElement('span');
|
|
25917
|
+
label.className = 'pc-pane-label pc-pane-margin-label';
|
|
25918
|
+
label.textContent = text;
|
|
25919
|
+
return label;
|
|
25920
|
+
}
|
|
25921
|
+
createValue(text) {
|
|
25922
|
+
const value = document.createElement('span');
|
|
25923
|
+
value.className = 'pc-pane-info-value';
|
|
25924
|
+
value.textContent = text;
|
|
25925
|
+
return value;
|
|
25926
|
+
}
|
|
25927
|
+
createSpacer() {
|
|
25928
|
+
return document.createElement('div');
|
|
25676
25929
|
}
|
|
25677
25930
|
/**
|
|
25678
25931
|
* Update the displayed information from the editor.
|
|
@@ -25852,24 +26105,48 @@ class DocumentSettingsPane extends BasePane {
|
|
|
25852
26105
|
const container = document.createElement('div');
|
|
25853
26106
|
// Margins section
|
|
25854
26107
|
const marginsSection = this.createSection('Margins (mm)');
|
|
26108
|
+
// Five-column grid: label, edit, label, edit, stretch
|
|
25855
26109
|
const marginsGrid = document.createElement('div');
|
|
25856
|
-
marginsGrid.className = 'pc-pane-margins-grid';
|
|
26110
|
+
marginsGrid.className = 'pc-pane-margins-grid-5col';
|
|
25857
26111
|
this.marginTopInput = this.createNumberInput({ min: 5, max: 50, step: 0.5, value: 20 });
|
|
25858
26112
|
this.marginRightInput = this.createNumberInput({ min: 5, max: 50, step: 0.5, value: 20 });
|
|
25859
26113
|
this.marginBottomInput = this.createNumberInput({ min: 5, max: 50, step: 0.5, value: 20 });
|
|
25860
26114
|
this.marginLeftInput = this.createNumberInput({ min: 5, max: 50, step: 0.5, value: 20 });
|
|
25861
|
-
|
|
25862
|
-
|
|
25863
|
-
|
|
25864
|
-
|
|
26115
|
+
// Apply margins on blur
|
|
26116
|
+
const applyMargins = () => this.applyMargins();
|
|
26117
|
+
this.marginTopInput.addEventListener('blur', applyMargins);
|
|
26118
|
+
this.marginRightInput.addEventListener('blur', applyMargins);
|
|
26119
|
+
this.marginBottomInput.addEventListener('blur', applyMargins);
|
|
26120
|
+
this.marginLeftInput.addEventListener('blur', applyMargins);
|
|
26121
|
+
this.eventCleanup.push(() => {
|
|
26122
|
+
this.marginTopInput?.removeEventListener('blur', applyMargins);
|
|
26123
|
+
this.marginRightInput?.removeEventListener('blur', applyMargins);
|
|
26124
|
+
this.marginBottomInput?.removeEventListener('blur', applyMargins);
|
|
26125
|
+
this.marginLeftInput?.removeEventListener('blur', applyMargins);
|
|
26126
|
+
});
|
|
26127
|
+
// Row 1: Top / Right
|
|
26128
|
+
const topLabel = this.createMarginLabel('Top:');
|
|
26129
|
+
const rightLabel = this.createMarginLabel('Right:');
|
|
26130
|
+
marginsGrid.appendChild(topLabel);
|
|
26131
|
+
marginsGrid.appendChild(this.marginTopInput);
|
|
26132
|
+
marginsGrid.appendChild(rightLabel);
|
|
26133
|
+
marginsGrid.appendChild(this.marginRightInput);
|
|
26134
|
+
marginsGrid.appendChild(this.createSpacer());
|
|
26135
|
+
// Row 2: Bottom / Left
|
|
26136
|
+
const bottomLabel = this.createMarginLabel('Bottom:');
|
|
26137
|
+
const leftLabel = this.createMarginLabel('Left:');
|
|
26138
|
+
marginsGrid.appendChild(bottomLabel);
|
|
26139
|
+
marginsGrid.appendChild(this.marginBottomInput);
|
|
26140
|
+
marginsGrid.appendChild(leftLabel);
|
|
26141
|
+
marginsGrid.appendChild(this.marginLeftInput);
|
|
26142
|
+
marginsGrid.appendChild(this.createSpacer());
|
|
25865
26143
|
marginsSection.appendChild(marginsGrid);
|
|
25866
|
-
// Apply margins button
|
|
25867
|
-
const applyMarginsBtn = this.createButton('Apply Margins');
|
|
25868
|
-
this.addButtonListener(applyMarginsBtn, () => this.applyMargins());
|
|
25869
|
-
marginsSection.appendChild(applyMarginsBtn);
|
|
25870
26144
|
container.appendChild(marginsSection);
|
|
25871
|
-
// Page
|
|
25872
|
-
const
|
|
26145
|
+
// Page settings section using label-value grid: label, value, stretch
|
|
26146
|
+
const pageSection = this.createSection();
|
|
26147
|
+
const pageGrid = document.createElement('div');
|
|
26148
|
+
pageGrid.className = 'pc-pane-label-value-grid';
|
|
26149
|
+
// Page Size
|
|
25873
26150
|
this.pageSizeSelect = this.createSelect([
|
|
25874
26151
|
{ value: 'A4', label: 'A4' },
|
|
25875
26152
|
{ value: 'Letter', label: 'Letter' },
|
|
@@ -25877,19 +26154,32 @@ class DocumentSettingsPane extends BasePane {
|
|
|
25877
26154
|
{ value: 'A3', label: 'A3' }
|
|
25878
26155
|
], 'A4');
|
|
25879
26156
|
this.addImmediateApplyListener(this.pageSizeSelect, () => this.applyPageSettings());
|
|
25880
|
-
|
|
25881
|
-
|
|
25882
|
-
|
|
25883
|
-
|
|
26157
|
+
pageGrid.appendChild(this.createMarginLabel('Page Size:'));
|
|
26158
|
+
pageGrid.appendChild(this.pageSizeSelect);
|
|
26159
|
+
pageGrid.appendChild(this.createSpacer());
|
|
26160
|
+
// Orientation
|
|
25884
26161
|
this.orientationSelect = this.createSelect([
|
|
25885
26162
|
{ value: 'portrait', label: 'Portrait' },
|
|
25886
26163
|
{ value: 'landscape', label: 'Landscape' }
|
|
25887
26164
|
], 'portrait');
|
|
25888
26165
|
this.addImmediateApplyListener(this.orientationSelect, () => this.applyPageSettings());
|
|
25889
|
-
|
|
25890
|
-
|
|
26166
|
+
pageGrid.appendChild(this.createMarginLabel('Orientation:'));
|
|
26167
|
+
pageGrid.appendChild(this.orientationSelect);
|
|
26168
|
+
pageGrid.appendChild(this.createSpacer());
|
|
26169
|
+
pageSection.appendChild(pageGrid);
|
|
26170
|
+
container.appendChild(pageSection);
|
|
25891
26171
|
return container;
|
|
25892
26172
|
}
|
|
26173
|
+
createMarginLabel(text) {
|
|
26174
|
+
const label = document.createElement('label');
|
|
26175
|
+
label.className = 'pc-pane-label pc-pane-margin-label';
|
|
26176
|
+
label.textContent = text;
|
|
26177
|
+
return label;
|
|
26178
|
+
}
|
|
26179
|
+
createSpacer() {
|
|
26180
|
+
const spacer = document.createElement('div');
|
|
26181
|
+
return spacer;
|
|
26182
|
+
}
|
|
25893
26183
|
loadSettings() {
|
|
25894
26184
|
if (!this.editor)
|
|
25895
26185
|
return;
|
|
@@ -26288,9 +26578,15 @@ class FormattingPane extends BasePane {
|
|
|
26288
26578
|
this.bulletListBtn?.classList.toggle('pc-pane-button--active', listFormatting.listType === 'bullet');
|
|
26289
26579
|
this.numberedListBtn?.classList.toggle('pc-pane-button--active', listFormatting.listType === 'number');
|
|
26290
26580
|
}
|
|
26581
|
+
else {
|
|
26582
|
+
this.bulletListBtn?.classList.remove('pc-pane-button--active');
|
|
26583
|
+
this.numberedListBtn?.classList.remove('pc-pane-button--active');
|
|
26584
|
+
}
|
|
26291
26585
|
}
|
|
26292
26586
|
catch {
|
|
26293
26587
|
// No text editing active
|
|
26588
|
+
this.bulletListBtn?.classList.remove('pc-pane-button--active');
|
|
26589
|
+
this.numberedListBtn?.classList.remove('pc-pane-button--active');
|
|
26294
26590
|
}
|
|
26295
26591
|
}
|
|
26296
26592
|
getSelection() {
|
|
@@ -26429,6 +26725,7 @@ class HyperlinkPane extends BasePane {
|
|
|
26429
26725
|
this.titleInput = null;
|
|
26430
26726
|
this.rangeHint = null;
|
|
26431
26727
|
this.currentHyperlink = null;
|
|
26728
|
+
this._isUpdating = false;
|
|
26432
26729
|
this.onApply = options.onApply;
|
|
26433
26730
|
this.onRemove = options.onRemove;
|
|
26434
26731
|
}
|
|
@@ -26470,15 +26767,21 @@ class HyperlinkPane extends BasePane {
|
|
|
26470
26767
|
return container;
|
|
26471
26768
|
}
|
|
26472
26769
|
updateFromCursor() {
|
|
26473
|
-
if (!this.editor)
|
|
26770
|
+
if (!this.editor || this._isUpdating)
|
|
26474
26771
|
return;
|
|
26475
|
-
|
|
26476
|
-
|
|
26477
|
-
|
|
26478
|
-
this.
|
|
26772
|
+
this._isUpdating = true;
|
|
26773
|
+
try {
|
|
26774
|
+
const cursorPos = this.editor.getCursorPosition();
|
|
26775
|
+
const hyperlink = this.editor.getHyperlinkAt(cursorPos);
|
|
26776
|
+
if (hyperlink) {
|
|
26777
|
+
this.showHyperlink(hyperlink);
|
|
26778
|
+
}
|
|
26779
|
+
else {
|
|
26780
|
+
this.hideHyperlink();
|
|
26781
|
+
}
|
|
26479
26782
|
}
|
|
26480
|
-
|
|
26481
|
-
this.
|
|
26783
|
+
finally {
|
|
26784
|
+
this._isUpdating = false;
|
|
26482
26785
|
}
|
|
26483
26786
|
}
|
|
26484
26787
|
showHyperlink(hyperlink) {
|
|
@@ -27097,6 +27400,7 @@ class TextBoxPane extends BasePane {
|
|
|
27097
27400
|
this.borderColorInput = null;
|
|
27098
27401
|
this.borderStyleSelect = null;
|
|
27099
27402
|
this.paddingInput = null;
|
|
27403
|
+
this._isUpdating = false;
|
|
27100
27404
|
this.currentTextBox = null;
|
|
27101
27405
|
this.onApplyCallback = options.onApply;
|
|
27102
27406
|
}
|
|
@@ -27170,14 +27474,20 @@ class TextBoxPane extends BasePane {
|
|
|
27170
27474
|
return container;
|
|
27171
27475
|
}
|
|
27172
27476
|
updateFromSelection() {
|
|
27173
|
-
if (!this.editor)
|
|
27477
|
+
if (!this.editor || this._isUpdating)
|
|
27174
27478
|
return;
|
|
27175
|
-
|
|
27176
|
-
|
|
27177
|
-
this.
|
|
27479
|
+
this._isUpdating = true;
|
|
27480
|
+
try {
|
|
27481
|
+
const textBox = this.editor.getSelectedTextBox?.();
|
|
27482
|
+
if (textBox && !textBox.editing) {
|
|
27483
|
+
this.showTextBox(textBox);
|
|
27484
|
+
}
|
|
27485
|
+
else {
|
|
27486
|
+
this.hideTextBox();
|
|
27487
|
+
}
|
|
27178
27488
|
}
|
|
27179
|
-
|
|
27180
|
-
this.
|
|
27489
|
+
finally {
|
|
27490
|
+
this._isUpdating = false;
|
|
27181
27491
|
}
|
|
27182
27492
|
}
|
|
27183
27493
|
/**
|
|
@@ -27331,6 +27641,7 @@ class ImagePane extends BasePane {
|
|
|
27331
27641
|
this.altTextInput = null;
|
|
27332
27642
|
this.fileInput = null;
|
|
27333
27643
|
this.currentImage = null;
|
|
27644
|
+
this._isUpdating = false;
|
|
27334
27645
|
this.maxImageWidth = options.maxImageWidth ?? 400;
|
|
27335
27646
|
this.maxImageHeight = options.maxImageHeight ?? 400;
|
|
27336
27647
|
this.onApplyCallback = options.onApply;
|
|
@@ -27412,14 +27723,20 @@ class ImagePane extends BasePane {
|
|
|
27412
27723
|
return container;
|
|
27413
27724
|
}
|
|
27414
27725
|
updateFromSelection() {
|
|
27415
|
-
if (!this.editor)
|
|
27726
|
+
if (!this.editor || this._isUpdating)
|
|
27416
27727
|
return;
|
|
27417
|
-
|
|
27418
|
-
|
|
27419
|
-
this.
|
|
27728
|
+
this._isUpdating = true;
|
|
27729
|
+
try {
|
|
27730
|
+
const image = this.editor.getSelectedImage?.();
|
|
27731
|
+
if (image) {
|
|
27732
|
+
this.showImage(image);
|
|
27733
|
+
}
|
|
27734
|
+
else {
|
|
27735
|
+
this.hideImage();
|
|
27736
|
+
}
|
|
27420
27737
|
}
|
|
27421
|
-
|
|
27422
|
-
this.
|
|
27738
|
+
finally {
|
|
27739
|
+
this._isUpdating = false;
|
|
27423
27740
|
}
|
|
27424
27741
|
}
|
|
27425
27742
|
/**
|
|
@@ -27584,6 +27901,9 @@ class TablePane extends BasePane {
|
|
|
27584
27901
|
// Default controls
|
|
27585
27902
|
this.defaultPaddingInput = null;
|
|
27586
27903
|
this.defaultBorderColorInput = null;
|
|
27904
|
+
// Merge/split buttons
|
|
27905
|
+
this.mergeCellsBtn = null;
|
|
27906
|
+
this.splitCellBtn = null;
|
|
27587
27907
|
// Cell formatting controls
|
|
27588
27908
|
this.cellBgColorInput = null;
|
|
27589
27909
|
this.borderTopCheck = null;
|
|
@@ -27594,6 +27914,7 @@ class TablePane extends BasePane {
|
|
|
27594
27914
|
this.borderColorInput = null;
|
|
27595
27915
|
this.borderStyleSelect = null;
|
|
27596
27916
|
this.currentTable = null;
|
|
27917
|
+
this._isUpdating = false;
|
|
27597
27918
|
this.onApplyCallback = options.onApply;
|
|
27598
27919
|
}
|
|
27599
27920
|
attach(options) {
|
|
@@ -27602,12 +27923,12 @@ class TablePane extends BasePane {
|
|
|
27602
27923
|
// Listen for selection/focus changes
|
|
27603
27924
|
const updateHandler = () => this.updateFromFocusedTable();
|
|
27604
27925
|
this.editor.on('selection-change', updateHandler);
|
|
27605
|
-
this.editor.on('
|
|
27606
|
-
this.editor.on('table-cell-selection', updateHandler);
|
|
27926
|
+
this.editor.on('tablecell-cursor-changed', updateHandler);
|
|
27927
|
+
this.editor.on('table-cell-selection-changed', updateHandler);
|
|
27607
27928
|
this.eventCleanup.push(() => {
|
|
27608
27929
|
this.editor?.off('selection-change', updateHandler);
|
|
27609
|
-
this.editor?.off('
|
|
27610
|
-
this.editor?.off('table-cell-selection', updateHandler);
|
|
27930
|
+
this.editor?.off('tablecell-cursor-changed', updateHandler);
|
|
27931
|
+
this.editor?.off('table-cell-selection-changed', updateHandler);
|
|
27611
27932
|
});
|
|
27612
27933
|
// Initial update
|
|
27613
27934
|
this.updateFromFocusedTable();
|
|
@@ -27676,6 +27997,17 @@ class TablePane extends BasePane {
|
|
|
27676
27997
|
const cellSection = this.createSection('Cell Formatting');
|
|
27677
27998
|
this.cellSelectionDisplay = this.createHint('No cell selected');
|
|
27678
27999
|
cellSection.appendChild(this.cellSelectionDisplay);
|
|
28000
|
+
// Merge/Split buttons
|
|
28001
|
+
const mergeBtnGroup = this.createButtonGroup();
|
|
28002
|
+
this.mergeCellsBtn = this.createButton('Merge Cells');
|
|
28003
|
+
this.mergeCellsBtn.disabled = true;
|
|
28004
|
+
this.splitCellBtn = this.createButton('Split Cell');
|
|
28005
|
+
this.splitCellBtn.disabled = true;
|
|
28006
|
+
this.addButtonListener(this.mergeCellsBtn, () => this.doMergeCells());
|
|
28007
|
+
this.addButtonListener(this.splitCellBtn, () => this.doSplitCell());
|
|
28008
|
+
mergeBtnGroup.appendChild(this.mergeCellsBtn);
|
|
28009
|
+
mergeBtnGroup.appendChild(this.splitCellBtn);
|
|
28010
|
+
cellSection.appendChild(mergeBtnGroup);
|
|
27679
28011
|
// Background
|
|
27680
28012
|
this.cellBgColorInput = this.createColorInput('#ffffff');
|
|
27681
28013
|
cellSection.appendChild(this.createFormGroup('Background', this.cellBgColorInput));
|
|
@@ -27732,14 +28064,20 @@ class TablePane extends BasePane {
|
|
|
27732
28064
|
return container;
|
|
27733
28065
|
}
|
|
27734
28066
|
updateFromFocusedTable() {
|
|
27735
|
-
if (!this.editor)
|
|
28067
|
+
if (!this.editor || this._isUpdating)
|
|
27736
28068
|
return;
|
|
27737
|
-
|
|
27738
|
-
|
|
27739
|
-
this.
|
|
28069
|
+
this._isUpdating = true;
|
|
28070
|
+
try {
|
|
28071
|
+
const table = this.editor.getFocusedTable();
|
|
28072
|
+
if (table) {
|
|
28073
|
+
this.showTable(table);
|
|
28074
|
+
}
|
|
28075
|
+
else {
|
|
28076
|
+
this.hideTable();
|
|
28077
|
+
}
|
|
27740
28078
|
}
|
|
27741
|
-
|
|
27742
|
-
this.
|
|
28079
|
+
finally {
|
|
28080
|
+
this._isUpdating = false;
|
|
27743
28081
|
}
|
|
27744
28082
|
}
|
|
27745
28083
|
/**
|
|
@@ -27786,6 +28124,15 @@ class TablePane extends BasePane {
|
|
|
27786
28124
|
return;
|
|
27787
28125
|
const focusedCell = table.focusedCell;
|
|
27788
28126
|
const selectedRange = table.selectedRange;
|
|
28127
|
+
// Update merge/split button states
|
|
28128
|
+
if (this.mergeCellsBtn) {
|
|
28129
|
+
const canMerge = selectedRange ? table.canMergeRange(selectedRange).canMerge : false;
|
|
28130
|
+
this.mergeCellsBtn.disabled = !canMerge;
|
|
28131
|
+
}
|
|
28132
|
+
if (this.splitCellBtn) {
|
|
28133
|
+
const canSplit = focusedCell ? table.canSplitCell(focusedCell.row, focusedCell.col).canSplit : false;
|
|
28134
|
+
this.splitCellBtn.disabled = !canSplit;
|
|
28135
|
+
}
|
|
27789
28136
|
if (selectedRange) {
|
|
27790
28137
|
const count = (selectedRange.end.row - selectedRange.start.row + 1) *
|
|
27791
28138
|
(selectedRange.end.col - selectedRange.start.col + 1);
|
|
@@ -27943,6 +28290,21 @@ class TablePane extends BasePane {
|
|
|
27943
28290
|
hasTable() {
|
|
27944
28291
|
return this.currentTable !== null;
|
|
27945
28292
|
}
|
|
28293
|
+
doMergeCells() {
|
|
28294
|
+
if (!this.editor || !this.currentTable)
|
|
28295
|
+
return;
|
|
28296
|
+
this.editor.tableMergeCells(this.currentTable);
|
|
28297
|
+
this.updateFromFocusedTable();
|
|
28298
|
+
}
|
|
28299
|
+
doSplitCell() {
|
|
28300
|
+
if (!this.editor || !this.currentTable)
|
|
28301
|
+
return;
|
|
28302
|
+
const focused = this.currentTable.focusedCell;
|
|
28303
|
+
if (!focused)
|
|
28304
|
+
return;
|
|
28305
|
+
this.editor.tableSplitCell(this.currentTable, focused.row, focused.col);
|
|
28306
|
+
this.updateFromFocusedTable();
|
|
28307
|
+
}
|
|
27946
28308
|
/**
|
|
27947
28309
|
* Update the pane from current editor state.
|
|
27948
28310
|
*/
|
|
@@ -27975,6 +28337,7 @@ exports.HtmlConverter = HtmlConverter;
|
|
|
27975
28337
|
exports.HyperlinkPane = HyperlinkPane;
|
|
27976
28338
|
exports.ImageObject = ImageObject;
|
|
27977
28339
|
exports.ImagePane = ImagePane;
|
|
28340
|
+
exports.Logger = Logger;
|
|
27978
28341
|
exports.MergeDataPane = MergeDataPane;
|
|
27979
28342
|
exports.PCEditor = PCEditor;
|
|
27980
28343
|
exports.PDFImportError = PDFImportError;
|