@zambon-dev/shared 1.2.0 → 2.1.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.
- package/CHANGELOG.md +194 -1
- package/fesm2022/zambon-dev-shared.mjs +477 -12
- package/fesm2022/zambon-dev-shared.mjs.map +1 -1
- package/i18n/external-content/en.json +10 -0
- package/i18n/external-content/pt.json +10 -0
- package/index.d.ts +1 -0
- package/lib/features/external-content/external-content.component.d.ts +43 -0
- package/lib/features/external-content/external-content.routes.d.ts +25 -0
- package/lib/features/external-content/index.d.ts +2 -0
- package/lib/layouts/main-layout/main-layout.component.d.ts +3 -0
- package/lib/models/current-user-info.d.ts +15 -0
- package/lib/models/external-content.d.ts +28 -0
- package/lib/models/index.d.ts +1 -0
- package/lib/models/operations-history.d.ts +4 -2
- package/lib/models/services-history.d.ts +1 -1
- package/lib/services/external-content.service.d.ts +35 -0
- package/lib/services/external-url-resolver.service.d.ts +49 -0
- package/lib/services/index.d.ts +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -23,6 +23,197 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
23
23
|
|
|
24
24
|
### ⚠ Breaking Changes / Migration
|
|
25
25
|
|
|
26
|
+
## [2.1.0] - 2026-09-08
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **External sidebar destinations are now opened, in either of two modes.** `MainLayoutComponent`
|
|
31
|
+
subscribes to `@zambon-dev/library`’s new `SidebarService.menuExternalUrlSelected`: an item whose
|
|
32
|
+
`openMode` is `ExternalNewTab` opens in a new browser tab (`noopener,noreferrer`), and one marked
|
|
33
|
+
`ExternalEmbedded` opens an application tab that displays the destination in a sandboxed iframe,
|
|
34
|
+
keeping the user inside the application. Internal routes are untouched.
|
|
35
|
+
|
|
36
|
+
- **`ExternalUrlResolverService`** — substitutes the runtime placeholders in an external menu URL
|
|
37
|
+
and vets the result. The placeholder set is closed and case-sensitive: `{email}`, `{language}`,
|
|
38
|
+
`{userId}`, `{userName}`. Every substituted value is `encodeURIComponent`-ed, so a name containing
|
|
39
|
+
`&` cannot inject a query parameter — which also means one placeholder must occupy one whole path
|
|
40
|
+
segment or query-parameter value. A supported placeholder with no value becomes an empty string
|
|
41
|
+
(with a console warning); an unrecognized `{…}` is left exactly as configured, so a report URL
|
|
42
|
+
that legitimately contains braces, such as `?filter={"a":1}`, is not corrupted.
|
|
43
|
+
|
|
44
|
+
No authentication token is ever substituted, and the set is closed by construction rather than
|
|
45
|
+
reflected off the stored user info — `AuthenticationService` persists the whole sign-in response
|
|
46
|
+
under `userInfo`, tokens included, so a reflective implementation would let a URL configured as
|
|
47
|
+
`?t={token}` hand the JWT to a third party.
|
|
48
|
+
|
|
49
|
+
`{language}` and `{userName}` work today. `{userId}` and `{email}` resolve to an empty string
|
|
50
|
+
until your `Authentication/SignIn` and `Authentication/RefreshToken` responses include `userID`
|
|
51
|
+
and `email` (see `ICurrentUserInfo` below).
|
|
52
|
+
|
|
53
|
+
- **`ExternalContentComponent` + `externalContentRoutes`** — the embedded view. Spread the routes
|
|
54
|
+
into `MainLayoutComponent`’s children:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
{ path: '', component: MainLayoutComponent, canActivate: [AuthGuard], children: [
|
|
58
|
+
...externalContentRoutes,
|
|
59
|
+
// your own features
|
|
60
|
+
] }
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Register them even if you only plan to use `ExternalNewTab`: an embedded item configured without
|
|
64
|
+
them opens a tab that immediately bounces to the home route.
|
|
65
|
+
|
|
66
|
+
The view is an ordinary `TabViewBase` hosted by `DefaultTabViewComponent`, so its actions appear
|
|
67
|
+
in the application ribbon (a **Page** group with **Refresh** and **Open in a new browser tab**)
|
|
68
|
+
and look like every other screen’s — the shipped `externalContentRoutes` already wires that host
|
|
69
|
+
up, which is the other reason not to hand-write the routes. **Refresh** carries the same icon and
|
|
70
|
+
label as `framework-button-refresh`, because reloading a report is the same action as refreshing
|
|
71
|
+
a grid.
|
|
72
|
+
|
|
73
|
+
Refreshing genuinely tears the frame down and builds a new one — a cross-origin frame cannot be
|
|
74
|
+
navigated any other way — so the old render visibly goes away instead of sitting there while you
|
|
75
|
+
wonder whether anything happened. The button spins and the panel shows a loading overlay until
|
|
76
|
+
the destination reports `load`, or until the slow-frame delay elapses, so a destination that
|
|
77
|
+
never reports one cannot leave the controls stuck.
|
|
78
|
+
|
|
79
|
+
**Open in a new browser tab** is always available, because many sites refuse to be embedded
|
|
80
|
+
(`X-Frame-Options`, CSP `frame-ancestors`) and a browser gives JavaScript no reliable way to
|
|
81
|
+
detect that — if nothing has loaded after a few seconds the view also shows a hint saying so. An
|
|
82
|
+
`https` application cannot embed an `http` destination at all; the same button is the way out.
|
|
83
|
+
|
|
84
|
+
The tab URL is `/external-content/<menu id>` and never carries the destination, so no one can
|
|
85
|
+
hand-craft a link that makes your application frame an arbitrary site. Pressing F5 on an embedded
|
|
86
|
+
tab restores both the frame and the tab title from `sessionStorage`. Opening that URL in a *fresh*
|
|
87
|
+
browser tab can only work if your `SidebarService.getMenuFromUrl()` resolves
|
|
88
|
+
`/external-content/<id>`; otherwise the view says the content is unavailable and asks the user to
|
|
89
|
+
reopen it from the menu.
|
|
90
|
+
|
|
91
|
+
- **`EXTERNAL_CONTENT_CONFIGS`** — `allowedOrigins` (default `[]`, meaning any `http`/`https`
|
|
92
|
+
origin) and `slowFrameHintDelay` (default 5000 ms). **Populate `allowedOrigins` in production.**
|
|
93
|
+
Embedded and new-tab destinations are already rejected unless they are absolute `http`/`https`
|
|
94
|
+
URLs, which stops a `javascript:` or `data:` URL in your menu table from executing in your users’
|
|
95
|
+
session; an origin allowlist narrows what is left from “any site on the internet” to your known
|
|
96
|
+
report hosts. The iframe is sandboxed with a fixed, non-configurable token list that withholds
|
|
97
|
+
`allow-top-navigation`, so a framed site cannot navigate your application away. Do not point an
|
|
98
|
+
embedded item at your own application’s origin — use an internal route for that.
|
|
99
|
+
|
|
100
|
+
- **`ICurrentUserInfo.email`, `.userID`, `.username`** (all optional) — the values behind
|
|
101
|
+
`{email}`, `{userId}` and `{userName}`. `username` was already being persisted in the base64
|
|
102
|
+
`userInfo` entry and is now simply typed, so `{userName}` needs no backend change; `email` and
|
|
103
|
+
`userID` must be added to your sign-in and refresh responses.
|
|
104
|
+
|
|
105
|
+
- **Translations** for the embedded view under `i18n/external-content/`, already included in
|
|
106
|
+
`ZAMBON_SHARED_I18N_RESOURCES`.
|
|
107
|
+
|
|
108
|
+
### Fixed
|
|
109
|
+
|
|
110
|
+
- **A deep link whose URL the menu API cannot resolve no longer surfaces an unhandled error.**
|
|
111
|
+
`MainLayoutComponent` resolves a deep-linked tab’s title through `SidebarService.getMenuFromUrl()`
|
|
112
|
+
and had no error handler, so a 404 became an unhandled rejection in the console. The title is
|
|
113
|
+
best-effort and the failure is now swallowed — which matters more now that the embedded-content
|
|
114
|
+
route is a URL shape most menu endpoints do not know.
|
|
115
|
+
|
|
116
|
+
### ⚠ Breaking Changes / Migration
|
|
117
|
+
|
|
118
|
+
- **Requires `@zambon-dev/library` 1.4.0 or later.** `SidebarMenuOpenMode`,
|
|
119
|
+
`toSidebarMenuOpenMode` and `SidebarService.menuExternalUrlSelected` ship in that release. The
|
|
120
|
+
declared peer range still allows older versions, so upgrade both packages together.
|
|
121
|
+
- **To use external menu items, register `externalContentRoutes`** as children of
|
|
122
|
+
`MainLayoutComponent` (snippet above), and make your menu endpoint return `openMode`. The
|
|
123
|
+
resolver accepts it as the camelCase string (`"internal"`, `"externalNewTab"`,
|
|
124
|
+
`"externalEmbedded"`, matched case-insensitively) or as the enum ordinal (`0`, `1`, `2`), so a
|
|
125
|
+
plain ASP.NET Core enum property works with no converter.
|
|
126
|
+
- **The embedded view needs both i18n bundles registered.** Its own strings ship in this package
|
|
127
|
+
(`ZAMBON_SHARED_I18N_RESOURCES`, under `assets/i18n/zambon-dev/shared/external-content/`), but the
|
|
128
|
+
ribbon reuses three keys owned by `@zambon-dev/framework` — `RibbonGroup-Page`, `Button-Refresh`
|
|
129
|
+
and `Loading` — so `ZAMBON_FRAMEWORK_I18N_RESOURCES` has to be registered too, or those render as
|
|
130
|
+
raw keys. Any application that already uses framework buttons registers it; if your
|
|
131
|
+
`TranslateLoader` hand-lists prefixes instead of spreading the two constants, add both.
|
|
132
|
+
- Nothing else changes: applications with no `openMode` on any menu item behave exactly as before
|
|
133
|
+
and need no action.
|
|
134
|
+
|
|
135
|
+
## [2.0.0] - 2026-07-30
|
|
136
|
+
|
|
137
|
+
### Added
|
|
138
|
+
|
|
139
|
+
- **Storybook: `Shared/App Showcase` story** — a full-height, navigable demo of the complete
|
|
140
|
+
application shell, backed by in-memory mock data. Clicking the sidebar entries (Dashboard,
|
|
141
|
+
General ▸ Customers/Units, Security ▸ Users) opens tabs that render working list-views and
|
|
142
|
+
detail-views through the real hosts (`DefaultTabViewComponent` /
|
|
143
|
+
`DefaultDetailsTabViewComponent`, `TabViewList` / `FormView`, and the `framework-button-*`
|
|
144
|
+
ribbon buttons). It covers:
|
|
145
|
+
- a branded top bar (logo, app name, subtitle, environment badge and a working notifications
|
|
146
|
+
bell) and a versioned sidebar footer;
|
|
147
|
+
- a mocked audit/history view, reachable from the detail views' Views button;
|
|
148
|
+
- `framework-button-filters` (via a `FiltersBase` component per entity) and
|
|
149
|
+
`framework-button-export` on every list: the mock dataset honours `IListParameters.filters`, so
|
|
150
|
+
filtering visibly narrows the grid, and exporting downloads a real CSV of the filtered rows;
|
|
151
|
+
- a **child list of addresses** on the Customers detail view, edited through `lib-multi-editor` —
|
|
152
|
+
`ChildList` and `MultiEditorModal` together, with the accordion gated on `hasEntityID` (a child
|
|
153
|
+
collection needs a persisted parent), the Edit button opening the multi-editor rather than
|
|
154
|
+
entering form edit mode, and add/remove/edit applied as one batch. The Customers grid's City
|
|
155
|
+
column is **derived** from the customer's first address, so editing an address is reflected in
|
|
156
|
+
the parent list and its filter with nothing to keep in sync;
|
|
157
|
+
- mocked request latency, so loading states are observable;
|
|
158
|
+
- full `en`/`pt` translation, so the language selector switches the entire showcase.
|
|
159
|
+
|
|
160
|
+
Development-only: it lives entirely in `app-showcase.stories.ts`, which is excluded from the
|
|
161
|
+
package build.
|
|
162
|
+
|
|
163
|
+
### Fixed
|
|
164
|
+
|
|
165
|
+
- **Audit history models now match the JSON the audit endpoints actually return.**
|
|
166
|
+
`IServicesHistoryList` and `IOperationsHistoryList` declared an `ID` property, and
|
|
167
|
+
`IOperationsHistoryList` declared `entityId`. Neither key is ever sent. The audit endpoints
|
|
168
|
+
(`POST /{controller}/{entityID}/Audit` and `.../Audit/{serviceHistoryID}`) serialize under
|
|
169
|
+
ASP.NET Core's default camelCase policy, which lowercases only a *leading* run of capitals — so the
|
|
170
|
+
backend's `ID` goes out as `id` and its `EntityID` goes out as `entityID`. Typing a row against
|
|
171
|
+
`.ID` or `.entityId` therefore compiled fine and read `undefined` at run time. See
|
|
172
|
+
**⚠ Breaking Changes / Migration** below.
|
|
173
|
+
|
|
174
|
+
This was also a latent trap for anything supplying its own audit rows: the grid resolves row
|
|
175
|
+
identity through `compareProperty`, which is `'id'`, so a row carrying only `ID` could never be
|
|
176
|
+
selected — clicking a service entry left the operations grid empty. Rows shaped like the real
|
|
177
|
+
payload work unchanged; `@shared`'s own runtime behaviour is not affected by this release.
|
|
178
|
+
|
|
179
|
+
- **The top bar's notifications bell is no longer pushed off-centre by its unread badge.** The badge
|
|
180
|
+
is an absolutely-positioned overlay, so it adds no width — but it is still a DOM sibling after the
|
|
181
|
+
bell icon, which defeated the `.btn i:not(:last-child)` margin guard in the global button styles.
|
|
182
|
+
That left 8px of dead space to the icon's right and widened the button from 38px to 46px, but only
|
|
183
|
+
while an unread count was showing, so it came and went with the count. The margin is now cleared
|
|
184
|
+
for that button specifically; the shared guard, which is correct for icon-plus-label buttons, is
|
|
185
|
+
unchanged.
|
|
186
|
+
|
|
187
|
+
- **Lint: `shared-` component/directive selector prefix is now accepted.**
|
|
188
|
+
`libs/shared/eslint.config.mjs` still carried the scaffolded `prefix: 'lib'`, so the library's own
|
|
189
|
+
`shared-`-prefixed components (`shared-main-layout`, `shared-login-layout`) failed
|
|
190
|
+
`@angular-eslint/component-selector`. Both selector rules now accept `['lib', 'shared']`.
|
|
191
|
+
|
|
192
|
+
### ⚠ Breaking Changes / Migration
|
|
193
|
+
|
|
194
|
+
Two properties on the audit history models were renamed to the keys the backend actually sends. Both
|
|
195
|
+
models are exported from the package root, so anything typed against the old names will now fail to
|
|
196
|
+
compile. Nothing in `@shared` changes behaviour at run time — the fix is to the declared types.
|
|
197
|
+
|
|
198
|
+
| Model | Before | After |
|
|
199
|
+
|-------|--------|-------|
|
|
200
|
+
| `IServicesHistoryList` | `ID: number` | `id: number` |
|
|
201
|
+
| `IOperationsHistoryList` | `ID: number` | `id: number` |
|
|
202
|
+
| `IOperationsHistoryList` | `entityId?: number` | `entityID?: number` |
|
|
203
|
+
|
|
204
|
+
To upgrade:
|
|
205
|
+
|
|
206
|
+
1. Rename `.ID` to `.id` wherever you read or construct an `IServicesHistoryList` or
|
|
207
|
+
`IOperationsHistoryList`. If the compiler now reports the property as missing, that code was
|
|
208
|
+
reading `undefined` before — it never matched the payload.
|
|
209
|
+
2. Rename `.entityId` to `.entityID` on `IOperationsHistoryList`. The capitals are deliberate and
|
|
210
|
+
match the backend's `EntityID`; do not "correct" them back.
|
|
211
|
+
3. If you supply audit rows yourself — a test double, a Storybook mock, a hand-rolled
|
|
212
|
+
`ServicesHistoryService` / `OperationsHistoryService` — emit `id`, not `ID`. Rows keyed only on
|
|
213
|
+
`ID` were never selectable, so a service row's selection could not reach the operations grid.
|
|
214
|
+
4. If you worked around that by overriding `compareProperty` to `'ID'` in a `ServicesHistoryDataset`
|
|
215
|
+
or `OperationsHistoryDataset` subclass, remove the override. The inherited `'id'` is now correct.
|
|
216
|
+
|
|
26
217
|
## [1.2.0] - 2026-07-28
|
|
27
218
|
|
|
28
219
|
### Added
|
|
@@ -109,7 +300,9 @@ URL) in `AppConfig`, and implement a hub that pushes the notification list to cl
|
|
|
109
300
|
available via [GitHub Releases](https://github.com/RicardoZambon/ZLibraries/releases) and the
|
|
110
301
|
`shared-v*` tags.
|
|
111
302
|
|
|
112
|
-
[Unreleased]: https://github.com/RicardoZambon/ZLibraries/compare/shared-
|
|
303
|
+
[Unreleased]: https://github.com/RicardoZambon/ZLibraries/compare/shared-v2.1.0...HEAD
|
|
304
|
+
[2.1.0]: https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v2.1.0
|
|
305
|
+
[2.0.0]: https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v2.0.0
|
|
113
306
|
[1.2.0]: https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v1.2.0
|
|
114
307
|
[1.1.0]: https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v1.1.0
|
|
115
308
|
[1.0.2]: https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v1.0.2
|