pptx-angular-viewer 1.14.1 → 1.16.0
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.
|
@@ -20766,6 +20766,51 @@ function defaultTextAdvancedState() {
|
|
|
20766
20766
|
rtl: false,
|
|
20767
20767
|
};
|
|
20768
20768
|
}
|
|
20769
|
+
// ==========================================================================
|
|
20770
|
+
// Text wrap + autofit mode (standalone; not part of TextAdvancedState so
|
|
20771
|
+
// existing consumers of that interface are unaffected by this addition).
|
|
20772
|
+
// ==========================================================================
|
|
20773
|
+
/** Available text-wrap options (`a:bodyPr/@wrap`). */
|
|
20774
|
+
const TEXT_WRAP_OPTIONS = [
|
|
20775
|
+
['square', 'Wrap text in shape'],
|
|
20776
|
+
['none', "Don't wrap text"],
|
|
20777
|
+
];
|
|
20778
|
+
/**
|
|
20779
|
+
* Available autofit-mode options. Per this codebase's {@link TextStyle.autoFitMode}
|
|
20780
|
+
* mapping: `'shrink'` is `a:spAutoFit` (resize the shape to fit the text) and
|
|
20781
|
+
* `'normal'` is `a:normAutofit` (shrink the text on overflow).
|
|
20782
|
+
*/
|
|
20783
|
+
const AUTOFIT_MODE_OPTIONS = [
|
|
20784
|
+
['none', 'Do not autofit'],
|
|
20785
|
+
['normal', 'Shrink text on overflow'],
|
|
20786
|
+
['shrink', 'Resize shape to fit text'],
|
|
20787
|
+
];
|
|
20788
|
+
/** Read the effective text-wrap mode, defaulting to `'square'` (wrap on). */
|
|
20789
|
+
function textWrapOf(el) {
|
|
20790
|
+
if (!hasTextProperties(el)) {
|
|
20791
|
+
return 'square';
|
|
20792
|
+
}
|
|
20793
|
+
return el.textStyle?.textWrap ?? 'square';
|
|
20794
|
+
}
|
|
20795
|
+
/** Read the effective autofit mode, defaulting to `'none'`. */
|
|
20796
|
+
function autoFitModeOf(el) {
|
|
20797
|
+
if (!hasTextProperties(el)) {
|
|
20798
|
+
return 'none';
|
|
20799
|
+
}
|
|
20800
|
+
return el.textStyle?.autoFitMode ?? 'none';
|
|
20801
|
+
}
|
|
20802
|
+
/** Patch to update the text-wrap mode, preserving the rest of `textStyle`. */
|
|
20803
|
+
function textWrapPatch(el, wrap) {
|
|
20804
|
+
const base = hasTextProperties(el) ? (el.textStyle ?? {}) : {};
|
|
20805
|
+
return { textStyle: { ...base, textWrap: wrap } };
|
|
20806
|
+
}
|
|
20807
|
+
/** Patch to update the autofit mode, preserving the rest of `textStyle`. */
|
|
20808
|
+
function autoFitModePatch(el, mode) {
|
|
20809
|
+
const base = hasTextProperties(el) ? (el.textStyle ?? {}) : {};
|
|
20810
|
+
return {
|
|
20811
|
+
textStyle: { ...base, autoFitMode: mode, autoFit: mode !== 'none' },
|
|
20812
|
+
};
|
|
20813
|
+
}
|
|
20769
20814
|
|
|
20770
20815
|
/**
|
|
20771
20816
|
* PPTX document-theme font + colour resolution helpers.
|
|
@@ -22822,7 +22867,7 @@ function assignUserColor(seed, palette = CURSOR_PALETTE) {
|
|
|
22822
22867
|
* truncating, the result is exactly `maxChars` characters including a trailing
|
|
22823
22868
|
* `...` ellipsis.
|
|
22824
22869
|
*/
|
|
22825
|
-
function formatCursorLabel
|
|
22870
|
+
function formatCursorLabel(userName, maxChars = MAX_LABEL_CHARS) {
|
|
22826
22871
|
if (userName.length <= maxChars) {
|
|
22827
22872
|
return userName;
|
|
22828
22873
|
}
|
|
@@ -22953,6 +22998,175 @@ function asSelectionIds(value) {
|
|
|
22953
22998
|
return value.filter((entry) => typeof entry === 'string');
|
|
22954
22999
|
}
|
|
22955
23000
|
|
|
23001
|
+
/**
|
|
23002
|
+
* Create a throttled presence publisher bound to an awareness instance. The
|
|
23003
|
+
* initial presence is announced synchronously so peers learn of us at once.
|
|
23004
|
+
*/
|
|
23005
|
+
function createPresencePublisher(awareness, identity) {
|
|
23006
|
+
const local = { activeSlideIndex: 0, cursorX: 0, cursorY: 0 };
|
|
23007
|
+
// Starts at 0 so the first real update publishes immediately (the initial
|
|
23008
|
+
// announce below does not consume the throttle budget).
|
|
23009
|
+
let lastBroadcast = 0;
|
|
23010
|
+
let pending = null;
|
|
23011
|
+
function publish() {
|
|
23012
|
+
awareness.setLocalStateField('presence', {
|
|
23013
|
+
userName: identity.userName,
|
|
23014
|
+
userColor: identity.userColor,
|
|
23015
|
+
userAvatar: identity.userAvatar,
|
|
23016
|
+
role: identity.role,
|
|
23017
|
+
activeSlideIndex: local.activeSlideIndex,
|
|
23018
|
+
cursorX: local.cursorX,
|
|
23019
|
+
cursorY: local.cursorY,
|
|
23020
|
+
selectedElementId: local.selectedElementId,
|
|
23021
|
+
lastUpdated: new Date().toISOString(),
|
|
23022
|
+
});
|
|
23023
|
+
}
|
|
23024
|
+
function update(patch) {
|
|
23025
|
+
Object.assign(local, patch);
|
|
23026
|
+
const elapsed = Date.now() - lastBroadcast;
|
|
23027
|
+
if (elapsed >= BROADCAST_THROTTLE_MS) {
|
|
23028
|
+
if (pending !== null) {
|
|
23029
|
+
clearTimeout(pending);
|
|
23030
|
+
pending = null;
|
|
23031
|
+
}
|
|
23032
|
+
lastBroadcast = Date.now();
|
|
23033
|
+
publish();
|
|
23034
|
+
}
|
|
23035
|
+
else if (pending === null) {
|
|
23036
|
+
pending = setTimeout(() => {
|
|
23037
|
+
pending = null;
|
|
23038
|
+
lastBroadcast = Date.now();
|
|
23039
|
+
publish();
|
|
23040
|
+
}, BROADCAST_THROTTLE_MS - elapsed);
|
|
23041
|
+
}
|
|
23042
|
+
}
|
|
23043
|
+
function flush() {
|
|
23044
|
+
if (pending !== null) {
|
|
23045
|
+
clearTimeout(pending);
|
|
23046
|
+
pending = null;
|
|
23047
|
+
}
|
|
23048
|
+
lastBroadcast = Date.now();
|
|
23049
|
+
publish();
|
|
23050
|
+
}
|
|
23051
|
+
function dispose() {
|
|
23052
|
+
if (pending !== null) {
|
|
23053
|
+
clearTimeout(pending);
|
|
23054
|
+
pending = null;
|
|
23055
|
+
}
|
|
23056
|
+
}
|
|
23057
|
+
// Announce initial presence immediately (without consuming the throttle).
|
|
23058
|
+
publish();
|
|
23059
|
+
return { update, flush, dispose };
|
|
23060
|
+
}
|
|
23061
|
+
|
|
23062
|
+
/**
|
|
23063
|
+
* collaboration-assets.ts: binary payload sync for the pptx-viewer
|
|
23064
|
+
* collaboration stack.
|
|
23065
|
+
*
|
|
23066
|
+
* Large binary element fields (media/OLE/3D-model payloads) are synced once
|
|
23067
|
+
* via a separate `pptx:assets` Y.Map keyed by `${elementId}:${fieldName}`,
|
|
23068
|
+
* instead of being re-embedded as base64 inside every element write. This
|
|
23069
|
+
* avoids re-transmitting multi-MB payloads on every unrelated edit to the
|
|
23070
|
+
* same element: `writeAssetFields` only touches the assets map when the
|
|
23071
|
+
* stored payload actually differs from the incoming value.
|
|
23072
|
+
*
|
|
23073
|
+
* Known, accepted cost: asset entries are NOT garbage-collected when the
|
|
23074
|
+
* element that referenced them is deleted outright (only an explicit
|
|
23075
|
+
* field-clear removes its entry, via the `else` branch below). A long-lived
|
|
23076
|
+
* room's `pptx:assets` map can only grow. No sweep is implemented in this
|
|
23077
|
+
* pass; correct GC across concurrent/offline peers is a separate problem.
|
|
23078
|
+
*/
|
|
23079
|
+
const YDOC_ASSETS_KEY = 'pptx:assets';
|
|
23080
|
+
/**
|
|
23081
|
+
* Element fields large/binary enough to route through the asset map instead
|
|
23082
|
+
* of being embedded inline as a scalar or complex JSON-blob field.
|
|
23083
|
+
*/
|
|
23084
|
+
const ASSET_ELEMENT_FIELDS = new Set([
|
|
23085
|
+
'mediaData',
|
|
23086
|
+
'posterFrameData',
|
|
23087
|
+
'oleEmbeddedData',
|
|
23088
|
+
'previewImageData',
|
|
23089
|
+
'modelData',
|
|
23090
|
+
]);
|
|
23091
|
+
const ASSET_REF_KEYS = {
|
|
23092
|
+
mediaData: '_mdRef',
|
|
23093
|
+
posterFrameData: '_pfdRef',
|
|
23094
|
+
oleEmbeddedData: '_oedRef',
|
|
23095
|
+
previewImageData: '_pidRef',
|
|
23096
|
+
modelData: '_moRef',
|
|
23097
|
+
};
|
|
23098
|
+
const REV_ASSET_REF_KEYS = Object.fromEntries(Object.entries(ASSET_REF_KEYS).map(([field, ref]) => [ref, field]));
|
|
23099
|
+
function assetKey(elementId, fieldName) {
|
|
23100
|
+
return `${elementId}:${fieldName}`;
|
|
23101
|
+
}
|
|
23102
|
+
function getAssetsMap(ydoc) {
|
|
23103
|
+
return ydoc.getMap(YDOC_ASSETS_KEY);
|
|
23104
|
+
}
|
|
23105
|
+
/** True when `key` is one of the element-map ref-pointer keys this module owns. */
|
|
23106
|
+
function isAssetRefKey(key) {
|
|
23107
|
+
return key in REV_ASSET_REF_KEYS;
|
|
23108
|
+
}
|
|
23109
|
+
/**
|
|
23110
|
+
* Writes any present asset-routed fields from `rec` into `assets`, storing
|
|
23111
|
+
* only a small ref pointer (not the payload) on the element's own `ymap`.
|
|
23112
|
+
* Never reads from `ymap` itself, so it's safe to call on a brand-new,
|
|
23113
|
+
* not-yet-integrated map (Yjs throws on reading an unintegrated type) - use
|
|
23114
|
+
* this from a create/write path. `assets` is always a root-level (already
|
|
23115
|
+
* integrated) map, so the identical-payload skip-write check is still safe.
|
|
23116
|
+
*/
|
|
23117
|
+
function writeAssetFields(elementId, rec, ymap, assets) {
|
|
23118
|
+
for (const field of ASSET_ELEMENT_FIELDS) {
|
|
23119
|
+
const value = rec[field];
|
|
23120
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
23121
|
+
const key = assetKey(elementId, field);
|
|
23122
|
+
if (assets.get(key) !== value) {
|
|
23123
|
+
assets.set(key, value);
|
|
23124
|
+
}
|
|
23125
|
+
ymap.set(ASSET_REF_KEYS[field], key);
|
|
23126
|
+
}
|
|
23127
|
+
}
|
|
23128
|
+
}
|
|
23129
|
+
/**
|
|
23130
|
+
* Reconcile variant of {@link writeAssetFields} for an `ymap` that is
|
|
23131
|
+
* already integrated (an existing live element being updated in place):
|
|
23132
|
+
* additionally reads the current ref to skip a redundant set, and clears
|
|
23133
|
+
* the ref + its `assets` entry when the field was cleared.
|
|
23134
|
+
*/
|
|
23135
|
+
function reconcileAssetFields(elementId, rec, ymap, assets) {
|
|
23136
|
+
for (const field of ASSET_ELEMENT_FIELDS) {
|
|
23137
|
+
const refKey = ASSET_REF_KEYS[field];
|
|
23138
|
+
const value = rec[field];
|
|
23139
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
23140
|
+
const key = assetKey(elementId, field);
|
|
23141
|
+
if (assets.get(key) !== value) {
|
|
23142
|
+
assets.set(key, value);
|
|
23143
|
+
}
|
|
23144
|
+
if (ymap.get(refKey) !== key) {
|
|
23145
|
+
ymap.set(refKey, key);
|
|
23146
|
+
}
|
|
23147
|
+
}
|
|
23148
|
+
else {
|
|
23149
|
+
const existingRef = ymap.get(refKey);
|
|
23150
|
+
if (typeof existingRef === 'string') {
|
|
23151
|
+
ymap.delete(refKey);
|
|
23152
|
+
assets.delete(existingRef);
|
|
23153
|
+
}
|
|
23154
|
+
}
|
|
23155
|
+
}
|
|
23156
|
+
}
|
|
23157
|
+
/** Rehydrates any asset-routed fields referenced by `ymap` onto `element`. */
|
|
23158
|
+
function readAssetFields(ymap, assets, element) {
|
|
23159
|
+
for (const [refKey, field] of Object.entries(REV_ASSET_REF_KEYS)) {
|
|
23160
|
+
const ref = ymap.get(refKey);
|
|
23161
|
+
if (typeof ref === 'string') {
|
|
23162
|
+
const value = assets.get(ref);
|
|
23163
|
+
if (typeof value === 'string') {
|
|
23164
|
+
element[field] = value;
|
|
23165
|
+
}
|
|
23166
|
+
}
|
|
23167
|
+
}
|
|
23168
|
+
}
|
|
23169
|
+
|
|
22956
23170
|
/**
|
|
22957
23171
|
* collaboration-text-codec.ts: TextSegment[] <-> Y.Text delta codec used by the
|
|
22958
23172
|
* collaboration sync layer. Split out of collaboration-sync.ts to keep both
|
|
@@ -23221,6 +23435,9 @@ const SCALAR_ELEMENT_KEYS = new Set([
|
|
|
23221
23435
|
'width',
|
|
23222
23436
|
'height',
|
|
23223
23437
|
'rotation',
|
|
23438
|
+
'shapeId',
|
|
23439
|
+
'skewX',
|
|
23440
|
+
'skewY',
|
|
23224
23441
|
'flipHorizontal',
|
|
23225
23442
|
'flipVertical',
|
|
23226
23443
|
'hidden',
|
|
@@ -23229,14 +23446,66 @@ const SCALAR_ELEMENT_KEYS = new Set([
|
|
|
23229
23446
|
'name',
|
|
23230
23447
|
'altText',
|
|
23231
23448
|
'shapeType',
|
|
23232
|
-
'placeholder',
|
|
23233
23449
|
'imagePath',
|
|
23234
23450
|
'imageData',
|
|
23235
|
-
'
|
|
23236
|
-
'
|
|
23237
|
-
'
|
|
23451
|
+
'svgData',
|
|
23452
|
+
'svgPath',
|
|
23453
|
+
'cropLeft',
|
|
23454
|
+
'cropTop',
|
|
23455
|
+
'cropRight',
|
|
23456
|
+
'cropBottom',
|
|
23457
|
+
'tileOffsetX',
|
|
23458
|
+
'tileOffsetY',
|
|
23459
|
+
'tileScaleX',
|
|
23460
|
+
'tileScaleY',
|
|
23461
|
+
'tileFlip',
|
|
23462
|
+
'tileAlignment',
|
|
23463
|
+
'pathData',
|
|
23464
|
+
'pathWidth',
|
|
23465
|
+
'pathHeight',
|
|
23238
23466
|
'mediaType',
|
|
23239
23467
|
'mediaPath',
|
|
23468
|
+
'mediaMimeType',
|
|
23469
|
+
'trimStartMs',
|
|
23470
|
+
'trimEndMs',
|
|
23471
|
+
'posterFramePath',
|
|
23472
|
+
'fullScreen',
|
|
23473
|
+
'loop',
|
|
23474
|
+
'fadeInDuration',
|
|
23475
|
+
'fadeOutDuration',
|
|
23476
|
+
'volume',
|
|
23477
|
+
'autoPlay',
|
|
23478
|
+
'playAcrossSlides',
|
|
23479
|
+
'hideWhenNotPlaying',
|
|
23480
|
+
'playbackSpeed',
|
|
23481
|
+
'mediaMissing',
|
|
23482
|
+
'isLinked',
|
|
23483
|
+
'oleTarget',
|
|
23484
|
+
'oleProgId',
|
|
23485
|
+
'oleName',
|
|
23486
|
+
'oleClsId',
|
|
23487
|
+
'oleObjectType',
|
|
23488
|
+
'oleFileExtension',
|
|
23489
|
+
'fileName',
|
|
23490
|
+
'externalPath',
|
|
23491
|
+
'previewImage',
|
|
23492
|
+
'oleShowAsIcon',
|
|
23493
|
+
'oleImgW',
|
|
23494
|
+
'oleImgH',
|
|
23495
|
+
'oleEmbeddedFileName',
|
|
23496
|
+
'oleEmbeddedMimeType',
|
|
23497
|
+
'oleEmbeddedByteSize',
|
|
23498
|
+
'inkPaths',
|
|
23499
|
+
'inkColors',
|
|
23500
|
+
'inkWidths',
|
|
23501
|
+
'inkOpacities',
|
|
23502
|
+
'inkTool',
|
|
23503
|
+
'zoomType',
|
|
23504
|
+
'targetSlideIndex',
|
|
23505
|
+
'targetSectionId',
|
|
23506
|
+
'modelPath',
|
|
23507
|
+
'modelMimeType',
|
|
23508
|
+
'posterImage',
|
|
23240
23509
|
'linkedTxbxId',
|
|
23241
23510
|
'linkedTxbxSeq',
|
|
23242
23511
|
'promptText',
|
|
@@ -23249,26 +23518,35 @@ const COMPLEX_ELEMENT_FIELDS = {
|
|
|
23249
23518
|
tableData: '_td',
|
|
23250
23519
|
chartData: '_cd',
|
|
23251
23520
|
smartArtData: '_smad',
|
|
23252
|
-
connectionStart: '_cs',
|
|
23253
|
-
connectionEnd: '_ce',
|
|
23254
|
-
animations: '_an',
|
|
23255
|
-
nativeAnimations: '_na',
|
|
23256
23521
|
children: '_ch',
|
|
23257
23522
|
paragraphIndents: '_pi',
|
|
23258
23523
|
rawXml: '_rx',
|
|
23524
|
+
extLstXml: '_elx',
|
|
23259
23525
|
actionClick: '_ac',
|
|
23260
23526
|
actionHover: '_av',
|
|
23261
23527
|
locks: '_lk',
|
|
23262
23528
|
imageEffects: '_ie',
|
|
23263
23529
|
cropShape: '_cr',
|
|
23264
|
-
|
|
23530
|
+
bookmarks: '_mb',
|
|
23265
23531
|
captionTracks: '_ct',
|
|
23532
|
+
metadata: '_md',
|
|
23533
|
+
groupFill: '_gf',
|
|
23534
|
+
inkPointPressures: '_ipp',
|
|
23535
|
+
inkStrokes: '_cis',
|
|
23536
|
+
extensionXml: '_ext',
|
|
23537
|
+
customGeometryPaths: '_cgp',
|
|
23538
|
+
customGeometryRawData: '_cgr',
|
|
23539
|
+
customGeometryAdjustHandlesXY: '_cgx',
|
|
23540
|
+
customGeometryAdjustHandlesPolar: '_cgo',
|
|
23541
|
+
customGeometryConnectionSites: '_cgc',
|
|
23542
|
+
customGeometryTextRect: '_cgt',
|
|
23266
23543
|
};
|
|
23267
23544
|
const REV_COMPLEX_ELEMENT = Object.fromEntries(Object.entries(COMPLEX_ELEMENT_FIELDS).map(([k, v]) => [v, k]));
|
|
23268
23545
|
const SCALAR_SLIDE_KEYS = new Set([
|
|
23269
23546
|
'id',
|
|
23270
23547
|
'rId',
|
|
23271
23548
|
'sourceSlideId',
|
|
23549
|
+
'name',
|
|
23272
23550
|
'layoutPath',
|
|
23273
23551
|
'layoutName',
|
|
23274
23552
|
'slideNumber',
|
|
@@ -23278,7 +23556,9 @@ const SCALAR_SLIDE_KEYS = new Set([
|
|
|
23278
23556
|
'backgroundColor',
|
|
23279
23557
|
'backgroundImage',
|
|
23280
23558
|
'backgroundGradient',
|
|
23559
|
+
'backgroundShadeToTitle',
|
|
23281
23560
|
'notes',
|
|
23561
|
+
'notesCSldName',
|
|
23282
23562
|
'backgroundShowAnimation',
|
|
23283
23563
|
'showMasterShapes',
|
|
23284
23564
|
'isDirty',
|
|
@@ -23289,6 +23569,8 @@ const COMPLEX_SLIDE_FIELDS = {
|
|
|
23289
23569
|
nativeAnimations: '_na',
|
|
23290
23570
|
rawTiming: '_rt',
|
|
23291
23571
|
notesSegments: '_ns',
|
|
23572
|
+
notesShapes: '_nsh',
|
|
23573
|
+
notesClrMapOverride: '_ncm',
|
|
23292
23574
|
comments: '_cm',
|
|
23293
23575
|
warnings: '_wa',
|
|
23294
23576
|
rawXml: '_rx',
|
|
@@ -23296,15 +23578,17 @@ const COMPLEX_SLIDE_FIELDS = {
|
|
|
23296
23578
|
guides: '_gu',
|
|
23297
23579
|
customerData: '_cu',
|
|
23298
23580
|
activeXControls: '_ax',
|
|
23581
|
+
backgroundPattern: '_bp',
|
|
23582
|
+
headerFooterFlags: '_hff',
|
|
23299
23583
|
};
|
|
23300
23584
|
const REV_COMPLEX_SLIDE = Object.fromEntries(Object.entries(COMPLEX_SLIDE_FIELDS).map(([k, v]) => [v, k]));
|
|
23301
23585
|
// ---------------------------------------------------------------------------
|
|
23302
23586
|
// Element serialization
|
|
23303
23587
|
// ---------------------------------------------------------------------------
|
|
23304
|
-
function writeElementToYMap(element, ymap, factories) {
|
|
23588
|
+
function writeElementToYMap(element, ymap, factories, assets) {
|
|
23305
23589
|
const rec = element;
|
|
23306
23590
|
for (const [key, value] of Object.entries(rec)) {
|
|
23307
|
-
if (value === undefined) {
|
|
23591
|
+
if (value === undefined || ASSET_ELEMENT_FIELDS.has(key)) {
|
|
23308
23592
|
continue;
|
|
23309
23593
|
}
|
|
23310
23594
|
if (SCALAR_ELEMENT_KEYS.has(key)) {
|
|
@@ -23321,8 +23605,9 @@ function writeElementToYMap(element, ymap, factories) {
|
|
|
23321
23605
|
ymap.set(COMPLEX_ELEMENT_FIELDS[key], JSON.stringify(value));
|
|
23322
23606
|
}
|
|
23323
23607
|
}
|
|
23608
|
+
writeAssetFields(rec.id, rec, ymap, assets);
|
|
23324
23609
|
}
|
|
23325
|
-
function readElementFromYMap(ymap) {
|
|
23610
|
+
function readElementFromYMap(ymap, assets) {
|
|
23326
23611
|
const element = {};
|
|
23327
23612
|
ymap.forEach((value, key) => {
|
|
23328
23613
|
if (key === 'textBody') {
|
|
@@ -23330,6 +23615,9 @@ function readElementFromYMap(ymap) {
|
|
|
23330
23615
|
element.textSegments = decodeTextBody(value);
|
|
23331
23616
|
}
|
|
23332
23617
|
}
|
|
23618
|
+
else if (isAssetRefKey(key)) {
|
|
23619
|
+
// handled by readAssetFields below; not a literal PptxElement field
|
|
23620
|
+
}
|
|
23333
23621
|
else if (REV_COMPLEX_ELEMENT[key]) {
|
|
23334
23622
|
try {
|
|
23335
23623
|
element[REV_COMPLEX_ELEMENT[key]] = JSON.parse(value);
|
|
@@ -23342,12 +23630,13 @@ function readElementFromYMap(ymap) {
|
|
|
23342
23630
|
element[key] = value;
|
|
23343
23631
|
}
|
|
23344
23632
|
});
|
|
23633
|
+
readAssetFields(ymap, assets, element);
|
|
23345
23634
|
return element;
|
|
23346
23635
|
}
|
|
23347
23636
|
// ---------------------------------------------------------------------------
|
|
23348
23637
|
// Slide serialization
|
|
23349
23638
|
// ---------------------------------------------------------------------------
|
|
23350
|
-
function writeSlideToYMap(slide, ymap, factories) {
|
|
23639
|
+
function writeSlideToYMap(slide, ymap, factories, assets) {
|
|
23351
23640
|
const rec = slide;
|
|
23352
23641
|
for (const key of SCALAR_SLIDE_KEYS) {
|
|
23353
23642
|
if (rec[key] !== undefined) {
|
|
@@ -23362,12 +23651,12 @@ function writeSlideToYMap(slide, ymap, factories) {
|
|
|
23362
23651
|
const elemArr = factories.createArray();
|
|
23363
23652
|
for (const el of slide.elements) {
|
|
23364
23653
|
const elemMap = factories.createMap();
|
|
23365
|
-
writeElementToYMap(el, elemMap, factories);
|
|
23654
|
+
writeElementToYMap(el, elemMap, factories, assets);
|
|
23366
23655
|
elemArr.push([elemMap]);
|
|
23367
23656
|
}
|
|
23368
23657
|
ymap.set('elements', elemArr);
|
|
23369
23658
|
}
|
|
23370
|
-
function readSlideFromYMap(ymap) {
|
|
23659
|
+
function readSlideFromYMap(ymap, assets) {
|
|
23371
23660
|
const slide = {};
|
|
23372
23661
|
for (const key of SCALAR_SLIDE_KEYS) {
|
|
23373
23662
|
const v = ymap.get(key);
|
|
@@ -23390,7 +23679,7 @@ function readSlideFromYMap(ymap) {
|
|
|
23390
23679
|
const elements = [];
|
|
23391
23680
|
if (elemArr) {
|
|
23392
23681
|
for (let i = 0; i < elemArr.length; i++) {
|
|
23393
|
-
elements.push(readElementFromYMap(elemArr.get(i)));
|
|
23682
|
+
elements.push(readElementFromYMap(elemArr.get(i), assets));
|
|
23394
23683
|
}
|
|
23395
23684
|
}
|
|
23396
23685
|
slide.elements = elements;
|
|
@@ -23405,6 +23694,7 @@ function readSlideFromYMap(ymap) {
|
|
|
23405
23694
|
* one-shot seeding of an empty document.
|
|
23406
23695
|
*/
|
|
23407
23696
|
function writeSlidesToYDoc(slides, ydoc, factories, origin) {
|
|
23697
|
+
const assets = getAssetsMap(ydoc);
|
|
23408
23698
|
ydoc.transact(() => {
|
|
23409
23699
|
const arr = ydoc.getArray(YDOC_SLIDES_KEY);
|
|
23410
23700
|
if (arr.length > 0) {
|
|
@@ -23412,16 +23702,17 @@ function writeSlidesToYDoc(slides, ydoc, factories, origin) {
|
|
|
23412
23702
|
}
|
|
23413
23703
|
for (const slide of slides) {
|
|
23414
23704
|
const ymap = factories.createMap();
|
|
23415
|
-
writeSlideToYMap(slide, ymap, factories);
|
|
23705
|
+
writeSlideToYMap(slide, ymap, factories, assets);
|
|
23416
23706
|
arr.push([ymap]);
|
|
23417
23707
|
}
|
|
23418
23708
|
}, origin);
|
|
23419
23709
|
}
|
|
23420
23710
|
function readSlidesFromYDoc(ydoc) {
|
|
23711
|
+
const assets = getAssetsMap(ydoc);
|
|
23421
23712
|
const arr = ydoc.getArray(YDOC_SLIDES_KEY);
|
|
23422
23713
|
const slides = [];
|
|
23423
23714
|
for (let i = 0; i < arr.length; i++) {
|
|
23424
|
-
slides.push(readSlideFromYMap(arr.get(i)));
|
|
23715
|
+
slides.push(readSlideFromYMap(arr.get(i), assets));
|
|
23425
23716
|
}
|
|
23426
23717
|
return slides;
|
|
23427
23718
|
}
|
|
@@ -23673,11 +23964,12 @@ function reconcileTextBody(ymap, rec, factories) {
|
|
|
23673
23964
|
encodeTextBody(segments, ytext);
|
|
23674
23965
|
ymap.set('textBody', ytext);
|
|
23675
23966
|
}
|
|
23676
|
-
function reconcileElementYMap(ymap, element, factories) {
|
|
23967
|
+
function reconcileElementYMap(ymap, element, factories, assets) {
|
|
23677
23968
|
const rec = element;
|
|
23678
23969
|
reconcileScalars(ymap, rec, SCALAR_ELEMENT_KEYS);
|
|
23679
23970
|
reconcileComplexFields(ymap, rec, COMPLEX_ELEMENT_FIELDS);
|
|
23680
23971
|
reconcileTextBody(ymap, rec, factories);
|
|
23972
|
+
reconcileAssetFields(rec.id, rec, ymap, assets);
|
|
23681
23973
|
}
|
|
23682
23974
|
function mapIdAt(arr, index) {
|
|
23683
23975
|
const entry = arr.get(index);
|
|
@@ -23754,7 +24046,7 @@ const isYArrayLike = (value) => typeof value === 'object' &&
|
|
|
23754
24046
|
value !== null &&
|
|
23755
24047
|
typeof value.insert === 'function' &&
|
|
23756
24048
|
typeof value.toArray === 'function';
|
|
23757
|
-
function reconcileSlideYMap(ymap, slide, factories) {
|
|
24049
|
+
function reconcileSlideYMap(ymap, slide, factories, assets) {
|
|
23758
24050
|
const rec = slide;
|
|
23759
24051
|
reconcileScalars(ymap, rec, SCALAR_SLIDE_KEYS);
|
|
23760
24052
|
reconcileComplexFields(ymap, rec, COMPLEX_SLIDE_FIELDS);
|
|
@@ -23767,10 +24059,10 @@ function reconcileSlideYMap(ymap, slide, factories) {
|
|
|
23767
24059
|
idOf: (el) => (typeof el.id === 'string' ? el.id : undefined),
|
|
23768
24060
|
create: (el) => {
|
|
23769
24061
|
const map = factories.createMap();
|
|
23770
|
-
writeElementToYMap(el, map, factories);
|
|
24062
|
+
writeElementToYMap(el, map, factories, assets);
|
|
23771
24063
|
return map;
|
|
23772
24064
|
},
|
|
23773
|
-
update: (map, el) => reconcileElementYMap(map, el, factories),
|
|
24065
|
+
update: (map, el) => reconcileElementYMap(map, el, factories, assets),
|
|
23774
24066
|
});
|
|
23775
24067
|
}
|
|
23776
24068
|
/**
|
|
@@ -23779,16 +24071,17 @@ function reconcileSlideYMap(ymap, slide, factories) {
|
|
|
23779
24071
|
* caller's own observer can skip the resulting events.
|
|
23780
24072
|
*/
|
|
23781
24073
|
function reconcileSlidesInYDoc(slides, ydoc, factories, origin = LOCAL_SYNC_ORIGIN) {
|
|
24074
|
+
const assets = getAssetsMap(ydoc);
|
|
23782
24075
|
ydoc.transact(() => {
|
|
23783
24076
|
const arr = ydoc.getArray(YDOC_SLIDES_KEY);
|
|
23784
24077
|
reconcileYArrayById(arr, slides, {
|
|
23785
24078
|
idOf: (slide) => (typeof slide.id === 'string' ? slide.id : undefined),
|
|
23786
24079
|
create: (slide) => {
|
|
23787
24080
|
const map = factories.createMap();
|
|
23788
|
-
writeSlideToYMap(slide, map, factories);
|
|
24081
|
+
writeSlideToYMap(slide, map, factories, assets);
|
|
23789
24082
|
return map;
|
|
23790
24083
|
},
|
|
23791
|
-
update: (map, slide) => reconcileSlideYMap(map, slide, factories),
|
|
24084
|
+
update: (map, slide) => reconcileSlideYMap(map, slide, factories, assets),
|
|
23792
24085
|
});
|
|
23793
24086
|
}, origin);
|
|
23794
24087
|
}
|
|
@@ -23845,6 +24138,50 @@ function createSyncGate(onOpen, graceMs = INITIAL_SYNC_GRACE_MS) {
|
|
|
23845
24138
|
};
|
|
23846
24139
|
}
|
|
23847
24140
|
|
|
24141
|
+
const DEFAULT_DEBOUNCE_MS = 5_000;
|
|
24142
|
+
function createWriteBackScheduler(deps) {
|
|
24143
|
+
let timer = null;
|
|
24144
|
+
function cancel() {
|
|
24145
|
+
if (timer !== null) {
|
|
24146
|
+
clearTimeout(timer);
|
|
24147
|
+
timer = null;
|
|
24148
|
+
}
|
|
24149
|
+
}
|
|
24150
|
+
function schedule(config) {
|
|
24151
|
+
if (!config.onWriteBack || config.role !== 'owner' || !deps.getYDoc()) {
|
|
24152
|
+
return;
|
|
24153
|
+
}
|
|
24154
|
+
cancel();
|
|
24155
|
+
const debounceMs = config.writeBackDebounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
24156
|
+
timer = setTimeout(async () => {
|
|
24157
|
+
timer = null;
|
|
24158
|
+
const ydoc = deps.getYDoc();
|
|
24159
|
+
if (!ydoc || !config.onWriteBack) {
|
|
24160
|
+
return;
|
|
24161
|
+
}
|
|
24162
|
+
const sourceBytes = deps.getSourceBytes?.();
|
|
24163
|
+
if (!sourceBytes) {
|
|
24164
|
+
return;
|
|
24165
|
+
}
|
|
24166
|
+
try {
|
|
24167
|
+
const { PptxHandler } = await import('pptx-viewer-core');
|
|
24168
|
+
const handler = new PptxHandler();
|
|
24169
|
+
await handler.load(sourceBytes.buffer);
|
|
24170
|
+
const slides = readSlidesFromYDoc(ydoc);
|
|
24171
|
+
const merged = deps.mergeTemplateElements
|
|
24172
|
+
? deps.mergeTemplateElements(slides, deps.getTemplateElements?.() ?? {})
|
|
24173
|
+
: slides;
|
|
24174
|
+
const bytes = await handler.save(merged);
|
|
24175
|
+
config.onWriteBack(bytes);
|
|
24176
|
+
}
|
|
24177
|
+
catch {
|
|
24178
|
+
/* non-fatal */
|
|
24179
|
+
}
|
|
24180
|
+
}, debounceMs);
|
|
24181
|
+
}
|
|
24182
|
+
return { schedule, cancel };
|
|
24183
|
+
}
|
|
24184
|
+
|
|
23848
24185
|
// ---------------------------------------------------------------------------
|
|
23849
24186
|
// Tuning constants (px tolerances for position / size changes)
|
|
23850
24187
|
// ---------------------------------------------------------------------------
|
|
@@ -31881,6 +32218,155 @@ function sanitizeStops(raw) {
|
|
|
31881
32218
|
return sortStops(mapped);
|
|
31882
32219
|
}
|
|
31883
32220
|
|
|
32221
|
+
/**
|
|
32222
|
+
* Pure (framework-agnostic) helpers for the image adjustments + crop inspector
|
|
32223
|
+
* panel. Readers extract current values from a PptxElement (falling back to
|
|
32224
|
+
* sensible defaults); patch-builders produce shallow-merge-ready
|
|
32225
|
+
* `Partial<PptxElement>` objects safe to pass to each binding's element-update
|
|
32226
|
+
* action. Mirrors the pattern used by `gradient-picker.ts` / `text-advanced.ts`.
|
|
32227
|
+
*
|
|
32228
|
+
* Scope: brightness/contrast/saturation (`PptxImageEffects`) and the four crop
|
|
32229
|
+
* insets (`PptxImageProperties.cropLeft/Top/Right/Bottom`). Other image effects
|
|
32230
|
+
* (duotone, artistic filters, alpha primitives) are out of scope for the
|
|
32231
|
+
* inspector's "highest-traffic" surface; see `image-effects.ts` for the full
|
|
32232
|
+
* read-side effect model.
|
|
32233
|
+
*/
|
|
32234
|
+
/** Read the current brightness/contrast/saturation off an element (0 when unset). */
|
|
32235
|
+
function imageAdjustmentsStateOf(el) {
|
|
32236
|
+
const fx = isImageLikeElement(el) ? el.imageEffects : undefined;
|
|
32237
|
+
return {
|
|
32238
|
+
brightness: fx?.brightness ?? 0,
|
|
32239
|
+
contrast: fx?.contrast ?? 0,
|
|
32240
|
+
saturation: fx?.saturation ?? 0,
|
|
32241
|
+
};
|
|
32242
|
+
}
|
|
32243
|
+
/**
|
|
32244
|
+
* Build a Partial<PptxElement> that merges the given adjustment changes into
|
|
32245
|
+
* the element's existing `imageEffects` without dropping other effect fields.
|
|
32246
|
+
* No-op (returns `{}`) for non-image elements.
|
|
32247
|
+
*/
|
|
32248
|
+
function imageAdjustmentsPatch(el, changes) {
|
|
32249
|
+
if (!isImageLikeElement(el)) {
|
|
32250
|
+
return {};
|
|
32251
|
+
}
|
|
32252
|
+
return {
|
|
32253
|
+
imageEffects: {
|
|
32254
|
+
...el.imageEffects,
|
|
32255
|
+
...changes,
|
|
32256
|
+
},
|
|
32257
|
+
};
|
|
32258
|
+
}
|
|
32259
|
+
/** Read the current crop insets off an element (0 on every edge when unset). */
|
|
32260
|
+
function imageCropStateOf(el) {
|
|
32261
|
+
if (!isImageLikeElement(el)) {
|
|
32262
|
+
return { cropLeft: 0, cropTop: 0, cropRight: 0, cropBottom: 0 };
|
|
32263
|
+
}
|
|
32264
|
+
return {
|
|
32265
|
+
cropLeft: el.cropLeft ?? 0,
|
|
32266
|
+
cropTop: el.cropTop ?? 0,
|
|
32267
|
+
cropRight: el.cropRight ?? 0,
|
|
32268
|
+
cropBottom: el.cropBottom ?? 0,
|
|
32269
|
+
};
|
|
32270
|
+
}
|
|
32271
|
+
/** Clamp a crop fraction to the sane `[0, 0.9]` range (never crop past 90% of an edge). */
|
|
32272
|
+
function clampCropFraction(value) {
|
|
32273
|
+
if (!Number.isFinite(value)) {
|
|
32274
|
+
return 0;
|
|
32275
|
+
}
|
|
32276
|
+
return Math.min(0.9, Math.max(0, value));
|
|
32277
|
+
}
|
|
32278
|
+
/**
|
|
32279
|
+
* Build a Partial<PptxElement> that merges the given crop-inset changes onto
|
|
32280
|
+
* the element. No-op (returns `{}`) for non-image elements.
|
|
32281
|
+
*/
|
|
32282
|
+
function imageCropPatch(el, changes) {
|
|
32283
|
+
if (!isImageLikeElement(el)) {
|
|
32284
|
+
return {};
|
|
32285
|
+
}
|
|
32286
|
+
const patch = {};
|
|
32287
|
+
for (const [key, value] of Object.entries(changes)) {
|
|
32288
|
+
if (value !== undefined) {
|
|
32289
|
+
patch[key] = clampCropFraction(value);
|
|
32290
|
+
}
|
|
32291
|
+
}
|
|
32292
|
+
return patch;
|
|
32293
|
+
}
|
|
32294
|
+
|
|
32295
|
+
/**
|
|
32296
|
+
* Pure (framework-agnostic) helpers for the table-level inspector panel
|
|
32297
|
+
* (header row / banded rows toggles + a uniform default cell padding). Mirrors
|
|
32298
|
+
* the reader + patch-builder pattern used by `gradient-picker.ts` /
|
|
32299
|
+
* `text-advanced.ts` / `image-adjustments.ts`.
|
|
32300
|
+
*
|
|
32301
|
+
* Scope note: this binding has no per-cell selection model, so cell-level
|
|
32302
|
+
* formatting (background/border for one specific cell) is out of scope here;
|
|
32303
|
+
* `applyUniformCellPaddingPatch` instead writes the same margin onto every
|
|
32304
|
+
* cell uniformly, which is the closest table-level equivalent.
|
|
32305
|
+
*/
|
|
32306
|
+
/** Read table-level flags off a table element (all false/0 for non-table elements). */
|
|
32307
|
+
function tableInspectorStateOf(el) {
|
|
32308
|
+
if (el.type !== 'table' || !el.tableData) {
|
|
32309
|
+
return { firstRowHeader: false, bandedRows: false, bandedColumns: false, cellPadding: 0 };
|
|
32310
|
+
}
|
|
32311
|
+
const data = el.tableData;
|
|
32312
|
+
return {
|
|
32313
|
+
firstRowHeader: data.firstRowHeader ?? false,
|
|
32314
|
+
bandedRows: data.bandedRows ?? false,
|
|
32315
|
+
bandedColumns: data.bandedColumns ?? false,
|
|
32316
|
+
cellPadding: firstCellPadding(data),
|
|
32317
|
+
};
|
|
32318
|
+
}
|
|
32319
|
+
function firstCellPadding(data) {
|
|
32320
|
+
const firstCell = data.rows[0]?.cells[0];
|
|
32321
|
+
return firstCell?.style?.marginLeft ?? 0;
|
|
32322
|
+
}
|
|
32323
|
+
/**
|
|
32324
|
+
* Build a Partial<PptxElement> that merges the given flag changes into the
|
|
32325
|
+
* element's existing `tableData`. No-op (returns `{}`) for non-table elements
|
|
32326
|
+
* or a table with no parsed data.
|
|
32327
|
+
*/
|
|
32328
|
+
function tableInspectorPatch(el, changes) {
|
|
32329
|
+
if (el.type !== 'table' || !el.tableData) {
|
|
32330
|
+
return {};
|
|
32331
|
+
}
|
|
32332
|
+
return {
|
|
32333
|
+
tableData: {
|
|
32334
|
+
...el.tableData,
|
|
32335
|
+
...changes,
|
|
32336
|
+
},
|
|
32337
|
+
};
|
|
32338
|
+
}
|
|
32339
|
+
/**
|
|
32340
|
+
* Build a Partial<PptxElement> that sets the same left/right/top/bottom margin
|
|
32341
|
+
* (in px) on every cell of every row, so the table gets uniform default
|
|
32342
|
+
* padding. No-op (returns `{}`) for non-table elements or a table with no rows.
|
|
32343
|
+
*/
|
|
32344
|
+
function applyUniformCellPaddingPatch(el, padding) {
|
|
32345
|
+
if (el.type !== 'table' || !el.tableData) {
|
|
32346
|
+
return {};
|
|
32347
|
+
}
|
|
32348
|
+
const clamped = Math.max(0, Math.round(padding));
|
|
32349
|
+
const rows = el.tableData.rows.map((row) => ({
|
|
32350
|
+
...row,
|
|
32351
|
+
cells: row.cells.map((cell) => ({
|
|
32352
|
+
...cell,
|
|
32353
|
+
style: {
|
|
32354
|
+
...cell.style,
|
|
32355
|
+
marginLeft: clamped,
|
|
32356
|
+
marginRight: clamped,
|
|
32357
|
+
marginTop: clamped,
|
|
32358
|
+
marginBottom: clamped,
|
|
32359
|
+
},
|
|
32360
|
+
})),
|
|
32361
|
+
}));
|
|
32362
|
+
return {
|
|
32363
|
+
tableData: {
|
|
32364
|
+
...el.tableData,
|
|
32365
|
+
rows,
|
|
32366
|
+
},
|
|
32367
|
+
};
|
|
32368
|
+
}
|
|
32369
|
+
|
|
31884
32370
|
/**
|
|
31885
32371
|
* Pure, framework-agnostic comment-array transforms, shared by every binding.
|
|
31886
32372
|
*
|
|
@@ -33088,6 +33574,51 @@ const CHANGE_CASE_OPTIONS$1 = [
|
|
|
33088
33574
|
{ value: 'toggle', i18nKey: 'pptx.text.changeCaseToggle' },
|
|
33089
33575
|
];
|
|
33090
33576
|
|
|
33577
|
+
/**
|
|
33578
|
+
* color-swatches.ts: the canonical "Office Standard Colors" swatch catalogue
|
|
33579
|
+
* shared by every binding's colour pickers (font colour, highlight colour,
|
|
33580
|
+
* and any future fill/line colour picker that wants the same 10-swatch grid).
|
|
33581
|
+
*
|
|
33582
|
+
* Mirrors the "Standard Colors" row PowerPoint itself ships. Before this
|
|
33583
|
+
* module existed, the VanillaJS (`swatch-picker.ts`) and Svelte
|
|
33584
|
+
* (`SwatchColorPicker.svelte`) bindings each independently hardcoded an
|
|
33585
|
+
* identical 10-colour list; this module is the single source of truth going
|
|
33586
|
+
* forward so no binding drifts from the others.
|
|
33587
|
+
*
|
|
33588
|
+
* There is no shared-i18n dictionary key for individual colour names yet
|
|
33589
|
+
* (only the `pptx.ribbon.customColour` "Custom colour..." row label exists),
|
|
33590
|
+
* so {@link OfficeColorSwatch.label} is a plain English fallback rather than
|
|
33591
|
+
* an `i18nKey`, matching the optional-label convention used elsewhere in this
|
|
33592
|
+
* module family (e.g. `LineSpacingOption` in `text-format-presets.ts`).
|
|
33593
|
+
*
|
|
33594
|
+
* @module render/color-swatches
|
|
33595
|
+
*/
|
|
33596
|
+
/**
|
|
33597
|
+
* The canonical 10-swatch "Office Standard Colors" catalogue, in PowerPoint's
|
|
33598
|
+
* on-screen order. Consumed by the font-colour and highlight-colour pickers
|
|
33599
|
+
* (and any other picker that wants the same standard set) across every
|
|
33600
|
+
* binding.
|
|
33601
|
+
*/
|
|
33602
|
+
const OFFICE_COLOR_SWATCHES = [
|
|
33603
|
+
{ hex: '#000000', label: 'Black' },
|
|
33604
|
+
{ hex: '#ffffff', label: 'White' },
|
|
33605
|
+
{ hex: '#ff0000', label: 'Red' },
|
|
33606
|
+
{ hex: '#00aa00', label: 'Green' },
|
|
33607
|
+
{ hex: '#0000ff', label: 'Blue' },
|
|
33608
|
+
{ hex: '#ff8800', label: 'Orange' },
|
|
33609
|
+
{ hex: '#8800cc', label: 'Purple' },
|
|
33610
|
+
{ hex: '#00cccc', label: 'Cyan' },
|
|
33611
|
+
{ hex: '#ff69b4', label: 'Pink' },
|
|
33612
|
+
{ hex: '#808080', label: 'Gray' },
|
|
33613
|
+
];
|
|
33614
|
+
/**
|
|
33615
|
+
* The same catalogue flattened to bare `#rrggbb` hex strings, in the same
|
|
33616
|
+
* order as {@link OFFICE_COLOR_SWATCHES}. A drop-in replacement for the
|
|
33617
|
+
* `readonly string[]` swatch lists the vanilla and Svelte bindings each
|
|
33618
|
+
* hardcoded locally before this module existed.
|
|
33619
|
+
*/
|
|
33620
|
+
const OFFICE_COLOR_SWATCH_HEXES = OFFICE_COLOR_SWATCHES.map((swatch) => swatch.hex);
|
|
33621
|
+
|
|
33091
33622
|
/**
|
|
33092
33623
|
* PowerPoint-style title bar: shared pure logic + Tailwind class tokens.
|
|
33093
33624
|
*
|
|
@@ -37508,25 +38039,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
37508
38039
|
* surface so `collaboration.service.ts`, `collaboration-cursors.component.ts`,
|
|
37509
38040
|
* the viewer barrel, and the colocated tests are unchanged.
|
|
37510
38041
|
*
|
|
37511
|
-
*
|
|
37512
|
-
*
|
|
37513
|
-
*
|
|
37514
|
-
*
|
|
37515
|
-
*
|
|
37516
|
-
*
|
|
37517
|
-
*
|
|
37518
|
-
*
|
|
37519
|
-
* `mapAwarenessCursors` (the foundational bare-`{ cursor, user }` mapping) now
|
|
37520
|
-
* lives in shared and is re-exported here so existing Angular imports are
|
|
37521
|
-
* unchanged.
|
|
37522
|
-
*/
|
|
37523
|
-
/**
|
|
37524
|
-
* Clamp/format a cursor label so long names don't overflow the chip. Uses a
|
|
37525
|
-
* single-character ellipsis (`…`); the total length is exactly `maxChars`.
|
|
38042
|
+
* `RemotePresence` is the Angular name for shared's `SanitizedPresence`
|
|
38043
|
+
* (re-aliased here). `formatCursorLabel` and `mapAwarenessCursors` are now
|
|
38044
|
+
* re-exported as-is from shared: Angular's `formatCursorLabel` used to keep
|
|
38045
|
+
* its own single-character ellipsis (`…`) truncation that differed from
|
|
38046
|
+
* shared's three-dot (`...`) variant, so two peers viewing the same presence
|
|
38047
|
+
* record saw differently-truncated names depending on which binding hosted
|
|
38048
|
+
* the session - a real cross-binding consistency bug, not a style choice.
|
|
37526
38049
|
*/
|
|
37527
|
-
function formatCursorLabel(userName, maxChars = MAX_LABEL_CHARS) {
|
|
37528
|
-
return userName.length > maxChars ? `${userName.slice(0, maxChars - 1)}…` : userName;
|
|
37529
|
-
}
|
|
37530
38050
|
|
|
37531
38051
|
/**
|
|
37532
38052
|
* collaboration-cursors.component.ts: Angular port of the Vue
|
|
@@ -37668,8 +38188,6 @@ class LocalPresencePublisher {
|
|
|
37668
38188
|
setCursor(x, y, activeSlideIndex = this.activeSlide) {
|
|
37669
38189
|
this.cursor = { x, y };
|
|
37670
38190
|
this.activeSlide = activeSlideIndex;
|
|
37671
|
-
// A bare `cursor` field mirrors the flat awareness shape some peers read.
|
|
37672
|
-
this.awareness.setLocalStateField('cursor', { x, y });
|
|
37673
38191
|
this.publish();
|
|
37674
38192
|
}
|
|
37675
38193
|
setSelection(selectedElementId, activeSlideIndex = this.activeSlide) {
|
|
@@ -37689,14 +38207,14 @@ class LocalPresencePublisher {
|
|
|
37689
38207
|
*
|
|
37690
38208
|
* Isolates the dynamic `yjs` / `y-websocket` / `y-webrtc` imports and provider
|
|
37691
38209
|
* construction so the service focuses on lifecycle + reactive state. The Yjs
|
|
37692
|
-
* packages are dynamically imported
|
|
37693
|
-
*
|
|
37694
|
-
*
|
|
37695
|
-
*
|
|
38210
|
+
* packages are dynamically imported (plain string-literal specifiers, matching
|
|
38211
|
+
* React/Vue) so they are fully tree-shaken when collaboration is unused, while
|
|
38212
|
+
* still resolving correctly under a consuming app's Vite dev server: an
|
|
38213
|
+
* `@vite-ignore`d or variable specifier skips Vite's dev-time bare-specifier
|
|
38214
|
+
* rewrite, which then fails to resolve natively in the browser.
|
|
37696
38215
|
*/
|
|
37697
38216
|
async function createDoc() {
|
|
37698
|
-
const
|
|
37699
|
-
const Y = (await import(/* @vite-ignore */ yModule));
|
|
38217
|
+
const Y = (await import('yjs'));
|
|
37700
38218
|
const doc = new Y.Doc();
|
|
37701
38219
|
const factories = {
|
|
37702
38220
|
createMap: () => new Y.Map(),
|
|
@@ -37707,10 +38225,9 @@ async function createDoc() {
|
|
|
37707
38225
|
}
|
|
37708
38226
|
/** Create a y-websocket transport bundle for `config`. */
|
|
37709
38227
|
async function createWebsocketBundle(config) {
|
|
37710
|
-
const wsSpecifier = 'y-websocket';
|
|
37711
38228
|
const [{ doc, factories }, yws] = await Promise.all([
|
|
37712
38229
|
createDoc(),
|
|
37713
|
-
import(
|
|
38230
|
+
import('y-websocket'),
|
|
37714
38231
|
]);
|
|
37715
38232
|
const provider = new yws.WebsocketProvider(config.serverUrl, config.roomId, doc, config.authToken ? { params: { token: config.authToken } } : undefined);
|
|
37716
38233
|
return { doc, provider, awareness: provider.awareness, factories };
|
|
@@ -37722,10 +38239,9 @@ async function createWebsocketBundle(config) {
|
|
|
37722
38239
|
* `authToken` is passed as the room `password`.
|
|
37723
38240
|
*/
|
|
37724
38241
|
async function createWebrtcBundle(config) {
|
|
37725
|
-
const rtcSpecifier = 'y-webrtc';
|
|
37726
38242
|
const [{ doc, factories }, yrtc] = await Promise.all([
|
|
37727
38243
|
createDoc(),
|
|
37728
|
-
import(
|
|
38244
|
+
import('y-webrtc'),
|
|
37729
38245
|
]);
|
|
37730
38246
|
const provider = new yrtc.WebrtcProvider(config.roomId, doc, {
|
|
37731
38247
|
signaling: config.signaling?.length ? config.signaling : undefined,
|
|
@@ -37950,9 +38466,14 @@ class CollaborationService {
|
|
|
37950
38466
|
this.status.set('error');
|
|
37951
38467
|
return;
|
|
37952
38468
|
}
|
|
38469
|
+
// Falls back from a blank serverUrl the same way Vue's session layer
|
|
38470
|
+
// already does, so a bare CollaborationConfig behaves identically
|
|
38471
|
+
// regardless of which binding's session layer receives it directly (not
|
|
38472
|
+
// just via the Share/Broadcast dialogs, which already pre-resolve it).
|
|
38473
|
+
const transport = config.transport ?? resolveTransportForServerUrl(config.serverUrl);
|
|
37953
38474
|
// Fail fast on mixed content (websocket only): an https page cannot open a
|
|
37954
38475
|
// ws:// socket, so surface the error rather than hanging until the timeout.
|
|
37955
|
-
if (
|
|
38476
|
+
if (transport !== 'webrtc' && isMixedContentBlocked(config.serverUrl)) {
|
|
37956
38477
|
this.status.set('error');
|
|
37957
38478
|
return;
|
|
37958
38479
|
}
|
|
@@ -37965,7 +38486,7 @@ class CollaborationService {
|
|
|
37965
38486
|
this.activeRole.set(config.role);
|
|
37966
38487
|
this.status.set('connecting');
|
|
37967
38488
|
try {
|
|
37968
|
-
const bundle =
|
|
38489
|
+
const bundle = transport === 'webrtc'
|
|
37969
38490
|
? await createWebrtcBundle(config)
|
|
37970
38491
|
: await createWebsocketBundle(config);
|
|
37971
38492
|
this.ydoc = bundle.doc;
|
|
@@ -37982,7 +38503,7 @@ class CollaborationService {
|
|
|
37982
38503
|
this.localPresence.publish();
|
|
37983
38504
|
this.awareness.on('change', this.refreshPresence);
|
|
37984
38505
|
this.awareness.on('update', this.refreshPresence);
|
|
37985
|
-
this.wireStatus(
|
|
38506
|
+
this.wireStatus(transport);
|
|
37986
38507
|
this.syncGate.reset();
|
|
37987
38508
|
this.wireSynced();
|
|
37988
38509
|
this.unobserveSlides = observeYDocSlides(this.ydoc, (_events, transaction) => this.onRemoteChange(transaction));
|
|
@@ -38001,18 +38522,23 @@ class CollaborationService {
|
|
|
38001
38522
|
}
|
|
38002
38523
|
}
|
|
38003
38524
|
/** Wire the provider status events + (websocket-only) connection timeout. */
|
|
38004
|
-
wireStatus(
|
|
38525
|
+
wireStatus(transport) {
|
|
38005
38526
|
const provider = this.provider;
|
|
38006
38527
|
if (!provider) {
|
|
38007
38528
|
return;
|
|
38008
38529
|
}
|
|
38009
|
-
if (
|
|
38530
|
+
if (transport === 'webrtc') {
|
|
38010
38531
|
// P2P: no server round-trip to wait on. Treat "created" as connected,
|
|
38011
38532
|
// and reflect explicit disconnect events.
|
|
38012
38533
|
this.status.set('connected');
|
|
38013
38534
|
provider.on('status', (payload) => {
|
|
38014
38535
|
if (payload.connected === false && this.active()) {
|
|
38015
38536
|
this.status.set('disconnected');
|
|
38537
|
+
// Re-arm on (re)connect: without this, a peer that drops and
|
|
38538
|
+
// rejoins keeps the gate permanently open from the first
|
|
38539
|
+
// connection and can clobber the room with a stale local doc.
|
|
38540
|
+
this.syncGate.reset();
|
|
38541
|
+
this.syncGate.arm();
|
|
38016
38542
|
}
|
|
38017
38543
|
else if (payload.connected === true) {
|
|
38018
38544
|
this.status.set('connected');
|
|
@@ -38027,6 +38553,8 @@ class CollaborationService {
|
|
|
38027
38553
|
}
|
|
38028
38554
|
else if (payload.status === 'disconnected' && this.active()) {
|
|
38029
38555
|
this.status.set('disconnected');
|
|
38556
|
+
this.syncGate.reset();
|
|
38557
|
+
this.syncGate.arm();
|
|
38030
38558
|
}
|
|
38031
38559
|
});
|
|
38032
38560
|
if (provider.wsconnected) {
|
|
@@ -40044,6 +40572,7 @@ const translationsEn = {
|
|
|
40044
40572
|
'pptx.table.bandRowCycle': 'Row band size',
|
|
40045
40573
|
'pptx.table.cell': 'Cell R{{row}} C{{col}}',
|
|
40046
40574
|
'pptx.table.cellBorders': 'Cell borders',
|
|
40575
|
+
'pptx.table.cellPadding': 'Cell padding',
|
|
40047
40576
|
'pptx.table.color': 'Text',
|
|
40048
40577
|
'pptx.table.columnWidths': 'Column widths',
|
|
40049
40578
|
'pptx.table.even': 'Even',
|
|
@@ -40585,6 +41114,9 @@ const translationsEn = {
|
|
|
40585
41114
|
'pptx.ribbon.strokeWidth': 'Stroke width',
|
|
40586
41115
|
'pptx.ribbon.browseThemes': 'Browse Themes',
|
|
40587
41116
|
'pptx.ribbon.browseThemesTitle': 'Browse and apply built-in themes',
|
|
41117
|
+
'pptx.ribbon.theme.default': 'Default',
|
|
41118
|
+
'pptx.ribbon.theme.light': 'Light (Vermilion)',
|
|
41119
|
+
'pptx.ribbon.theme.dark': 'Dark (Vermilion)',
|
|
40588
41120
|
'pptx.ribbon.editTheme': 'Edit Theme',
|
|
40589
41121
|
'pptx.ribbon.editThemeTitle': 'Edit theme - theme editor not yet ported',
|
|
40590
41122
|
'pptx.ribbon.slideSize': 'Slide Size',
|
|
@@ -41337,6 +41869,10 @@ const translationsEn = {
|
|
|
41337
41869
|
'pptx.text.characterSpacingTight': 'Tight',
|
|
41338
41870
|
'pptx.text.characterSpacingVeryLoose': 'Very Loose',
|
|
41339
41871
|
'pptx.text.characterSpacingVeryTight': 'Very Tight',
|
|
41872
|
+
'pptx.textAdvanced.autoFit': 'AutoFit',
|
|
41873
|
+
'pptx.textAdvanced.autoFitNone': 'Do not autofit',
|
|
41874
|
+
'pptx.textAdvanced.autoFitResize': 'Resize shape to fit text',
|
|
41875
|
+
'pptx.textAdvanced.autoFitShrink': 'Shrink text on overflow',
|
|
41340
41876
|
'pptx.textAdvanced.autoPlaceholder': 'auto',
|
|
41341
41877
|
'pptx.textAdvanced.indent': 'Indent',
|
|
41342
41878
|
'pptx.textAdvanced.indentMargin': 'Indent & Margin',
|
|
@@ -41350,6 +41886,7 @@ const translationsEn = {
|
|
|
41350
41886
|
'pptx.textAdvanced.spaceBefore': 'Space Before',
|
|
41351
41887
|
'pptx.textAdvanced.spacing': 'Spacing',
|
|
41352
41888
|
'pptx.textAdvanced.vAlign': 'V-Align',
|
|
41889
|
+
'pptx.textAdvanced.wrapText': 'Wrap text in shape',
|
|
41353
41890
|
'pptx.textFormatting.bulleted': 'Bulleted',
|
|
41354
41891
|
'pptx.textFormatting.numbered': 'Numbered',
|
|
41355
41892
|
'pptx.textFormatting.paragraphAfter': 'Paragraph After',
|