Skip to content

Commit d668786

Browse files
Merge branch 'lts-2025' into fix-WEBUI-810-vocabulary-edit-lts2025
2 parents 45faa4b + 7bc95ea commit d668786

46 files changed

Lines changed: 3723 additions & 119 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.sample

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
NUXEO_WEB_UI_VERSION=2025.16.0-SNAPSHOT
1+
NUXEO_WEB_UI_VERSION=2025.17.0-SNAPSHOT
22
NUXEO_VERSION=master
33
NUXEO_PACKAGES=nuxeo-drive nuxeo-liveconnect nuxeo-template-rendering
44
NUXEO_DEV_MODE=true

.github/workflows/sonar.yaml

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ jobs:
111111
args: >
112112
-Dsonar.projectVersion=${{ steps.project_meta.outputs.version }}
113113
-Dsonar.scm.revision=${{ github.sha }}
114-
-Dsonar.qualitygate.wait=true
114+
-Dsonar.qualitygate.wait=false
115115
${{ github.event_name == 'pull_request' &&
116116
format('-Dsonar.pullrequest.key={0} -Dsonar.pullrequest.branch={1} -Dsonar.pullrequest.base={2}',
117117
github.event.pull_request.number,
@@ -120,17 +120,16 @@ jobs:
120120
|| format('-Dsonar.branch.name={0}', steps.project_meta.outputs.branch) }}
121121
122122
- name: Enforce Quality Gate
123-
# Fails if sonar scan was skipped (e.g. prior step failed) or gate not passed.
123+
# TODO: Temporarily non-blocking due to revert PR baseline issue. Revert to exit 1 once
124+
# SonarCloud new-code baseline resets (after next version bump or baseline reconfiguration).
124125
if: always()
125126
run: |
126127
if [ "${{ steps.sonar_scan.outcome }}" = "skipped" ]; then
127-
echo "❌ SonarCloud scan was skipped — a prior step (e.g. coverage baseline) failed."
128-
exit 1
129-
fi
130-
if [ "${{ steps.sonar_scan.outcome }}" != "success" ]; then
131-
echo "❌ SonarCloud Quality Gate FAILED. Fix flagged issues before merging."
128+
echo "⚠️ SonarCloud scan was skipped — a prior step (e.g. coverage baseline) failed."
129+
elif [ "${{ steps.sonar_scan.outcome }}" != "success" ]; then
130+
echo "⚠️ SonarCloud Quality Gate did not pass (non-blocking, baseline issue)."
132131
echo " • New-code coverage must be ≥ 90 %"
133132
echo " • No unresolved code-quality issues on new/changed code"
134-
exit 1
135-
fi
136-
echo "✅ SonarCloud Quality Gate passed."
133+
else
134+
echo "✅ SonarCloud Quality Gate passed."
135+
fi

charts/preview/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ apiVersion: v1
22
description: A Helm chart for Web UI preview
33
icon: https://media.licdn.com/dms/image/C4D0BAQFPXiXFrp4LBA/company-logo_200_200/0?e=2159024400&v=beta&t=RW9EU0QUciUVuPSpLySd9FtJ2yG-O37_hAAvc32f6ro
44
name: preview
5-
version: 2025.16.0-SNAPSHOT
5+
version: 2025.17.0-SNAPSHOT
Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
/**
2+
@license
3+
©2023 Hyland Software, Inc. and its affiliates. All rights reserved.
4+
All Hyland product names are registered or unregistered trademarks of Hyland Software, Inc. or its affiliates.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
/** Natural width (px) of the drawer's content (icons + labels), excluding the sidebar column. */
20+
export const DRAWER_NATURAL_CONTENT_PX = 298;
21+
/** Absolute floor (px) for the drawer body under heavy zoom: `sidebarPx + this value`. */
22+
const DRAWER_MIN_FLOOR_OFFSET_PX = 120;
23+
/** Hard floor (px) for the main content column beside the drawer (matches document-page). */
24+
const DRAWER_MAIN_COLUMN_MIN_PX = 240;
25+
/** Max main-column reservation (px) when computing drawer max width; mirrors document-page. */
26+
const DRAWER_MAIN_COLUMN_TARGET_MAX_PX = 640;
27+
/** Share of layout width (sidebar excluded) reserved for main before the drawer can grow further. */
28+
const DRAWER_MAIN_COLUMN_RATIO = 0.5;
29+
/** The drawer can never occupy more than this fraction of the viewport. */
30+
const DRAWER_VIEWPORT_HALF_RATIO = 0.5;
31+
/** Fallback for `_sidebarPx()` when the `--nuxeo-sidebar-width` CSS variable can't be parsed. */
32+
const DRAWER_SIDEBAR_FALLBACK_PX = 52;
33+
/** Debounce before re-enabling drawer width transition after info-pane push-back. */
34+
const DRAWER_RESIZING_CLEAR_DELAY_MS = 100;
35+
/** localStorage key under which the user's preferred drawer width is persisted. */
36+
const DRAWER_STORAGE_KEY = 'nuxeo.drawerWidth';
37+
38+
/**
39+
* Resizable browse/search drawer: width math, persistence, pointer/keyboard resize,
40+
* and push-back when the document info pane grows.
41+
*
42+
* Expects the host to provide `drawerWidth`, `drawerOpened`, `sidebarWidth`, `isNarrow`,
43+
* `_isRTL`, and layout helpers `_notifyLayoutChanged` / `_scheduleDrawerDragLayoutNotify`.
44+
*
45+
* @polymerBehavior Nuxeo.AppDrawerResizeBehavior
46+
*/
47+
export const NuxeoAppDrawerResizeBehavior = {
48+
properties: {
49+
/** Persisted drawer width (px); null uses the default open width. */
50+
_drawerOpenWidth: {
51+
type: Number,
52+
value: null,
53+
},
54+
55+
/** Hidden when the drawer is closed or in narrow (overlay) layout. */
56+
_drawerResizeHidden: {
57+
type: Boolean,
58+
computed: '_computeDrawerResizeHidden(drawerOpened, isNarrow)',
59+
value: true,
60+
},
61+
62+
/** ARIA bounds for the drawer resize separator (updated with width changes). */
63+
_drawerResizeAriaMin: {
64+
type: Number,
65+
value: 0,
66+
},
67+
68+
_drawerResizeAriaMax: {
69+
type: Number,
70+
value: 0,
71+
},
72+
73+
_drawerResizeAriaNow: {
74+
type: Number,
75+
value: 0,
76+
},
77+
},
78+
79+
/** Viewport width minus the icon sidebar (px). */
80+
_drawerLayoutWidth() {
81+
return Math.max(0, window.innerWidth - this._sidebarPx());
82+
},
83+
84+
/** Min width (px) left for main content when the drawer grows (proportional, like info pane). */
85+
_minMainWidthForDrawer() {
86+
const layoutWidth = this._drawerLayoutWidth();
87+
return Math.max(
88+
DRAWER_MAIN_COLUMN_MIN_PX,
89+
Math.min(DRAWER_MAIN_COLUMN_TARGET_MAX_PX, Math.floor(layoutWidth * DRAWER_MAIN_COLUMN_RATIO)),
90+
);
91+
},
92+
93+
/** Min drawer width: natural open size, capped by viewport on zoom. */
94+
_minDrawerWidth() {
95+
const defaultOpen = DRAWER_NATURAL_CONTENT_PX + this._sidebarPx();
96+
const viewportCap = Math.floor(window.innerWidth * DRAWER_VIEWPORT_HALF_RATIO);
97+
return Math.min(defaultOpen, Math.max(this._sidebarPx() + DRAWER_MIN_FLOOR_OFFSET_PX, viewportCap));
98+
},
99+
100+
/** Max drawer width: half the viewport and enough room for `_minMainWidthForDrawer`. */
101+
_maxDrawerWidth() {
102+
const min = this._minDrawerWidth();
103+
const layoutWidth = this._drawerLayoutWidth();
104+
const capFromMain = Math.floor(layoutWidth - this._minMainWidthForDrawer());
105+
const capFromViewport = Math.floor(window.innerWidth * DRAWER_VIEWPORT_HALF_RATIO);
106+
const cap = Math.min(capFromMain, capFromViewport);
107+
return Math.max(min, cap);
108+
},
109+
110+
/** Parsed icon sidebar width (px); uses fallback when the CSS value is missing. */
111+
_sidebarPx() {
112+
return Number.parseInt(this.sidebarWidth, 10) || DRAWER_SIDEBAR_FALLBACK_PX;
113+
},
114+
115+
/** Open drawer width (px): stored preference or natural default, clamped. */
116+
_computeOpenDrawerWidth() {
117+
const fallback = DRAWER_NATURAL_CONTENT_PX + this._sidebarPx();
118+
const stored = this._drawerOpenWidth ?? this._loadStoredDrawerWidth();
119+
if (stored == null) {
120+
return fallback;
121+
}
122+
this._drawerOpenWidth = stored;
123+
return this._clampDrawerWidth(stored);
124+
},
125+
126+
/** Clamp drawer width between `_minDrawerWidth` and `_maxDrawerWidth`. */
127+
_clampDrawerWidth(px) {
128+
return Math.min(this._maxDrawerWidth(), Math.max(this._minDrawerWidth(), px));
129+
},
130+
131+
/** Read persisted drawer width from localStorage, or null if missing/invalid. */
132+
_loadStoredDrawerWidth() {
133+
try {
134+
const raw = globalThis.localStorage?.getItem(DRAWER_STORAGE_KEY);
135+
const n = Number.parseInt(raw ?? '', 10);
136+
if (!Number.isFinite(n)) {
137+
return null;
138+
}
139+
return n;
140+
} catch {
141+
return null;
142+
}
143+
},
144+
145+
/** Persist drawer width under `nuxeo.drawerWidth` (no-op when storage is unavailable). */
146+
_persistDrawerWidth(px) {
147+
try {
148+
if (globalThis.localStorage) {
149+
globalThis.localStorage.setItem(DRAWER_STORAGE_KEY, String(px));
150+
}
151+
} catch {
152+
// Storage may be unavailable (private mode, quota); width is not persisted.
153+
}
154+
},
155+
156+
_drawerResizeActive() {
157+
return this.drawerOpened && !this.isNarrow;
158+
},
159+
160+
/** Applies a keyboard step from `nuxeo-resize-handle`. */
161+
_onDrawerResizeStep(e) {
162+
if (!this._drawerResizeActive()) {
163+
return;
164+
}
165+
const current = this._computeOpenDrawerWidth();
166+
const next = this._clampDrawerWidth(current + e.detail.delta);
167+
this._drawerOpenWidth = next;
168+
this.drawerWidth = `${next}px`;
169+
this._persistDrawerWidth(next);
170+
this._updateDrawerResizeAria();
171+
this._scheduleDrawerDragLayoutNotify();
172+
},
173+
174+
/** Jumps to min or max width from `nuxeo-resize-handle` (Home/End). */
175+
_onDrawerResizeBound(e) {
176+
if (!this._drawerResizeActive()) {
177+
return;
178+
}
179+
const next = e.detail.bound === 'min' ? this._minDrawerWidth() : this._maxDrawerWidth();
180+
this._drawerOpenWidth = next;
181+
this.drawerWidth = `${next}px`;
182+
this._persistDrawerWidth(next);
183+
this._updateDrawerResizeAria();
184+
this._notifyLayoutChanged();
185+
},
186+
187+
/** Reset width from `nuxeo-resize-handle` (Enter/Space/dblclick). */
188+
_onDrawerResizeReset() {
189+
if (!this._drawerResizeActive()) {
190+
return;
191+
}
192+
this._resetDrawerWidth();
193+
},
194+
195+
/** Pointer drag start from `nuxeo-resize-handle`. */
196+
_onDrawerResizeDragStart() {
197+
if (!this._drawerResizeActive()) {
198+
return;
199+
}
200+
this._drawerDragStartWidth = this._computeOpenDrawerWidth();
201+
this.setAttribute('drawer-resizing', '');
202+
},
203+
204+
/** Pointer drag move from `nuxeo-resize-handle`. */
205+
_onDrawerResizeDrag(e) {
206+
if (!this._drawerResizeActive() || this._drawerDragStartWidth == null) {
207+
return;
208+
}
209+
const next = this._clampDrawerWidth(this._drawerDragStartWidth + e.detail.deltaFromStart);
210+
this._drawerOpenWidth = next;
211+
this.drawerWidth = `${next}px`;
212+
this._updateDrawerResizeAria();
213+
this._scheduleDrawerDragLayoutNotify();
214+
},
215+
216+
/** Pointer drag end from `nuxeo-resize-handle`. */
217+
_onDrawerResizeDragEnd() {
218+
this._cancelDrawerDragLayoutNotify();
219+
this.removeAttribute('drawer-resizing');
220+
this._drawerDragStartWidth = null;
221+
if (this._drawerOpenWidth != null) {
222+
this._persistDrawerWidth(this._drawerOpenWidth);
223+
}
224+
this._notifyLayoutChanged();
225+
},
226+
227+
/** Shrinks the open drawer when the info pane needs more width (`nuxeo-shrink-drawer`). */
228+
_onShrinkDrawerRequest(e) {
229+
if (!this.drawerOpened || this.isNarrow) {
230+
return;
231+
}
232+
const rawAmount = e?.detail?.amount;
233+
const amount = Number.isFinite(rawAmount) ? Math.max(0, rawAmount) : 0;
234+
if (amount <= 0) {
235+
return;
236+
}
237+
const current = this._computeOpenDrawerWidth();
238+
const min = this._minDrawerWidth();
239+
if (current <= min) {
240+
return;
241+
}
242+
const next = Math.max(min, current - amount);
243+
if (next === current) {
244+
return;
245+
}
246+
// Keep drawer-resizing set during push-back so width changes are instant.
247+
const hadAttr = this.hasAttribute('drawer-resizing');
248+
if (!hadAttr) {
249+
this.setAttribute('drawer-resizing', '');
250+
}
251+
this._drawerOpenWidth = next;
252+
this.drawerWidth = `${next}px`;
253+
this._persistDrawerWidth(next);
254+
this._updateDrawerResizeAria();
255+
if (!hadAttr) {
256+
if (this._clearDrawerResizingTimer) {
257+
clearTimeout(this._clearDrawerResizingTimer);
258+
}
259+
this._clearDrawerResizingTimer = setTimeout(() => {
260+
this.removeAttribute('drawer-resizing');
261+
this._clearDrawerResizingTimer = null;
262+
}, DRAWER_RESIZING_CLEAR_DELAY_MS);
263+
}
264+
this._scheduleDrawerDragLayoutNotify();
265+
},
266+
267+
/** Reset drawer to default width and clear localStorage. */
268+
_resetDrawerWidth() {
269+
this._drawerOpenWidth = null;
270+
try {
271+
if (globalThis.localStorage) {
272+
globalThis.localStorage.removeItem(DRAWER_STORAGE_KEY);
273+
}
274+
} catch {
275+
// Storage may be unavailable (private mode, quota); width is not persisted.
276+
}
277+
if (this.drawerOpened) {
278+
this.drawerWidth = `${DRAWER_NATURAL_CONTENT_PX + this._sidebarPx()}px`;
279+
this._updateDrawerResizeAria();
280+
this._notifyLayoutChanged();
281+
} else {
282+
this._updateDrawerResizeAria();
283+
}
284+
},
285+
286+
/** Hide the drawer resize handle when the drawer is closed or layout is narrow. */
287+
_computeDrawerResizeHidden(drawerOpened, isNarrow) {
288+
return !drawerOpened || Boolean(isNarrow);
289+
},
290+
291+
/** Sync aria-valuemin / aria-valuemax / aria-valuenow on the drawer resize handle. */
292+
_updateDrawerResizeAria() {
293+
if (!this.drawerOpened || this.isNarrow) {
294+
this._drawerResizeAriaMin = 0;
295+
this._drawerResizeAriaMax = 0;
296+
this._drawerResizeAriaNow = 0;
297+
return;
298+
}
299+
this._drawerResizeAriaMin = this._minDrawerWidth();
300+
this._drawerResizeAriaMax = this._maxDrawerWidth();
301+
this._drawerResizeAriaNow = this._computeOpenDrawerWidth();
302+
},
303+
304+
/**
305+
* Re-clamp drawer width on resize/zoom. Syncs inline `drawerWidth`, not only
306+
* `_drawerOpenWidth`, so a wide preference is restored after narrow-mode open.
307+
*/
308+
_reclampDrawerWidth() {
309+
if (!this.drawerOpened || this.isNarrow) {
310+
return;
311+
}
312+
const target = this._computeOpenDrawerWidth();
313+
const currentInlinePx = Number.parseInt(this.drawerWidth, 10) || 0;
314+
if (currentInlinePx !== target) {
315+
this.drawerWidth = `${target}px`;
316+
this._updateDrawerResizeAria();
317+
}
318+
},
319+
320+
/**
321+
* At most one layout notify per frame while dragging. Synthetic `window.resize`
322+
* is included so `nuxeo-document-page._onWindowResize` runs `_scheduleViewportReclamp`
323+
* and the info pane shrinks live during drag — preventing the main column from being
324+
* squeezed below its min width. `_updateIsNarrow` is suppressed via
325+
* `_suppressLayoutResizeHandler` inside `_runLayoutNotify` to avoid feedback.
326+
*/
327+
_scheduleDrawerDragLayoutNotify() {
328+
if (this._drawerDragLayoutRaf != null) {
329+
return;
330+
}
331+
this._drawerDragLayoutRaf = requestAnimationFrame(() => {
332+
this._drawerDragLayoutRaf = null;
333+
this._runLayoutNotify({ includeWindowResize: true });
334+
});
335+
},
336+
337+
/** Cancel a pending drawer-drag layout notify animation frame. */
338+
_cancelDrawerDragLayoutNotify() {
339+
if (this._drawerDragLayoutRaf != null) {
340+
cancelAnimationFrame(this._drawerDragLayoutRaf);
341+
this._drawerDragLayoutRaf = null;
342+
}
343+
},
344+
};

0 commit comments

Comments
 (0)