@arsedizioni/ars-utils 22.0.34 → 22.0.35

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.
@@ -1,10 +1,10 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, computed, inject, DestroyRef, Service, Injectable } from '@angular/core';
2
+ import { signal, computed, inject, Service, DestroyRef, Injector, Injectable } from '@angular/core';
3
3
  import { HttpClient, HttpHeaders, HttpRequest, HttpErrorResponse } from '@angular/common/http';
4
- import { BroadcastService, SplashService, SystemUtils } from '@arsedizioni/ars-utils/core';
5
- import { EMPTY, throwError, of, catchError as catchError$1 } from 'rxjs';
6
- import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
+ import { BroadcastService, SystemUtils, SplashService } from '@arsedizioni/ars-utils/core';
5
+ import { throwError, of, EMPTY, catchError as catchError$1 } from 'rxjs';
7
6
  import { catchError, map, finalize } from 'rxjs/operators';
7
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
8
8
 
9
9
  const ClipperMessages = {
10
10
  /**
@@ -2191,486 +2191,119 @@ const NotesColors = [{ name: "Verde", value: "#2E8B74" },
2191
2191
  { name: "Indaco", value: "#5B6EBF" }
2192
2192
  ];
2193
2193
 
2194
- class ClipperService {
2194
+ /**
2195
+ * Document search, references, export and metadata, plus the dashboard counters,
2196
+ * taxonomy/topics/tags lookups, the working-documents "bag" and saved searches.
2197
+ */
2198
+ class ClipperDocumentsService {
2195
2199
  constructor() {
2196
2200
  this.httpClient = inject(HttpClient);
2197
- this.destroyRef = inject(DestroyRef);
2198
2201
  this.broadcastService = inject(BroadcastService);
2199
- this.splashService = inject(SplashService);
2200
- this.broadcastInitialized = false;
2201
- this._serviceUri = '';
2202
- this._flags = ClipperServiceFlags.None;
2203
- this._loggedIn = signal(sessionStorage.getItem("clipper_oauth_token") !== null, /* @ts-ignore */
2204
- ...(ngDevMode ? [{ debugName: "_loggedIn" }] : /* istanbul ignore next */ []));
2205
- this.loggedIn = this._loggedIn.asReadonly();
2206
- this.loggingIn = signal(false, /* @ts-ignore */
2207
- ...(ngDevMode ? [{ debugName: "loggingIn" }] : /* istanbul ignore next */ []));
2208
- this.snapshot = signal(undefined, /* @ts-ignore */
2209
- ...(ngDevMode ? [{ debugName: "snapshot" }] : /* istanbul ignore next */ []));
2210
- this.supportsRS = signal(false, /* @ts-ignore */
2211
- ...(ngDevMode ? [{ debugName: "supportsRS" }] : /* istanbul ignore next */ []));
2212
- this.referencesSnapshot = signal(undefined, /* @ts-ignore */
2213
- ...(ngDevMode ? [{ debugName: "referencesSnapshot" }] : /* istanbul ignore next */ []));
2214
- this.dashboard = new ClipperDashboard();
2215
- this.bag = signal([], /* @ts-ignore */
2216
- ...(ngDevMode ? [{ debugName: "bag" }] : /* istanbul ignore next */ []));
2217
- this.bagTotal = computed(() => this.bag().length, /* @ts-ignore */
2218
- ...(ngDevMode ? [{ debugName: "bagTotal" }] : /* istanbul ignore next */ []));
2219
- this.visible = signal(false, /* @ts-ignore */
2220
- ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
2221
- this._teams = [];
2222
- this.availableChannels = signal([], /* @ts-ignore */
2223
- ...(ngDevMode ? [{ debugName: "availableChannels" }] : /* istanbul ignore next */ []));
2224
- this.activeChannels = computed(() => {
2225
- return this.availableChannels()?.filter(x => !x.suspended && !x.disabled && x.active === true);
2226
- }, /* @ts-ignore */
2227
- ...(ngDevMode ? [{ debugName: "activeChannels" }] : /* istanbul ignore next */ []));
2228
- this.allowTags = signal(false, /* @ts-ignore */
2229
- ...(ngDevMode ? [{ debugName: "allowTags" }] : /* istanbul ignore next */ []));
2230
- this.pendingNotes = signal(undefined, /* @ts-ignore */
2231
- ...(ngDevMode ? [{ debugName: "pendingNotes" }] : /* istanbul ignore next */ []));
2232
- }
2233
- get appUri() {
2234
- return this._appUri;
2202
+ this.core = inject(ClipperCoreService);
2235
2203
  }
2236
- get serviceUri() {
2237
- return this._serviceUri;
2204
+ /////
2205
+ // DOCUMENTS
2206
+ /////
2207
+ /**
2208
+ * Queries documents matching the given search parameters.
2209
+ * @param params - The document search parameters.
2210
+ * @returns An observable emitting the API result wrapping the search result.
2211
+ */
2212
+ query(params) {
2213
+ return this.httpClient.post(this.core.serviceUri + '/documents', params);
2238
2214
  }
2239
- get flags() {
2240
- return this._flags;
2215
+ /**
2216
+ * Retrieves the facets for a document query.
2217
+ * @param params - The document search parameters.
2218
+ * @returns An observable emitting the API result wrapping the search facets.
2219
+ */
2220
+ queryFacets(params) {
2221
+ return this.httpClient.post(this.core.serviceUri + '/documents/facets', params);
2241
2222
  }
2242
- get loginInfo() {
2243
- if (!this._loginInfo) {
2244
- const loginInfo = localStorage.getItem('clipper_context');
2245
- if (loginInfo) {
2246
- try {
2247
- this._loginInfo = JSON.parse(loginInfo);
2248
- }
2249
- catch { }
2250
- }
2251
- }
2252
- return this._loginInfo;
2223
+ /**
2224
+ * Updates the state of one or more documents.
2225
+ * @param params - The document state update parameters.
2226
+ * @returns An observable emitting the API result wrapping the number of updated documents.
2227
+ */
2228
+ updateState(params) {
2229
+ return this.httpClient.post(this.core.serviceUri + '/documents/state/update', params);
2253
2230
  }
2254
- get teams() {
2255
- return this._teams;
2231
+ /**
2232
+ * Exports a single document in PDF format.
2233
+ * @param id - The ID of the document to export.
2234
+ * @returns An observable emitting the PDF binary content as a blob.
2235
+ */
2236
+ exportPdf(id) {
2237
+ return this.httpClient.get(this.core.serviceUri + '/documents/export/' + id, { responseType: 'blob' });
2256
2238
  }
2257
2239
  /**
2258
- * Initialises the service with the API base URI, optional app URI, and feature flags.
2259
- * @param serviceUri - The base URI of the Clipper REST API.
2260
- * @param appUri - Optional URI of the Clipper web application (used to build document links).
2261
- * @param flags - Feature flags that control service behaviour. Defaults to `ClipperServiceFlags.None`.
2240
+ * Exports a document list (query or selected items), or exports deadlines as ICS.
2241
+ * @param params - The export parameters.
2242
+ * @returns An observable emitting the exported content as a blob.
2262
2243
  */
2263
- initialize(serviceUri, appUri, flags = ClipperServiceFlags.None) {
2264
- // Create unique client and machine id
2265
- if (!sessionStorage.getItem('clipper_client_id')) {
2266
- sessionStorage.setItem('clipper_client_id', (flags && ClipperServiceFlags.Embedded) > 0
2267
- ? 'embedded'
2268
- : SystemUtils.generateUUID());
2269
- }
2270
- // Initialize
2271
- this._serviceUri = serviceUri;
2272
- this._appUri = appUri;
2273
- this._flags = flags;
2274
- // React to message broadcasting
2275
- if (!this.broadcastInitialized) {
2276
- this.broadcastInitialized = true;
2277
- this.broadcastService.getMessage()
2278
- .pipe(takeUntilDestroyed(this.destroyRef))
2279
- .subscribe(message => {
2280
- if (message.id === ClipperMessages.LOGIN_CHANGED) {
2281
- this.login(undefined, undefined, true, message.data?.oauth ?? undefined, message.data?.oauthAccessToken ?? undefined).subscribe({
2282
- next: r => {
2283
- if (!r.success) {
2284
- if ((this.flags & ClipperServiceFlags.DisplayConnectionStateMessages) > 0) {
2285
- this.broadcastService.sendMessage(ClipperMessages.ERROR, { message: "Le credenziali di accesso sono cambiate o non sono più valide. Esegui un nuovo accesso." });
2286
- }
2287
- this.broadcastService.sendMessage(ClipperMessages.LOGIN_FAILED);
2288
- }
2289
- else {
2290
- if ((this.flags & ClipperServiceFlags.DisplayConnectionStateMessages) > 0) {
2291
- this.broadcastService.sendMessage(ClipperMessages.SUCCESS_TOAST, { message: "Connesso a Clipper", icon: 'power', duration: 1500 });
2292
- }
2293
- // Load bag
2294
- this.loadBag();
2295
- }
2296
- },
2297
- error: () => { console.error("Clipper non disponibile."); } // Avoid unwanted errors on client
2298
- });
2299
- }
2300
- else if (message.id === ClipperMessages.LOGOUT) {
2301
- if (this.loggedIn()) {
2302
- this.logout().subscribe(r => {
2303
- if (!r.success) {
2304
- if (r.message) {
2305
- this.broadcastService.sendMessage(ClipperMessages.ERROR, { message: "<p>" + r.message + "</p><br><br><hr><p class='small'><i>Per eliminare la configurazione di Clipper accedere a:<br><b>menu > personalizza > collegamenti</b></i></p>" });
2306
- }
2307
- }
2308
- else {
2309
- if ((this.flags & ClipperServiceFlags.DisplayConnectionStateMessages) > 0) {
2310
- this.broadcastService.sendMessage(ClipperMessages.SUCCESS_TOAST, { message: "Disconnesso da Clipper", icon: 'power_off', duration: 1500 });
2311
- }
2312
- // Empty bag
2313
- this.bag.set([]);
2314
- }
2315
- });
2316
- }
2317
- else {
2318
- this.clear();
2319
- }
2320
- }
2321
- });
2322
- }
2323
- // Eveluate current session storage in case of page refresh (F5)
2324
- if (this.loggedIn()) {
2325
- // Auto login
2326
- this.loggingIn.set(false);
2327
- // Initialize channels
2328
- this.initializeChannels();
2329
- // Notify
2330
- this.broadcastService.sendMessage(ClipperMessages.LOGIN_COMPLETED);
2331
- }
2244
+ export(params) {
2245
+ return this.httpClient.post(this.core.serviceUri + '/documents/export', params, { responseType: 'blob' });
2332
2246
  }
2333
2247
  /**
2334
- * Ping
2335
- */
2336
- ping() {
2337
- this.httpClient.get(this._serviceUri + '/ping?nocache=' + SystemUtils.generateUUID())
2338
- .pipe(catchError(() => { return EMPTY; }))
2339
- .subscribe();
2248
+ * Sends document links by email.
2249
+ * @param params - The send-by-email parameters including recipients and documents.
2250
+ * @returns An observable emitting the API result wrapping the number of sent items.
2251
+ */
2252
+ sendTo(params) {
2253
+ return this.httpClient.post(this.core.serviceUri + '/documents/send', params);
2340
2254
  }
2341
2255
  /**
2342
- * Persists the current login context to `localStorage`.
2256
+ * Sends an email notification about a document note update to the specified recipients.
2257
+ * @param params - The notification parameters including recipients and note details.
2258
+ * @returns An observable emitting the API result wrapping the note tracking update.
2343
2259
  */
2344
- storeContext() {
2345
- localStorage.setItem('clipper_context', JSON.stringify(this._loginInfo));
2260
+ notifyNoteTo(params) {
2261
+ return this.httpClient.post(this.core.serviceUri + '/documents/notes/notify', params);
2346
2262
  }
2347
2263
  /**
2348
- * Updates the stored login context with the values from a fresh login result.
2349
- * @param result - The login result containing the new user context and channel settings.
2264
+ * Retrieves the full document report page.
2265
+ * @param id - The ID of the document.
2266
+ * @returns An observable emitting the report content as a blob.
2350
2267
  */
2351
- updateContext(result) {
2352
- if (!this._loginInfo) {
2353
- this._loginInfo = { context: undefined };
2354
- }
2355
- this._loginInfo.context = result.context;
2356
- this._loginInfo.channels = result.settings;
2357
- this.storeContext();
2268
+ report(id) {
2269
+ return this.httpClient.get(this.core.serviceUri + '/documents/report/' + id, { responseType: 'blob' });
2358
2270
  }
2359
2271
  /**
2360
- * Attempts an automatic login using the credentials currently stored in session storage.
2361
- * @param onSuccess - Optional callback invoked with the login result value on success.
2272
+ * Gets the comment associated with a document.
2273
+ * @param id - The ID of the document.
2274
+ * @returns An observable emitting the API result wrapping the comment text.
2362
2275
  */
2363
- autoLogin(onSuccess) {
2364
- this.login(undefined, undefined, true)
2365
- .subscribe({
2366
- next: r => {
2367
- if (!r.success) {
2368
- this.broadcastService.sendMessage(ClipperMessages.ERROR, { message: r.message });
2369
- }
2370
- else {
2371
- if (!r.value.requiresMfa) {
2372
- this.broadcastService.sendMessage(ClipperMessages.SUCCESS_TOAST, { message: "Connesso a Clipper", icon: 'power', duration: 1500 });
2373
- }
2374
- if (onSuccess) {
2375
- onSuccess(r.value);
2376
- }
2377
- }
2378
- },
2379
- error: () => { this.broadcastService.sendMessage(ClipperMessages.ERROR, { message: "Clipper non disponibile." }); }
2380
- });
2276
+ comment(id) {
2277
+ return this.httpClient.get(this.core.serviceUri + '/documents/comment/' + id);
2381
2278
  }
2382
2279
  /**
2383
- * Performs an automatic logout and clears the stored session.
2384
- * @param onSuccess - Optional callback invoked after a successful logout.
2280
+ * Gets the info for a document.
2281
+ * @param id - The ID of the document.
2282
+ * @returns An observable emitting the API result wrapping the document info.
2385
2283
  */
2386
- autoLogout(onSuccess) {
2387
- this.logout()
2388
- .subscribe({
2389
- next: r => {
2390
- if (!r.success) {
2391
- if (r.message) {
2392
- this.broadcastService.sendMessage(ClipperMessages.ERROR, { message: r.message });
2393
- }
2394
- this.broadcastService.sendMessage(ClipperMessages.LOGIN_CHANGED);
2395
- }
2396
- else {
2397
- this.broadcastService.sendMessage(ClipperMessages.SUCCESS_TOAST, { message: "Disconnesso da Clipper", icon: 'power_off', duration: 1500 });
2398
- }
2399
- },
2400
- error: () => { },
2401
- complete: () => {
2402
- if (onSuccess) {
2403
- onSuccess();
2404
- }
2405
- }
2406
- });
2284
+ info(id) {
2285
+ return this.httpClient.get(this.core.serviceUri + '/documents/info/' + id);
2407
2286
  }
2408
2287
  /**
2409
- * Authenticates the user against the Clipper API, supporting both credential-based and
2410
- * OAuth2 login flows.
2411
- * @param email - The user's email address (credential login only).
2412
- * @param password - The user's password (credential login only).
2413
- * @param remember - When `true`, the session is remembered across browser restarts.
2414
- * @param oauth - The OAuth2 provider type, when authenticating via SSO.
2415
- * @param oauthAccessToken - The OAuth2 bearer token. Defaults to the value stored in session storage.
2288
+ * Gets the structure (index) of a document.
2289
+ * @param id - The ID of the document.
2290
+ * @returns An observable emitting the API result wrapping the document structure.
2416
2291
  */
2417
- login(email, password, remember, oauth, oauthAccessToken = sessionStorage.getItem("clipper_oauth_token") ?? undefined) {
2418
- this.splashService.setMessage('Accesso in corso...');
2419
- return this.httpClient
2420
- .post(this._serviceUri + '/login2', {
2421
- user: oauth ? null : email,
2422
- password: oauth ? null : password,
2423
- remember: remember,
2424
- oauth: oauth
2425
- }, {
2426
- headers: !oauth || !oauthAccessToken
2427
- ? new HttpHeaders()
2428
- : new HttpHeaders()
2429
- .set("Authorization", 'Bearer ' + oauthAccessToken)
2430
- })
2431
- .pipe(catchError(err => {
2432
- return throwError(() => err);
2433
- }), map((r) => {
2434
- if (r.success) {
2435
- if (!this._loginInfo) {
2436
- this._loginInfo = { context: undefined };
2437
- }
2438
- this._loginInfo.oauth = oauth;
2439
- this._loginInfo.remember = remember;
2440
- if (!oauth && r.value.requiresMfa) {
2441
- // Notify login is pending
2442
- this.broadcastService.sendMessage(ClipperMessages.LOGIN_PENDING);
2443
- }
2444
- else {
2445
- // Complete login
2446
- this.completeLogin(r.value);
2447
- }
2448
- }
2449
- return r;
2450
- }));
2292
+ index(id) {
2293
+ return this.httpClient.get(this.core.serviceUri + '/documents/structure/' + id);
2451
2294
  }
2452
2295
  /**
2453
- * Finalises the login flow by updating the stored context, setting the logged-in signal,
2454
- * initialising channels, and broadcasting `LOGIN_COMPLETED`.
2455
- * @param result - The login result returned by the API.
2296
+ * Gets the last-update metadata for a document.
2297
+ * @param id - The ID of the document.
2298
+ * @returns An observable emitting the API result wrapping the last update string and its notes.
2456
2299
  */
2457
- completeLogin(result) {
2458
- // Update context info
2459
- this.updateContext(result);
2460
- this._loggedIn.set(!result.context?.isTemporary);
2461
- this.loggingIn.set(false);
2462
- // Initialize channels
2463
- this.initializeChannels();
2464
- // Notify
2465
- this.broadcastService.sendMessage(ClipperMessages.LOGIN_COMPLETED);
2300
+ metadata(id) {
2301
+ return this.httpClient.get(this.core.serviceUri + '/documents/metadata/' + id);
2466
2302
  }
2467
2303
  /**
2468
- * Submits the MFA confirmation code to complete a two-factor login flow.
2469
- * @param code - The one-time confirmation code provided to the user.
2470
- */
2471
- confirmIdentity(code) {
2472
- return this.httpClient
2473
- .post(this._serviceUri + '/login/confirm/' + code, {})
2474
- .pipe(catchError((err) => {
2475
- return throwError(() => err);
2476
- }), map((r) => {
2477
- if (r.success) {
2478
- this.completeLogin(r.value);
2479
- }
2480
- return r;
2481
- }));
2482
- }
2483
- /**
2484
- * Logs the user out and clears the current session.
2485
- * @param forget - When `true`, all stored user credentials are also removed.
2486
- */
2487
- logout(forget = false) {
2488
- return this.httpClient.post(this._serviceUri + '/logout/?forget=' + forget, {})
2489
- .pipe(finalize(() => {
2490
- this.clear(true);
2491
- // Clean up
2492
- localStorage.removeItem('clipper_context');
2493
- }), catchError((_e) => {
2494
- return of([]);
2495
- }));
2496
- }
2497
- /**
2498
- * Resets the login state, clears the stored login info, and broadcasts `LOGOUT_COMPLETED`.
2499
- */
2500
- reset() {
2501
- // Clear login info
2502
- this._loginInfo = undefined;
2503
- // Logged out
2504
- this._loggedIn.set(false);
2505
- // Reset channels
2506
- this.availableChannels.set([]);
2507
- // Notify
2508
- this.broadcastService.sendMessage(ClipperMessages.LOGOUT_COMPLETED);
2509
- }
2510
- /**
2511
- * Clears all session-storage authentication keys and resets the login state.
2512
- * @param clearOAuthToken - When `true`, the OAuth bearer token is also removed.
2513
- */
2514
- clear(clearOAuthToken = false) {
2515
- // Clear local storage
2516
- sessionStorage.removeItem('clipper_auth');
2517
- sessionStorage.removeItem('clipper_oauth');
2518
- if (clearOAuthToken) {
2519
- sessionStorage.removeItem('clipper_oauth_token');
2520
- }
2521
- // Reset login
2522
- this.reset();
2523
- }
2524
- /**
2525
- * Applies a new dashboard result: updates channel settings, dashboard counters, and
2526
- * re-initialises the available-channels signal.
2527
- * @param value - The dashboard result containing updated channel and counter data.
2528
- */
2529
- setChannelsState(value) {
2530
- // Update channels
2531
- if (this._loginInfo) {
2532
- this._loginInfo.channels = value.channels ?? [];
2533
- }
2534
- // Update dashboard
2535
- this.dashboard.documentUpdates = value.documentUpdates;
2536
- this.dashboard.expiredDeadlines = value.expiredDeadlines;
2537
- this.dashboard.expiringDeadlines = value.expiringDeadlines;
2538
- this.dashboard.isTrial = value.isTrial;
2539
- this.dashboard.items.set(value.items ?? []);
2540
- this.broadcastService.sendMessage(ClipperMessages.COMMAND_DASHBOARD_UPDATED);
2541
- this.storeContext();
2542
- this.initializeChannels();
2543
- }
2544
- /**
2545
- * Replaces the stored channel settings and re-initialises the available-channels signal.
2546
- * @param channels - The new channel settings to store and activate.
2547
- */
2548
- setChannels(channels) {
2549
- if (this._loginInfo) {
2550
- this._loginInfo.channels = channels;
2551
- this.storeContext();
2552
- this.initializeChannels();
2553
- }
2554
- }
2555
- /**
2556
- * Rebuilds the `availableChannels` signal from the current login context.
2557
- */
2558
- initializeChannels() {
2559
- if (this.loginInfo) {
2560
- const channels = [];
2561
- this.loginInfo.channels?.forEach(n => {
2562
- const channelSubscription = this.loginInfo?.context?.channels?.find(x => x.channel === n.channelId);
2563
- n.isSuspended = channelSubscription?.isSuspended === true;
2564
- const channel = ClipperChannels.find(x => x.value === n.channelId);
2565
- if (channel) {
2566
- channel.disabled = !n.isActive;
2567
- channel.suspended = n.isSuspended === true;
2568
- channel.active = n.isActive === true && n.isEnabled === true;
2569
- channels.push(channel);
2570
- }
2571
- });
2572
- this.availableChannels.set(channels);
2573
- }
2574
- }
2575
- /**
2576
- * Toggles the active state of available channels based on the supplied selection list.
2577
- * @param values - The selected channel items; channels absent from this list are deactivated.
2578
- */
2579
- updateChannels(values) {
2580
- this.availableChannels().forEach(channel => {
2581
- if (!channel.disabled) {
2582
- channel.active = values?.findIndex(x => x.value === channel.value) !== -1;
2583
- }
2584
- });
2585
- }
2586
- /**
2587
- * Requests a new one-time password for the given repository.
2588
- * @param id - The repository ID for which the OTP should be generated.
2589
- */
2590
- newOTP(id) {
2591
- return this.httpClient.get(this._serviceUri + '/otp/new/?id=' + id);
2592
- }
2593
- /**
2594
- * Load Ars events calendar
2595
- */
2596
- events(params) {
2597
- return this.httpClient.post(this._serviceUri + '/ars/events', params);
2598
- }
2599
- /**
2600
- * Query documents
2601
- */
2602
- query(params) {
2603
- return this.httpClient.post(this._serviceUri + '/documents', params);
2604
- }
2605
- /**
2606
- * Get facets for a query
2607
- */
2608
- queryFacets(params) {
2609
- return this.httpClient.post(this._serviceUri + '/documents/facets', params);
2610
- }
2611
- /**
2612
- * Update document state
2613
- */
2614
- updateState(params) {
2615
- return this.httpClient.post(this._serviceUri + '/documents/state/update', params);
2616
- }
2617
- /**
2618
- * Export a document in pdf format
2619
- */
2620
- exportPdf(id) {
2621
- return this.httpClient.get(this._serviceUri + '/documents/export/' + id, { responseType: 'blob' });
2622
- }
2623
- /**
2624
- * Export document list (query or selected items) or export deadlines as ics
2625
- */
2626
- export(params) {
2627
- return this.httpClient.post(this._serviceUri + '/documents/export', params, { responseType: 'blob' });
2628
- }
2629
- /**
2630
- * Send documents link by email
2631
- */
2632
- sendTo(params) {
2633
- return this.httpClient.post(this._serviceUri + '/documents/send', params);
2634
- }
2635
- /**
2636
- * Sends an email notification about a document note update to the specified recipients.
2637
- * @param params - The notification parameters including recipients and note details.
2638
- */
2639
- notifyNoteTo(params) {
2640
- return this.httpClient.post(this._serviceUri + '/documents/notes/notify', params);
2641
- }
2642
- /**
2643
- * Display a page the full document report
2644
- */
2645
- report(id) {
2646
- return this.httpClient.get(this._serviceUri + '/documents/report/' + id, { responseType: 'blob' });
2647
- }
2648
- /**
2649
- * Get document comment
2650
- */
2651
- comment(id) {
2652
- return this.httpClient.get(this._serviceUri + '/documents/comment/' + id);
2653
- }
2654
- /**
2655
- * Get document info
2656
- */
2657
- info(id) {
2658
- return this.httpClient.get(this._serviceUri + '/documents/info/' + id);
2659
- }
2660
- /**
2661
- * Get document structure
2662
- */
2663
- index(id) {
2664
- return this.httpClient.get(this._serviceUri + '/documents/structure/' + id);
2665
- }
2666
- /**
2667
- * Get document last update
2668
- */
2669
- metadata(id) {
2670
- return this.httpClient.get(this._serviceUri + '/documents/metadata/' + id);
2671
- }
2672
- /**
2673
- * Query document references
2304
+ * Queries document references, changes or jurisprudence depending on the requested mode.
2305
+ * @param params - The references search parameters; `mode` defaults to `ReferencesIn`.
2306
+ * @returns An observable emitting the API result wrapping the search result, or `null` for an unsupported mode.
2674
2307
  */
2675
2308
  references(params) {
2676
2309
  let mode = params.mode;
@@ -2680,18 +2313,20 @@ class ClipperService {
2680
2313
  case ClipperQueryReferencesMode.ReferencesIn:
2681
2314
  case ClipperQueryReferencesMode.ReferencesOut:
2682
2315
  params.mode = mode;
2683
- return this.httpClient.post(this._serviceUri + '/documents/references', params);
2316
+ return this.httpClient.post(this.core.serviceUri + '/documents/references', params);
2684
2317
  case ClipperQueryReferencesMode.ChangesIn:
2685
2318
  case ClipperQueryReferencesMode.ChangesOut:
2686
- return this.httpClient.post(this._serviceUri + '/documents/changes', params);
2319
+ return this.httpClient.post(this.core.serviceUri + '/documents/changes', params);
2687
2320
  case ClipperQueryReferencesMode.Juris:
2688
- return this.httpClient.post(this._serviceUri + '/documents/juris', params);
2321
+ return this.httpClient.post(this.core.serviceUri + '/documents/juris', params);
2689
2322
  default:
2690
2323
  return null;
2691
2324
  }
2692
2325
  }
2693
2326
  /**
2694
- * Get facets for a document references
2327
+ * Retrieves the facets for a document references query.
2328
+ * @param params - The references search parameters; `mode` defaults to `ReferencesIn`.
2329
+ * @returns An observable emitting the API result wrapping the search facets, or `null` for an unsupported mode.
2695
2330
  */
2696
2331
  referencesFacets(params) {
2697
2332
  let mode = params.mode ?? ClipperQueryReferencesMode.ReferencesIn;
@@ -2699,72 +2334,83 @@ class ClipperService {
2699
2334
  case ClipperQueryReferencesMode.ReferencesIn:
2700
2335
  case ClipperQueryReferencesMode.ReferencesOut:
2701
2336
  params.mode = mode;
2702
- return this.httpClient.post(this._serviceUri + '/documents/references/facets', params);
2337
+ return this.httpClient.post(this.core.serviceUri + '/documents/references/facets', params);
2703
2338
  case ClipperQueryReferencesMode.ChangesIn:
2704
2339
  case ClipperQueryReferencesMode.ChangesOut:
2705
- return this.httpClient.post(this._serviceUri + '/documents/changes/facets', params);
2340
+ return this.httpClient.post(this.core.serviceUri + '/documents/changes/facets', params);
2706
2341
  case ClipperQueryReferencesMode.Juris:
2707
- return this.httpClient.post(this._serviceUri + '/documents/juris/facets', params);
2342
+ return this.httpClient.post(this.core.serviceUri + '/documents/juris/facets', params);
2708
2343
  default: return null;
2709
2344
  }
2710
2345
  }
2711
2346
  /**
2712
- * Wrap document rendering to allow token refresh
2347
+ * Wraps document rendering to allow token refresh.
2348
+ * @returns An observable emitting the API result wrapping a boolean readiness flag.
2713
2349
  */
2714
2350
  preRender() {
2715
- return this.httpClient.get(this._serviceUri + '/documents/pre-render?nocache=' + SystemUtils.generateUUID());
2351
+ return this.httpClient.get(this.core.serviceUri + '/documents/pre-render?nocache=' + SystemUtils.generateUUID());
2716
2352
  }
2717
2353
  /**
2718
- * Get document juris articles
2354
+ * Gets the jurisprudence articles for a document query.
2355
+ * @param params - The document search parameters.
2356
+ * @returns An observable emitting the API result wrapping the search result.
2719
2357
  */
2720
2358
  jurisArticles(params) {
2721
- return this.httpClient.post(this._serviceUri + '/documents/juris/articles', params);
2359
+ return this.httpClient.post(this.core.serviceUri + '/documents/juris/articles', params);
2722
2360
  }
2723
2361
  /**
2724
- * Get deadlines snapshot based on the deadlines
2725
- */
2362
+ * Gets a deadlines snapshot based on the supplied deadlines.
2363
+ * @param params - The calendar search parameters.
2364
+ * @returns An observable emitting the API result wrapping the calendar snapshot result.
2365
+ */
2726
2366
  deadlinesSnapshot(params) {
2727
- return this.httpClient.post(this._serviceUri + '/documents/calendar/snapshot', params);
2367
+ return this.httpClient.post(this.core.serviceUri + '/documents/calendar/snapshot', params);
2728
2368
  }
2729
2369
  /**
2730
- * Retrieve the taxonomy
2370
+ * Retrieves the taxonomy.
2371
+ * @param params - Optional taxonomy parameters. Defaults to `{ model: 0, countItems: false }`.
2372
+ * @returns An observable emitting the API result wrapping the taxonomy folder tree.
2731
2373
  */
2732
2374
  getTaxonomy(params) {
2733
- return this.httpClient.post(this._serviceUri + '/taxonomy', params ?? { model: 0, countItems: false });
2375
+ return this.httpClient.post(this.core.serviceUri + '/taxonomy', params ?? { model: 0, countItems: false });
2734
2376
  }
2735
2377
  /**
2736
- * Retrieve topics
2378
+ * Retrieves the topics as a flat list.
2379
+ * @returns An observable emitting the API result wrapping the list of topics.
2737
2380
  */
2738
2381
  getTopics() {
2739
- return this.httpClient.get(this._serviceUri + '/topics');
2382
+ return this.httpClient.get(this.core.serviceUri + '/topics');
2740
2383
  }
2741
2384
  /**
2742
- * Retrieve topics as tree
2743
- */
2385
+ * Retrieves the topics as a tree.
2386
+ * @returns An observable emitting the API result wrapping the topics folder tree.
2387
+ */
2744
2388
  getTopicsAsTree() {
2745
- return this.httpClient.get(this._serviceUri + '/topics2');
2389
+ return this.httpClient.get(this.core.serviceUri + '/topics2');
2746
2390
  }
2747
2391
  /**
2748
- * Retrieve tags
2392
+ * Retrieves the tags.
2393
+ * @returns An observable emitting the API result wrapping the list of tags.
2749
2394
  */
2750
2395
  getTags() {
2751
- return this.httpClient.get(this._serviceUri + '/tags');
2396
+ return this.httpClient.get(this.core.serviceUri + '/tags');
2752
2397
  }
2753
2398
  ///
2754
2399
  // DASHBOARD
2755
2400
  ///
2756
2401
  /**
2757
- * Retrieve current dashboard
2758
- */
2402
+ * Retrieves the current dashboard and applies its counters to the shared dashboard state.
2403
+ * @returns The subscription to the dashboard request.
2404
+ */
2759
2405
  loadDashboard() {
2760
2406
  return this.httpClient
2761
- .post(this._serviceUri + '/account/dashboard', { refreshing: true }).subscribe((r) => {
2407
+ .post(this.core.serviceUri + '/account/dashboard', { refreshing: true }).subscribe((r) => {
2762
2408
  if (r.success) {
2763
- this.dashboard.documentUpdates = r.value.documentUpdates;
2764
- this.dashboard.expiredDeadlines = r.value.expiredDeadlines;
2765
- this.dashboard.expiringDeadlines = r.value.expiringDeadlines;
2766
- this.dashboard.isTrial = r.value.isTrial;
2767
- this.dashboard.items.set(r.value.items ?? []);
2409
+ this.core.dashboard.documentUpdates = r.value.documentUpdates;
2410
+ this.core.dashboard.expiredDeadlines = r.value.expiredDeadlines;
2411
+ this.core.dashboard.expiringDeadlines = r.value.expiringDeadlines;
2412
+ this.core.dashboard.isTrial = r.value.isTrial;
2413
+ this.core.dashboard.items.set(r.value.items ?? []);
2768
2414
  this.broadcastService.sendMessage(ClipperMessages.COMMAND_DASHBOARD_UPDATED);
2769
2415
  }
2770
2416
  });
@@ -2772,9 +2418,10 @@ class ClipperService {
2772
2418
  /**
2773
2419
  * Retrieves documents updated in the last 15 days for the given search parameters.
2774
2420
  * @param params - The search parameters to filter results.
2421
+ * @returns An observable emitting the API result wrapping the last-days result.
2775
2422
  */
2776
2423
  last15Days(params) {
2777
- return this.httpClient.post(this._serviceUri + '/account/last-15-days', params);
2424
+ return this.httpClient.post(this.core.serviceUri + '/account/last-15-days', params);
2778
2425
  }
2779
2426
  /**
2780
2427
  * Adjusts the unread-item counter for the given module and broadcasts a dashboard update.
@@ -2785,19 +2432,20 @@ class ClipperService {
2785
2432
  updateUnreadItems(module, model, increment) {
2786
2433
  increment ??= 0;
2787
2434
  if (increment !== 0) {
2788
- this.dashboard.updateUnreadItems(module, model, increment);
2435
+ this.core.dashboard.updateUnreadItems(module, model, increment);
2789
2436
  }
2790
2437
  this.broadcastService.sendMessage(ClipperMessages.COMMAND_DASHBOARD_UPDATED);
2791
2438
  }
2792
2439
  ///
2793
- // BAG - WORKING DOCUMENTS
2440
+ // BAG
2794
2441
  ///
2795
2442
  /**
2796
- * Load working documents
2443
+ * Loads the working documents and populates the shared bag.
2444
+ * @returns The subscription to the working-documents request.
2797
2445
  */
2798
2446
  loadBag() {
2799
2447
  return this.httpClient
2800
- .get(this.serviceUri + '/documents/working')
2448
+ .get(this.core.serviceUri + '/documents/working')
2801
2449
  .subscribe(r => {
2802
2450
  if (!r.success) {
2803
2451
  this.broadcastService.sendMessage(ClipperMessages.ERROR, { message: r.message });
@@ -2819,7 +2467,7 @@ class ClipperService {
2819
2467
  items.push(n);
2820
2468
  }
2821
2469
  });
2822
- this.bag.set(items);
2470
+ this.core.bag.set(items);
2823
2471
  }
2824
2472
  }
2825
2473
  });
@@ -2827,10 +2475,11 @@ class ClipperService {
2827
2475
  /**
2828
2476
  * Adds one or more documents to the working documents bag.
2829
2477
  * @param documentIds - The IDs of the documents to add.
2478
+ * @returns The subscription to the add-to-bag request.
2830
2479
  */
2831
2480
  addToBag(documentIds) {
2832
2481
  return this.httpClient
2833
- .post(this.serviceUri + '/documents/working/add', { documentIds: documentIds })
2482
+ .post(this.core.serviceUri + '/documents/working/add', { documentIds: documentIds })
2834
2483
  .subscribe((r) => {
2835
2484
  if (!r.success) {
2836
2485
  this.broadcastService.sendMessage(ClipperMessages.LOGIN_CHANGED);
@@ -2851,7 +2500,7 @@ class ClipperService {
2851
2500
  }
2852
2501
  });
2853
2502
  if (newItems.length > 0) {
2854
- this.bag.update((values) => [...values, ...newItems]);
2503
+ this.core.bag.update((values) => [...values, ...newItems]);
2855
2504
  }
2856
2505
  this.broadcastService.sendMessage(ClipperMessages.SUCCESS_TOAST, { message: "Operazione completata con successo.", icon: 'check', duration: 1500 });
2857
2506
  }
@@ -2861,18 +2510,19 @@ class ClipperService {
2861
2510
  /**
2862
2511
  * Removes a document from the working documents bag.
2863
2512
  * @param documentId - The ID of the document to remove.
2513
+ * @returns The subscription to the remove-from-bag request.
2864
2514
  */
2865
2515
  removeFromBag(documentId) {
2866
2516
  return this.httpClient
2867
- .post(this.serviceUri + '/documents/working/delete', { documentIds: [documentId] })
2517
+ .post(this.core.serviceUri + '/documents/working/delete', { documentIds: [documentId] })
2868
2518
  .subscribe((r) => {
2869
2519
  if (!r.success) {
2870
2520
  this.broadcastService.sendMessage(ClipperMessages.ERROR, { message: r.message });
2871
2521
  }
2872
2522
  else {
2873
- const p = this.bag().findIndex((n) => n.documentId === documentId);
2523
+ const p = this.core.bag().findIndex((n) => n.documentId === documentId);
2874
2524
  if (p !== -1) {
2875
- this.bag.update((values) => {
2525
+ this.core.bag.update((values) => {
2876
2526
  values.splice(p, 1);
2877
2527
  return [...values];
2878
2528
  });
@@ -2882,128 +2532,671 @@ class ClipperService {
2882
2532
  });
2883
2533
  }
2884
2534
  /**
2885
- * Clear all working documents
2535
+ * Clears all working documents from the bag.
2536
+ * @returns The subscription to the clear-bag request.
2537
+ */
2538
+ clearBag() {
2539
+ return this.httpClient
2540
+ .post(this.core.serviceUri + '/documents/working/delete', { deleteAll: true })
2541
+ .subscribe((r) => {
2542
+ if (!r.success) {
2543
+ this.broadcastService.sendMessage(ClipperMessages.ERROR, { message: r.message });
2544
+ }
2545
+ else {
2546
+ this.core.bag.set([]);
2547
+ this.broadcastService.sendMessage(ClipperMessages.SUCCESS_TOAST, { message: "Operazione completata con successo.", icon: 'check', duration: 1500 });
2548
+ }
2549
+ });
2550
+ }
2551
+ /**
2552
+ * Retrieves the saved searches for the given module.
2553
+ * @param module - The Clipper module whose saved searches to load.
2554
+ * @returns An observable emitting the API result wrapping the list of saved searches.
2555
+ */
2556
+ loadSearches(module) {
2557
+ return this.httpClient
2558
+ .get(this.core.serviceUri + '/documents/searches/?module=' + module);
2559
+ }
2560
+ /**
2561
+ * Persists a user search configuration on the server.
2562
+ * @param params - The search configuration to save.
2563
+ * @returns An observable emitting the API result wrapping the saved search.
2564
+ */
2565
+ saveSearch(params) {
2566
+ return this.httpClient
2567
+ .post(this.core.serviceUri + '/documents/searches/save', params);
2568
+ }
2569
+ /**
2570
+ * Deletes a saved search by its ID.
2571
+ * @param id - The ID of the saved search to remove.
2572
+ * @returns An observable emitting the API result wrapping the number of deleted searches.
2573
+ */
2574
+ deleteSearch(id) {
2575
+ return this.httpClient
2576
+ .post(this.core.serviceUri + '/documents/searches/delete', { id: id });
2577
+ }
2578
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ClipperDocumentsService, deps: [], target: i0.ɵɵFactoryTarget.Service }); }
2579
+ static { this.ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.0.1", ngImport: i0, type: ClipperDocumentsService }); }
2580
+ }
2581
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ClipperDocumentsService, decorators: [{
2582
+ type: Service
2583
+ }] });
2584
+
2585
+ /**
2586
+ * Authentication: credential / OAuth2 login, MFA confirmation, logout and OTP.
2587
+ * Shared login context, channels and the logged-in flag live in `ClipperCoreService`.
2588
+ */
2589
+ class ClipperLoginService {
2590
+ constructor() {
2591
+ this.httpClient = inject(HttpClient);
2592
+ this.broadcastService = inject(BroadcastService);
2593
+ this.splashService = inject(SplashService);
2594
+ this.core = inject(ClipperCoreService);
2595
+ }
2596
+ /**
2597
+ * Attempts an automatic login using the credentials currently stored in session storage.
2598
+ * @param onSuccess - Optional callback invoked with the login result value on success.
2599
+ */
2600
+ autoLogin(onSuccess) {
2601
+ this.login(undefined, undefined, true)
2602
+ .subscribe({
2603
+ next: r => {
2604
+ if (!r.success) {
2605
+ this.broadcastService.sendMessage(ClipperMessages.ERROR, { message: r.message });
2606
+ }
2607
+ else {
2608
+ if (!r.value.requiresMfa) {
2609
+ this.broadcastService.sendMessage(ClipperMessages.SUCCESS_TOAST, { message: "Connesso a Clipper", icon: 'power', duration: 1500 });
2610
+ }
2611
+ if (onSuccess) {
2612
+ onSuccess(r.value);
2613
+ }
2614
+ }
2615
+ },
2616
+ error: () => { this.broadcastService.sendMessage(ClipperMessages.ERROR, { message: "Clipper non disponibile." }); }
2617
+ });
2618
+ }
2619
+ /**
2620
+ * Performs an automatic logout and clears the stored session.
2621
+ * @param onSuccess - Optional callback invoked after a successful logout.
2622
+ */
2623
+ autoLogout(onSuccess) {
2624
+ this.logout()
2625
+ .subscribe({
2626
+ next: r => {
2627
+ if (!r.success) {
2628
+ if (r.message) {
2629
+ this.broadcastService.sendMessage(ClipperMessages.ERROR, { message: r.message });
2630
+ }
2631
+ this.broadcastService.sendMessage(ClipperMessages.LOGIN_CHANGED);
2632
+ }
2633
+ else {
2634
+ this.broadcastService.sendMessage(ClipperMessages.SUCCESS_TOAST, { message: "Disconnesso da Clipper", icon: 'power_off', duration: 1500 });
2635
+ }
2636
+ },
2637
+ error: () => { },
2638
+ complete: () => {
2639
+ if (onSuccess) {
2640
+ onSuccess();
2641
+ }
2642
+ }
2643
+ });
2644
+ }
2645
+ /**
2646
+ * Authenticates the user against the Clipper API, supporting both credential-based and
2647
+ * OAuth2 login flows.
2648
+ * @param email - The user's email address (credential login only).
2649
+ * @param password - The user's password (credential login only).
2650
+ * @param remember - When `true`, the session is remembered across browser restarts.
2651
+ * @param oauth - The OAuth2 provider type, when authenticating via SSO.
2652
+ * @param oauthAccessToken - The OAuth2 bearer token. Defaults to the value stored in session storage.
2653
+ * @returns An observable emitting the API result wrapping the login result.
2654
+ */
2655
+ login(email, password, remember, oauth, oauthAccessToken = sessionStorage.getItem("clipper_oauth_token") ?? undefined) {
2656
+ this.splashService.setMessage('Accesso in corso...');
2657
+ return this.httpClient
2658
+ .post(this.core.serviceUri + '/login2', {
2659
+ user: oauth ? null : email,
2660
+ password: oauth ? null : password,
2661
+ remember: remember,
2662
+ oauth: oauth
2663
+ }, {
2664
+ headers: !oauth || !oauthAccessToken
2665
+ ? new HttpHeaders()
2666
+ : new HttpHeaders()
2667
+ .set("Authorization", 'Bearer ' + oauthAccessToken)
2668
+ })
2669
+ .pipe(catchError(err => {
2670
+ return throwError(() => err);
2671
+ }), map((r) => {
2672
+ if (r.success) {
2673
+ const info = this.core.ensureLoginInfo();
2674
+ info.oauth = oauth;
2675
+ info.remember = remember;
2676
+ if (!oauth && r.value.requiresMfa) {
2677
+ // Notify login is pending
2678
+ this.broadcastService.sendMessage(ClipperMessages.LOGIN_PENDING);
2679
+ }
2680
+ else {
2681
+ // Complete login
2682
+ this.completeLogin(r.value);
2683
+ }
2684
+ }
2685
+ return r;
2686
+ }));
2687
+ }
2688
+ /**
2689
+ * Finalises the login flow by updating the stored context, setting the logged-in signal,
2690
+ * initialising channels, and broadcasting `LOGIN_COMPLETED`.
2691
+ * @param result - The login result returned by the API.
2692
+ */
2693
+ completeLogin(result) {
2694
+ // Update context info
2695
+ this.core.updateContext(result);
2696
+ this.core.setLoggedIn(!result.context?.isTemporary);
2697
+ this.core.loggingIn.set(false);
2698
+ // Initialize channels
2699
+ this.core.initializeChannels();
2700
+ // Notify
2701
+ this.broadcastService.sendMessage(ClipperMessages.LOGIN_COMPLETED);
2702
+ }
2703
+ /**
2704
+ * Submits the MFA confirmation code to complete a two-factor login flow.
2705
+ * @param code - The one-time confirmation code provided to the user.
2706
+ * @returns An observable emitting the API result wrapping the login result.
2707
+ */
2708
+ confirmIdentity(code) {
2709
+ return this.httpClient
2710
+ .post(this.core.serviceUri + '/login/confirm/' + code, {})
2711
+ .pipe(catchError((err) => {
2712
+ return throwError(() => err);
2713
+ }), map((r) => {
2714
+ if (r.success) {
2715
+ this.completeLogin(r.value);
2716
+ }
2717
+ return r;
2718
+ }));
2719
+ }
2720
+ /**
2721
+ * Logs the user out and clears the current session.
2722
+ * @param forget - When `true`, all stored user credentials are also removed.
2723
+ * @returns An observable that completes once the logout request has been processed.
2724
+ */
2725
+ logout(forget = false) {
2726
+ return this.httpClient.post(this.core.serviceUri + '/logout/?forget=' + forget, {})
2727
+ .pipe(finalize(() => {
2728
+ this.core.clear(true);
2729
+ // Clean up
2730
+ localStorage.removeItem('clipper_context');
2731
+ }), catchError((_e) => {
2732
+ return of([]);
2733
+ }));
2734
+ }
2735
+ /**
2736
+ * Requests a new one-time password for the given repository.
2737
+ * @param id - The repository ID for which the OTP should be generated.
2738
+ * @returns An observable emitting the API result wrapping the generated OTP info.
2739
+ */
2740
+ newOTP(id) {
2741
+ return this.httpClient.get(this.core.serviceUri + '/otp/new/?id=' + id);
2742
+ }
2743
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ClipperLoginService, deps: [], target: i0.ɵɵFactoryTarget.Service }); }
2744
+ static { this.ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.0.1", ngImport: i0, type: ClipperLoginService }); }
2745
+ }
2746
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ClipperLoginService, decorators: [{
2747
+ type: Service
2748
+ }] });
2749
+
2750
+ /**
2751
+ * Shared state and bootstrap for the Clipper application.
2752
+ *
2753
+ * Holds every piece of state used by more than one feature service (service URI,
2754
+ * flags, login context, teams, dashboard, working-documents bag, channels and the
2755
+ * various UI signals) plus the low-level helpers that mutate that state.
2756
+ *
2757
+ * The feature services (`ClipperLoginService`, `ClipperDocumentsService`, ...) inject
2758
+ * this service to read shared state and call shared helpers. `ClipperService` (the
2759
+ * barrel/facade) re-exports the state declared here.
2760
+ */
2761
+ class ClipperCoreService {
2762
+ constructor() {
2763
+ this.httpClient = inject(HttpClient);
2764
+ this.destroyRef = inject(DestroyRef);
2765
+ this.broadcastService = inject(BroadcastService);
2766
+ /** Used to lazily resolve feature services inside `initialize`, avoiding circular DI. */
2767
+ this.injector = inject(Injector);
2768
+ this.broadcastInitialized = false;
2769
+ this._serviceUri = '';
2770
+ this._flags = ClipperServiceFlags.None;
2771
+ this._loggedIn = signal(sessionStorage.getItem("clipper_oauth_token") !== null, /* @ts-ignore */
2772
+ ...(ngDevMode ? [{ debugName: "_loggedIn" }] : /* istanbul ignore next */ []));
2773
+ this.loggedIn = this._loggedIn.asReadonly();
2774
+ this.loggingIn = signal(false, /* @ts-ignore */
2775
+ ...(ngDevMode ? [{ debugName: "loggingIn" }] : /* istanbul ignore next */ []));
2776
+ this.snapshot = signal(undefined, /* @ts-ignore */
2777
+ ...(ngDevMode ? [{ debugName: "snapshot" }] : /* istanbul ignore next */ []));
2778
+ this.supportsRS = signal(false, /* @ts-ignore */
2779
+ ...(ngDevMode ? [{ debugName: "supportsRS" }] : /* istanbul ignore next */ []));
2780
+ this.referencesSnapshot = signal(undefined, /* @ts-ignore */
2781
+ ...(ngDevMode ? [{ debugName: "referencesSnapshot" }] : /* istanbul ignore next */ []));
2782
+ this.dashboard = new ClipperDashboard();
2783
+ this.bag = signal([], /* @ts-ignore */
2784
+ ...(ngDevMode ? [{ debugName: "bag" }] : /* istanbul ignore next */ []));
2785
+ this.bagTotal = computed(() => this.bag().length, /* @ts-ignore */
2786
+ ...(ngDevMode ? [{ debugName: "bagTotal" }] : /* istanbul ignore next */ []));
2787
+ this.visible = signal(false, /* @ts-ignore */
2788
+ ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
2789
+ this._teams = [];
2790
+ this.availableChannels = signal([], /* @ts-ignore */
2791
+ ...(ngDevMode ? [{ debugName: "availableChannels" }] : /* istanbul ignore next */ []));
2792
+ this.activeChannels = computed(() => {
2793
+ return this.availableChannels()?.filter(x => !x.suspended && !x.disabled && x.active === true);
2794
+ }, /* @ts-ignore */
2795
+ ...(ngDevMode ? [{ debugName: "activeChannels" }] : /* istanbul ignore next */ []));
2796
+ this.allowTags = signal(false, /* @ts-ignore */
2797
+ ...(ngDevMode ? [{ debugName: "allowTags" }] : /* istanbul ignore next */ []));
2798
+ this.pendingNotes = signal(undefined, /* @ts-ignore */
2799
+ ...(ngDevMode ? [{ debugName: "pendingNotes" }] : /* istanbul ignore next */ []));
2800
+ }
2801
+ /** @returns The URI of the Clipper web application, or `undefined` if not set. */
2802
+ get appUri() {
2803
+ return this._appUri;
2804
+ }
2805
+ /** @returns The base URI of the Clipper REST API. */
2806
+ get serviceUri() {
2807
+ return this._serviceUri;
2808
+ }
2809
+ /** @returns The active feature flags. */
2810
+ get flags() {
2811
+ return this._flags;
2812
+ }
2813
+ /**
2814
+ * Lazily loads the login context from `localStorage` on first access.
2815
+ * @returns The current login context, or `undefined` if not authenticated.
2816
+ */
2817
+ get loginInfo() {
2818
+ if (!this._loginInfo) {
2819
+ const loginInfo = localStorage.getItem('clipper_context');
2820
+ if (loginInfo) {
2821
+ try {
2822
+ this._loginInfo = JSON.parse(loginInfo);
2823
+ }
2824
+ catch { }
2825
+ }
2826
+ }
2827
+ return this._loginInfo;
2828
+ }
2829
+ /** @returns The shared teams collection. */
2830
+ get teams() {
2831
+ return this._teams;
2832
+ }
2833
+ ////
2834
+ // SHARED MUTATORS
2835
+ ////
2836
+ /**
2837
+ * Ensures a login-info object exists and returns it for further mutation.
2838
+ * Replaces the inline `if (!this._loginInfo) { ... }` guards now that the field
2839
+ * is private to this service.
2840
+ * @returns The existing login-info object, or a freshly created empty one.
2841
+ */
2842
+ ensureLoginInfo() {
2843
+ if (!this._loginInfo) {
2844
+ this._loginInfo = { context: undefined };
2845
+ }
2846
+ return this._loginInfo;
2847
+ }
2848
+ /**
2849
+ * Sets the logged-in state. The backing signal is read-only to consumers, so the
2850
+ * feature services use this method instead.
2851
+ * @param value - The new logged-in state.
2852
+ */
2853
+ setLoggedIn(value) {
2854
+ this._loggedIn.set(value);
2855
+ }
2856
+ /**
2857
+ * Replaces the shared teams collection (populated by the calendar, archive and
2858
+ * collaboration queries).
2859
+ * @param teams - The teams to store.
2860
+ */
2861
+ setTeams(teams) {
2862
+ this._teams = teams;
2863
+ }
2864
+ /**
2865
+ * Persists the current login context to `localStorage`.
2866
+ */
2867
+ storeContext() {
2868
+ localStorage.setItem('clipper_context', JSON.stringify(this._loginInfo));
2869
+ }
2870
+ /**
2871
+ * Updates the stored login context with the values from a fresh login result.
2872
+ * @param result - The login result containing the new user context and channel settings.
2873
+ */
2874
+ updateContext(result) {
2875
+ const info = this.ensureLoginInfo();
2876
+ info.context = result.context;
2877
+ info.channels = result.settings;
2878
+ this.storeContext();
2879
+ }
2880
+ /**
2881
+ * Rebuilds the `availableChannels` signal from the current login context.
2882
+ */
2883
+ initializeChannels() {
2884
+ if (this.loginInfo) {
2885
+ const channels = [];
2886
+ this.loginInfo.channels?.forEach(n => {
2887
+ const channelSubscription = this.loginInfo?.context?.channels?.find(x => x.channel === n.channelId);
2888
+ n.isSuspended = channelSubscription?.isSuspended === true;
2889
+ const channel = ClipperChannels.find(x => x.value === n.channelId);
2890
+ if (channel) {
2891
+ channel.disabled = !n.isActive;
2892
+ channel.suspended = n.isSuspended === true;
2893
+ channel.active = n.isActive === true && n.isEnabled === true;
2894
+ channels.push(channel);
2895
+ }
2896
+ });
2897
+ this.availableChannels.set(channels);
2898
+ }
2899
+ }
2900
+ /**
2901
+ * Resets the login state, clears the stored login info, and broadcasts `LOGOUT_COMPLETED`.
2902
+ */
2903
+ reset() {
2904
+ // Clear login info
2905
+ this._loginInfo = undefined;
2906
+ // Logged out
2907
+ this._loggedIn.set(false);
2908
+ // Reset channels
2909
+ this.availableChannels.set([]);
2910
+ // Notify
2911
+ this.broadcastService.sendMessage(ClipperMessages.LOGOUT_COMPLETED);
2912
+ }
2913
+ /**
2914
+ * Clears all session-storage authentication keys and resets the login state.
2915
+ * @param clearOAuthToken - When `true`, the OAuth bearer token is also removed.
2916
+ */
2917
+ clear(clearOAuthToken = false) {
2918
+ // Clear local storage
2919
+ sessionStorage.removeItem('clipper_auth');
2920
+ sessionStorage.removeItem('clipper_oauth');
2921
+ if (clearOAuthToken) {
2922
+ sessionStorage.removeItem('clipper_oauth_token');
2923
+ }
2924
+ // Reset login
2925
+ this.reset();
2926
+ }
2927
+ ////
2928
+ // BOOTSTRAP
2929
+ ////
2930
+ /**
2931
+ * Initialises the service with the API base URI, optional app URI, and feature flags.
2932
+ * @param serviceUri - The base URI of the Clipper REST API.
2933
+ * @param appUri - Optional URI of the Clipper web application (used to build document links).
2934
+ * @param flags - Feature flags that control service behaviour. Defaults to `ClipperServiceFlags.None`.
2935
+ */
2936
+ initialize(serviceUri, appUri, flags = ClipperServiceFlags.None) {
2937
+ // Create unique client and machine id
2938
+ if (!sessionStorage.getItem('clipper_client_id')) {
2939
+ sessionStorage.setItem('clipper_client_id', (flags && ClipperServiceFlags.Embedded) > 0
2940
+ ? 'embedded'
2941
+ : SystemUtils.generateUUID());
2942
+ }
2943
+ // Initialize
2944
+ this._serviceUri = serviceUri;
2945
+ this._appUri = appUri;
2946
+ this._flags = flags;
2947
+ // React to message broadcasting
2948
+ if (!this.broadcastInitialized) {
2949
+ this.broadcastInitialized = true;
2950
+ this.broadcastService.getMessage()
2951
+ .pipe(takeUntilDestroyed(this.destroyRef))
2952
+ .subscribe(message => {
2953
+ if (message.id === ClipperMessages.LOGIN_CHANGED) {
2954
+ this.injector.get(ClipperLoginService).login(undefined, undefined, true, message.data?.oauth ?? undefined, message.data?.oauthAccessToken ?? undefined).subscribe({
2955
+ next: r => {
2956
+ if (!r.success) {
2957
+ if ((this.flags & ClipperServiceFlags.DisplayConnectionStateMessages) > 0) {
2958
+ this.broadcastService.sendMessage(ClipperMessages.ERROR, { message: "Le credenziali di accesso sono cambiate o non sono più valide. Esegui un nuovo accesso." });
2959
+ }
2960
+ this.broadcastService.sendMessage(ClipperMessages.LOGIN_FAILED);
2961
+ }
2962
+ else {
2963
+ if ((this.flags & ClipperServiceFlags.DisplayConnectionStateMessages) > 0) {
2964
+ this.broadcastService.sendMessage(ClipperMessages.SUCCESS_TOAST, { message: "Connesso a Clipper", icon: 'power', duration: 1500 });
2965
+ }
2966
+ // Load bag
2967
+ this.injector.get(ClipperDocumentsService).loadBag();
2968
+ }
2969
+ },
2970
+ error: () => { console.error("Clipper non disponibile."); } // Avoid unwanted errors on client
2971
+ });
2972
+ }
2973
+ else if (message.id === ClipperMessages.LOGOUT) {
2974
+ if (this.loggedIn()) {
2975
+ this.injector.get(ClipperLoginService).logout().subscribe(r => {
2976
+ if (!r.success) {
2977
+ if (r.message) {
2978
+ this.broadcastService.sendMessage(ClipperMessages.ERROR, { message: "<p>" + r.message + "</p><br><br><hr><p class='small'><i>Per eliminare la configurazione di Clipper accedere a:<br><b>menu > personalizza > collegamenti</b></i></p>" });
2979
+ }
2980
+ }
2981
+ else {
2982
+ if ((this.flags & ClipperServiceFlags.DisplayConnectionStateMessages) > 0) {
2983
+ this.broadcastService.sendMessage(ClipperMessages.SUCCESS_TOAST, { message: "Disconnesso da Clipper", icon: 'power_off', duration: 1500 });
2984
+ }
2985
+ // Empty bag
2986
+ this.bag.set([]);
2987
+ }
2988
+ });
2989
+ }
2990
+ else {
2991
+ this.clear();
2992
+ }
2993
+ }
2994
+ });
2995
+ }
2996
+ // Eveluate current session storage in case of page refresh (F5)
2997
+ if (this.loggedIn()) {
2998
+ // Auto login
2999
+ this.loggingIn.set(false);
3000
+ // Initialize channels
3001
+ this.initializeChannels();
3002
+ // Notify
3003
+ this.broadcastService.sendMessage(ClipperMessages.LOGIN_COMPLETED);
3004
+ }
3005
+ }
3006
+ /**
3007
+ * Ping
3008
+ */
3009
+ ping() {
3010
+ this.httpClient.get(this._serviceUri + '/ping?nocache=' + SystemUtils.generateUUID())
3011
+ .pipe(catchError(() => { return EMPTY; }))
3012
+ .subscribe();
3013
+ }
3014
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ClipperCoreService, deps: [], target: i0.ɵɵFactoryTarget.Service }); }
3015
+ static { this.ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.0.1", ngImport: i0, type: ClipperCoreService }); }
3016
+ }
3017
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ClipperCoreService, decorators: [{
3018
+ type: Service
3019
+ }] });
3020
+
3021
+ /**
3022
+ * Account management: password reset/recovery, settings, channel activation, trial info
3023
+ * and user links. Channel state lives in `ClipperCoreService`; the channel-mutating
3024
+ * helpers (`setChannels`, `setChannelsState`, `updateChannels`) live here.
3025
+ */
3026
+ class ClipperAccountService {
3027
+ constructor() {
3028
+ this.httpClient = inject(HttpClient);
3029
+ this.broadcastService = inject(BroadcastService);
3030
+ this.core = inject(ClipperCoreService);
3031
+ }
3032
+ /**
3033
+ * Resets the user's password using the provided parameters.
3034
+ * @param params - The new password and confirmation data.
3035
+ * @returns An observable emitting the API result wrapping the reset password payload.
2886
3036
  */
2887
- clearBag() {
2888
- return this.httpClient
2889
- .post(this.serviceUri + '/documents/working/delete', { deleteAll: true })
2890
- .subscribe((r) => {
2891
- if (!r.success) {
2892
- this.broadcastService.sendMessage(ClipperMessages.ERROR, { message: r.message });
2893
- }
2894
- else {
2895
- this.bag.set([]);
2896
- this.broadcastService.sendMessage(ClipperMessages.SUCCESS_TOAST, { message: "Operazione completata con successo.", icon: 'check', duration: 1500 });
2897
- }
2898
- });
3037
+ resetPassword(params) {
3038
+ return this.httpClient.post(this.core.serviceUri + '/account/password/reset', params);
2899
3039
  }
2900
- ///
2901
- // WORKING SEARCHES
2902
- ///
2903
3040
  /**
2904
- * Retrieves the saved searches for the given module.
2905
- * @param module - The Clipper module whose saved searches to load.
3041
+ * Initiates a password recovery flow by sending a reset link to the user's email.
3042
+ * @param params - The password recovery request data.
3043
+ * @returns An observable emitting the API result wrapping a boolean success flag.
2906
3044
  */
2907
- loadSearches(module) {
2908
- return this.httpClient
2909
- .get(this.serviceUri + '/documents/searches/?module=' + module);
3045
+ recoverPassword(params) {
3046
+ return this.httpClient.post(this.core.serviceUri + '/account/password/recover', params);
2910
3047
  }
2911
3048
  /**
2912
- * Persists a user search configuration on the server.
2913
- * @param params - The search configuration to save.
3049
+ * Gets the current user account settings.
3050
+ * @returns An observable emitting the API result wrapping the account settings.
2914
3051
  */
2915
- saveSearch(params) {
2916
- return this.httpClient
2917
- .post(this.serviceUri + '/documents/searches/save', params);
3052
+ getSettings() {
3053
+ return this.httpClient.get(this.core.serviceUri + '/account/settings');
2918
3054
  }
2919
3055
  /**
2920
- * Deletes a saved search by its ID.
2921
- * @param id - The ID of the saved search to remove.
3056
+ * Updates user account settings and applies any channel configuration changes.
3057
+ * @param params - A map of settings key-value pairs to update.
3058
+ * @returns An observable emitting the API result wrapping a boolean success flag.
2922
3059
  */
2923
- deleteSearch(id) {
2924
- return this.httpClient
2925
- .post(this.serviceUri + '/documents/searches/delete', { id: id });
3060
+ updateSettings(params) {
3061
+ return this.httpClient.post(this.core.serviceUri + '/account/settings/update', params).pipe(map(r => {
3062
+ if (r.success) {
3063
+ if (params.channels) {
3064
+ this.setChannels(params.channels);
3065
+ }
3066
+ }
3067
+ return r;
3068
+ }));
2926
3069
  }
2927
- ///
2928
- // CALENDAR
2929
- ///
2930
3070
  /**
2931
- * Query calendar
3071
+ * Saves updated channel activation states to the server and applies the new dashboard result.
3072
+ * @param params - The channel state update parameters.
3073
+ * @returns An observable emitting the API result wrapping the dashboard result.
2932
3074
  */
2933
- queryCalendar(params) {
2934
- return this.httpClient.post(this._serviceUri + '/calendar', params)
2935
- .pipe(map((r) => {
2936
- // Store teams
3075
+ updateChannelsState(params) {
3076
+ return this.httpClient.post(this.core.serviceUri + '/account/settings/channels/update', params).pipe(map(r => {
2937
3077
  if (r.success) {
2938
- this._teams = r.value.teams;
3078
+ this.setChannelsState(r.value);
2939
3079
  }
2940
3080
  return r;
2941
3081
  }));
2942
3082
  }
2943
3083
  /**
2944
- * Get the calendar snapshot
3084
+ * Applies a new dashboard result: updates channel settings, dashboard counters, and
3085
+ * re-initialises the available-channels signal.
3086
+ * @param value - The dashboard result containing updated channel and counter data.
2945
3087
  */
2946
- calendarSnapshot(params) {
2947
- return this.httpClient.post(this._serviceUri + '/calendar/snapshot', params);
3088
+ setChannelsState(value) {
3089
+ // Update channels
3090
+ const info = this.core.loginInfo;
3091
+ if (info) {
3092
+ info.channels = value.channels ?? [];
3093
+ }
3094
+ // Update dashboard
3095
+ this.core.dashboard.documentUpdates = value.documentUpdates;
3096
+ this.core.dashboard.expiredDeadlines = value.expiredDeadlines;
3097
+ this.core.dashboard.expiringDeadlines = value.expiringDeadlines;
3098
+ this.core.dashboard.isTrial = value.isTrial;
3099
+ this.core.dashboard.items.set(value.items ?? []);
3100
+ this.broadcastService.sendMessage(ClipperMessages.COMMAND_DASHBOARD_UPDATED);
3101
+ this.core.storeContext();
3102
+ this.core.initializeChannels();
2948
3103
  }
2949
3104
  /**
2950
- * Deletes one or more calendar deadlines by ID.
2951
- * @param ids - The IDs of the deadlines to delete.
3105
+ * Replaces the stored channel settings and re-initialises the available-channels signal.
3106
+ * @param channels - The new channel settings to store and activate.
2952
3107
  */
2953
- deleteCalendarDeadlines(ids) {
2954
- return this.httpClient.post(this._serviceUri + '/calendar/delete', { ids: ids });
3108
+ setChannels(channels) {
3109
+ const info = this.core.loginInfo;
3110
+ if (info) {
3111
+ info.channels = channels;
3112
+ this.core.storeContext();
3113
+ this.core.initializeChannels();
3114
+ }
2955
3115
  }
2956
3116
  /**
2957
- * Copies one or more calendar deadlines using the given parameters.
2958
- * @param params - The copy operation parameters.
3117
+ * Toggles the active state of available channels based on the supplied selection list.
3118
+ * @param values - The selected channel items; channels absent from this list are deactivated.
2959
3119
  */
2960
- copyCalendarDeadlines(params) {
2961
- return this.httpClient.post(this._serviceUri + '/calendar/copy', params);
3120
+ updateChannels(values) {
3121
+ this.core.availableChannels().forEach(channel => {
3122
+ if (!channel.disabled) {
3123
+ channel.active = values?.findIndex(x => x.value === channel.value) !== -1;
3124
+ }
3125
+ });
2962
3126
  }
2963
3127
  /**
2964
- * Marks the given calendar deadline as closed.
2965
- * @param id - The ID of the deadline to close.
3128
+ * Gets the trial info for the current account.
3129
+ * @returns An observable emitting the API result wrapping the trial info.
2966
3130
  */
2967
- closeCalendarDeadline(id) {
2968
- return this.httpClient.post(this._serviceUri + '/calendar/close', { id: id });
3131
+ getTrialInfo() {
3132
+ return this.httpClient.get(this.core.serviceUri + '/account/trial');
2969
3133
  }
2970
3134
  /**
2971
- * Creates or updates a calendar deadline.
2972
- * @param params - The deadline data to save.
3135
+ * Refreshes the trial status from the server and updates the stored login context.
2973
3136
  */
2974
- saveCalendarDeadline(params) {
2975
- return this.httpClient.post(this._serviceUri + '/calendar/save', params);
3137
+ updateTrialInfo() {
3138
+ const info = this.core.loginInfo;
3139
+ if (!info || info?.context?.hasTrial === false)
3140
+ return; // Not supported
3141
+ this.getTrialInfo().subscribe((r) => {
3142
+ if (r.success) {
3143
+ if (info?.context) {
3144
+ if (r.value) {
3145
+ info.context.hasTrial = true;
3146
+ info.context.trialInfo = r.value;
3147
+ }
3148
+ else {
3149
+ info.context.hasTrial = false;
3150
+ info.context.trialInfo = undefined;
3151
+ }
3152
+ }
3153
+ }
3154
+ });
2976
3155
  }
2977
3156
  /**
2978
- * Retrieves a single calendar deadline by its ID.
2979
- * @param id - The ID of the deadline to retrieve.
3157
+ * Creates or updates a user link record.
3158
+ * @param item - The user link data to save.
3159
+ * @returns An observable emitting the API result wrapping a boolean success flag.
2980
3160
  */
2981
- getCalendarDeadline(id) {
2982
- return this.httpClient.get(this._serviceUri + '/calendar/' + id);
3161
+ saveLink(item) {
3162
+ return this.httpClient.post(this.core.serviceUri + '/account/links/save', item);
2983
3163
  }
2984
3164
  /**
2985
- * Exports the selected deadlines in iCalendar (.ics) format.
2986
- * @param ids - The IDs of the deadlines to export.
3165
+ * Deletes a user link record.
3166
+ * @param item - The user link to remove.
3167
+ * @returns An observable emitting the API result wrapping a boolean success flag.
2987
3168
  */
2988
- exportCalendarDeadlines(ids) {
2989
- return this.httpClient.post(this._serviceUri + '/calendar/export', { ids: ids }, {
2990
- responseType: 'blob'
2991
- });
3169
+ deleteLink(item) {
3170
+ return this.httpClient.post(this.core.serviceUri + '/account/links/delete', item);
3171
+ }
3172
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ClipperAccountService, deps: [], target: i0.ɵɵFactoryTarget.Service }); }
3173
+ static { this.ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.0.1", ngImport: i0, type: ClipperAccountService }); }
3174
+ }
3175
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ClipperAccountService, decorators: [{
3176
+ type: Service
3177
+ }] });
3178
+
3179
+ /**
3180
+ * Document archive: folders and files search, save, copy, delete, import/export and download.
3181
+ */
3182
+ class ClipperArchiveService {
3183
+ constructor() {
3184
+ this.httpClient = inject(HttpClient);
3185
+ this.core = inject(ClipperCoreService);
2992
3186
  }
2993
- ///
2994
- // ARCHIVE
2995
- ///
2996
3187
  /**
2997
- * Retrieves all archive folders matching the given search parameters.
3188
+ * Retrieves all archive folders matching the given search parameters and stores the
3189
+ * returned teams in the shared state.
2998
3190
  * @param params - The folder search parameters.
3191
+ * @returns An observable emitting the API result wrapping the folders search result.
2999
3192
  */
3000
3193
  getArchiveFolders(params) {
3001
3194
  return this.httpClient
3002
- .post(this._serviceUri + '/archive/folders', params)
3195
+ .post(this.core.serviceUri + '/archive/folders', params)
3003
3196
  .pipe(map((r) => {
3004
3197
  // Store teams
3005
3198
  if (r.success) {
3006
- this._teams = r.value.teams;
3199
+ this.core.setTeams(r.value.teams);
3007
3200
  }
3008
3201
  return r;
3009
3202
  }));
@@ -3011,17 +3204,19 @@ class ClipperService {
3011
3204
  /**
3012
3205
  * Creates or updates an archive folder.
3013
3206
  * @param params - The folder data to save.
3207
+ * @returns An observable emitting the API result wrapping the saved folder.
3014
3208
  */
3015
3209
  saveArchiveFolder(params) {
3016
- return this.httpClient.post(this._serviceUri + '/archive/folders/save', params);
3210
+ return this.httpClient.post(this.core.serviceUri + '/archive/folders/save', params);
3017
3211
  }
3018
3212
  /**
3019
3213
  * Exports archive folders as a downloadable file.
3020
3214
  * @param id - The folder ID to export, or `undefined` to export all folders.
3021
3215
  * @param teamId - The optional team ID. Defaults to `undefined` (personal workspace).
3216
+ * @returns An observable emitting the exported folders content as a blob.
3022
3217
  */
3023
3218
  exportArchiveFolders(id, teamId) {
3024
- let url = this._serviceUri + '/archive/export/?';
3219
+ let url = this.core.serviceUri + '/archive/export/?';
3025
3220
  if (id) {
3026
3221
  url += 'id=' + id;
3027
3222
  }
@@ -3041,6 +3236,7 @@ class ClipperService {
3041
3236
  /**
3042
3237
  * Uploads and imports archive folders from a file.
3043
3238
  * @param params - The import parameters including the source file and destination folder.
3239
+ * @returns An observable of HTTP events reporting upload progress and the final response.
3044
3240
  */
3045
3241
  importArchiveFolders(params) {
3046
3242
  // Prepare form
@@ -3055,32 +3251,36 @@ class ClipperService {
3055
3251
  formData.append('teamId', params.teamId);
3056
3252
  }
3057
3253
  // Create request
3058
- return this.httpClient.request(new HttpRequest('POST', this._serviceUri + '/archive/import/', formData, { reportProgress: true }));
3254
+ return this.httpClient.request(new HttpRequest('POST', this.core.serviceUri + '/archive/import/', formData, { reportProgress: true }));
3059
3255
  }
3060
3256
  /**
3061
3257
  * Retrieves archive files and folders matching the given search parameters.
3062
3258
  * @param params - The archive files search parameters.
3259
+ * @returns An observable emitting the API result wrapping the files search result.
3063
3260
  */
3064
3261
  queryArchiveItems(params) {
3065
- return this.httpClient.post(this._serviceUri + '/archive/files', params);
3262
+ return this.httpClient.post(this.core.serviceUri + '/archive/files', params);
3066
3263
  }
3067
3264
  /**
3068
3265
  * Deletes one or more archive files or folders.
3069
3266
  * @param params - The delete operation parameters.
3267
+ * @returns An observable emitting the API result wrapping the number of deleted items.
3070
3268
  */
3071
3269
  deleteArchiveItems(params) {
3072
- return this.httpClient.post(this._serviceUri + '/archive/files/delete', params);
3270
+ return this.httpClient.post(this.core.serviceUri + '/archive/files/delete', params);
3073
3271
  }
3074
3272
  /**
3075
3273
  * Copies archive files or folders to a destination folder.
3076
3274
  * @param params - The copy operation parameters.
3275
+ * @returns An observable emitting the API result wrapping the number of copied items.
3077
3276
  */
3078
3277
  copyArchiveItems(params) {
3079
- return this.httpClient.post(this._serviceUri + '/archive/files/copy', params);
3278
+ return this.httpClient.post(this.core.serviceUri + '/archive/files/copy', params);
3080
3279
  }
3081
3280
  /**
3082
3281
  * Uploads and saves an archive file using multipart form data.
3083
3282
  * @param params - The file metadata and binary content to save.
3283
+ * @returns An observable of HTTP events reporting upload progress and the final response.
3084
3284
  */
3085
3285
  saveArchiveFile(params) {
3086
3286
  // Prepare form
@@ -3124,139 +3324,148 @@ class ClipperService {
3124
3324
  if (params.model)
3125
3325
  formData.append('model', params.model.toString());
3126
3326
  // Create request
3127
- return this.httpClient.request(new HttpRequest('POST', this._serviceUri + '/archive/files/save', formData, { reportProgress: true }));
3327
+ return this.httpClient.request(new HttpRequest('POST', this.core.serviceUri + '/archive/files/save', formData, { reportProgress: true }));
3128
3328
  }
3129
3329
  /**
3130
3330
  * Retrieves the metadata for a single archive file.
3131
3331
  * @param id - The ID of the archive file to retrieve.
3332
+ * @returns An observable emitting the API result wrapping the archive file info.
3132
3333
  */
3133
3334
  getArchiveFile(id) {
3134
- return this.httpClient.get(this._serviceUri + '/archive/files/' + id);
3335
+ return this.httpClient.get(this.core.serviceUri + '/archive/files/' + id);
3135
3336
  }
3136
3337
  /**
3137
3338
  * Downloads the binary content of an archive file.
3138
3339
  * @param id - The binary ID of the file to download.
3139
3340
  * @param otp - An optional one-time password for authenticated downloads.
3341
+ * @returns An observable emitting the file binary content as a blob.
3140
3342
  */
3141
3343
  downloadArchiveFile(id, otp) {
3142
- return this.httpClient.get(this._serviceUri + '/archive/files/download/?id=' + id + '&otp=' + (otp ?? ''), { responseType: 'blob' });
3344
+ return this.httpClient.get(this.core.serviceUri + '/archive/files/download/?id=' + id + '&otp=' + (otp ?? ''), { responseType: 'blob' });
3143
3345
  }
3144
- ///
3145
- // ACCOUNT AND SETTINGS
3146
- ///
3147
- /**
3148
- * Resets the user's password using the provided parameters.
3149
- * @param params - The new password and confirmation data.
3150
- */
3151
- resetPassword(params) {
3152
- return this.httpClient.post(this._serviceUri + '/account/password/reset', params);
3346
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ClipperArchiveService, deps: [], target: i0.ɵɵFactoryTarget.Service }); }
3347
+ static { this.ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.0.1", ngImport: i0, type: ClipperArchiveService }); }
3348
+ }
3349
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ClipperArchiveService, decorators: [{
3350
+ type: Service
3351
+ }] });
3352
+
3353
+ /**
3354
+ * Calendar deadlines: search, snapshot, save/copy/close/delete and iCalendar export.
3355
+ */
3356
+ class ClipperCalendarService {
3357
+ constructor() {
3358
+ this.httpClient = inject(HttpClient);
3359
+ this.core = inject(ClipperCoreService);
3153
3360
  }
3154
3361
  /**
3155
- * Initiates a password recovery flow by sending a reset link to the user's email.
3156
- * @param params - The password recovery request data.
3362
+ * Queries the calendar and stores the returned teams in the shared state.
3363
+ * @param params - The calendar search parameters.
3364
+ * @returns An observable emitting the API result wrapping the calendar search result.
3157
3365
  */
3158
- recoverPassword(params) {
3159
- return this.httpClient.post(this._serviceUri + '/account/password/recover', params);
3366
+ queryCalendar(params) {
3367
+ return this.httpClient.post(this.core.serviceUri + '/calendar', params)
3368
+ .pipe(map((r) => {
3369
+ // Store teams
3370
+ if (r.success) {
3371
+ this.core.setTeams(r.value.teams);
3372
+ }
3373
+ return r;
3374
+ }));
3160
3375
  }
3161
3376
  /**
3162
- * Gets the current user account settings
3377
+ * Gets the calendar snapshot.
3378
+ * @param params - The calendar search parameters.
3379
+ * @returns An observable emitting the API result wrapping the calendar snapshot result.
3163
3380
  */
3164
- getSettings() {
3165
- return this.httpClient.get(this._serviceUri + '/account/settings');
3381
+ calendarSnapshot(params) {
3382
+ return this.httpClient.post(this.core.serviceUri + '/calendar/snapshot', params);
3166
3383
  }
3167
3384
  /**
3168
- * Updates user account settings and applies any channel configuration changes.
3169
- * @param params - A map of settings key-value pairs to update.
3385
+ * Deletes one or more calendar deadlines by ID.
3386
+ * @param ids - The IDs of the deadlines to delete.
3387
+ * @returns An observable emitting the API result wrapping the number of deleted deadlines.
3170
3388
  */
3171
- updateSettings(params) {
3172
- return this.httpClient.post(this._serviceUri + '/account/settings/update', params).pipe(map(r => {
3173
- if (r.success) {
3174
- if (params.channels) {
3175
- this.setChannels(params.channels);
3176
- }
3177
- }
3178
- return r;
3179
- }));
3389
+ deleteCalendarDeadlines(ids) {
3390
+ return this.httpClient.post(this.core.serviceUri + '/calendar/delete', { ids: ids });
3180
3391
  }
3181
3392
  /**
3182
- * Saves updated channel activation states to the server and applies the new dashboard result.
3183
- * @param params - The channel state update parameters.
3393
+ * Copies one or more calendar deadlines using the given parameters.
3394
+ * @param params - The copy operation parameters.
3395
+ * @returns An observable emitting the API result wrapping the number of copied deadlines.
3184
3396
  */
3185
- updateChannelsState(params) {
3186
- return this.httpClient.post(this._serviceUri + '/account/settings/channels/update', params).pipe(map(r => {
3187
- if (r.success) {
3188
- this.setChannelsState(r.value);
3189
- }
3190
- return r;
3191
- }));
3397
+ copyCalendarDeadlines(params) {
3398
+ return this.httpClient.post(this.core.serviceUri + '/calendar/copy', params);
3192
3399
  }
3193
- ///
3194
- // TRIAL
3195
- ///
3196
3400
  /**
3197
- * Get trial info
3401
+ * Marks the given calendar deadline as closed.
3402
+ * @param id - The ID of the deadline to close.
3403
+ * @returns An observable emitting the API result wrapping the updated deadline.
3198
3404
  */
3199
- getTrialInfo() {
3200
- return this.httpClient.get(this._serviceUri + '/account/trial');
3405
+ closeCalendarDeadline(id) {
3406
+ return this.httpClient.post(this.core.serviceUri + '/calendar/close', { id: id });
3201
3407
  }
3202
3408
  /**
3203
- * Refreshes the trial status from the server and updates the stored login context.
3409
+ * Creates or updates a calendar deadline.
3410
+ * @param params - The deadline data to save.
3411
+ * @returns An observable emitting the API result wrapping the saved deadlines.
3204
3412
  */
3205
- updateTrialInfo() {
3206
- if (!this._loginInfo || this._loginInfo?.context?.hasTrial === false)
3207
- return; // Not supported
3208
- this.getTrialInfo().subscribe((r) => {
3209
- if (r.success) {
3210
- if (this._loginInfo?.context) {
3211
- if (r.value) {
3212
- this._loginInfo.context.hasTrial = true;
3213
- this._loginInfo.context.trialInfo = r.value;
3214
- }
3215
- else {
3216
- this._loginInfo.context.hasTrial = false;
3217
- this._loginInfo.context.trialInfo = undefined;
3218
- }
3219
- }
3220
- }
3221
- });
3413
+ saveCalendarDeadline(params) {
3414
+ return this.httpClient.post(this.core.serviceUri + '/calendar/save', params);
3222
3415
  }
3223
- ///
3224
- // LINKS
3225
- ///
3226
3416
  /**
3227
- * Creates or updates a user link record.
3228
- * @param item - The user link data to save.
3417
+ * Retrieves a single calendar deadline by its ID.
3418
+ * @param id - The ID of the deadline to retrieve.
3419
+ * @returns An observable emitting the API result wrapping the deadline.
3229
3420
  */
3230
- saveLink(item) {
3231
- return this.httpClient.post(this._serviceUri + '/account/links/save', item);
3421
+ getCalendarDeadline(id) {
3422
+ return this.httpClient.get(this.core.serviceUri + '/calendar/' + id);
3232
3423
  }
3233
3424
  /**
3234
- * Deletes a user link record.
3235
- * @param item - The user link to remove.
3425
+ * Exports the selected deadlines in iCalendar (.ics) format.
3426
+ * @param ids - The IDs of the deadlines to export.
3427
+ * @returns An observable emitting the iCalendar content as a blob.
3236
3428
  */
3237
- deleteLink(item) {
3238
- return this.httpClient.post(this._serviceUri + '/account/links/delete', item);
3429
+ exportCalendarDeadlines(ids) {
3430
+ return this.httpClient.post(this.core.serviceUri + '/calendar/export', { ids: ids }, {
3431
+ responseType: 'blob'
3432
+ });
3433
+ }
3434
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ClipperCalendarService, deps: [], target: i0.ɵɵFactoryTarget.Service }); }
3435
+ static { this.ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.0.1", ngImport: i0, type: ClipperCalendarService }); }
3436
+ }
3437
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ClipperCalendarService, decorators: [{
3438
+ type: Service
3439
+ }] });
3440
+
3441
+ /**
3442
+ * Collaboration: teams and team members, contacts and shared document notes.
3443
+ */
3444
+ class ClipperCollaborationService {
3445
+ constructor() {
3446
+ this.httpClient = inject(HttpClient);
3447
+ this.core = inject(ClipperCoreService);
3239
3448
  }
3240
- ///
3241
- // TEAMS
3242
- ///
3243
3449
  /**
3244
3450
  * Renames the current user's team.
3245
3451
  * @param newName - The new display name for the team.
3452
+ * @returns An observable emitting the API result wrapping the updated team info.
3246
3453
  */
3247
3454
  renameTeam(newName) {
3248
- return this.httpClient.post(this._serviceUri + '/account/teams/update', { title: newName });
3455
+ return this.httpClient.post(this.core.serviceUri + '/account/teams/update', { title: newName });
3249
3456
  }
3250
3457
  /**
3251
- * Retrieves the list of teams available to the current user.
3458
+ * Retrieves the list of teams available to the current user. When not querying
3459
+ * admins only, the result is also stored in the shared teams state.
3252
3460
  * @param params - The teams search parameters.
3461
+ * @returns An observable emitting the API result wrapping the teams search result.
3253
3462
  */
3254
3463
  getTeams(params) {
3255
- return this.httpClient.post(this._serviceUri + '/account/teams', params)
3464
+ return this.httpClient.post(this.core.serviceUri + '/account/teams', params)
3256
3465
  .pipe(map((r) => {
3257
3466
  // Store teams
3258
3467
  if (!params.adminsOnly) {
3259
- this._teams = r.value.items;
3468
+ this.core.setTeams(r.value.items);
3260
3469
  }
3261
3470
  return r;
3262
3471
  }));
@@ -3264,61 +3473,63 @@ class ClipperService {
3264
3473
  /**
3265
3474
  * Retrieves the members of a team matching the given search parameters.
3266
3475
  * @param params - The team members search parameters.
3476
+ * @returns An observable emitting the API result wrapping the team members search result.
3267
3477
  */
3268
3478
  getTeamMembers(params) {
3269
- return this.httpClient.post(this._serviceUri + '/account/teams/members', params);
3479
+ return this.httpClient.post(this.core.serviceUri + '/account/teams/members', params);
3270
3480
  }
3271
3481
  /**
3272
3482
  * Removes the specified members from the team.
3273
3483
  * @param teamId - The ID of the team.
3274
3484
  * @param ids - The numeric IDs of the team members to remove.
3485
+ * @returns An observable emitting the API result wrapping the number of removed members.
3275
3486
  */
3276
3487
  deleteTeamMembers(teamId, ids) {
3277
- return this.httpClient.post(this._serviceUri + '/account/teams/members/delete', { teamId: teamId, ids: ids });
3488
+ return this.httpClient.post(this.core.serviceUri + '/account/teams/members/delete', { teamId: teamId, ids: ids });
3278
3489
  }
3279
3490
  /**
3280
3491
  * Creates or updates a team member record.
3281
3492
  * @param item - The team member data to save.
3493
+ * @returns An observable emitting the API result wrapping the saved team member.
3282
3494
  */
3283
3495
  saveTeamMember(item) {
3284
- return this.httpClient.post(this._serviceUri + '/account/teams/members/save', item);
3496
+ return this.httpClient.post(this.core.serviceUri + '/account/teams/members/save', item);
3285
3497
  }
3286
- ///
3287
- // CONTACTS
3288
- ///
3289
3498
  /**
3290
3499
  * Retrieves contacts matching the given search parameters.
3291
3500
  * @param params - The contacts search parameters.
3501
+ * @returns An observable emitting the API result wrapping the contacts query result.
3292
3502
  */
3293
3503
  queryContacts(params) {
3294
- return this.httpClient.post(this._serviceUri + '/account/contacts', params);
3504
+ return this.httpClient.post(this.core.serviceUri + '/account/contacts', params);
3295
3505
  }
3296
3506
  /**
3297
3507
  * Deletes the specified contacts.
3298
3508
  * @param ids - The numeric IDs of the contacts to remove.
3509
+ * @returns An observable emitting the API result wrapping the number of deleted contacts.
3299
3510
  */
3300
3511
  deleteContacts(ids) {
3301
- return this.httpClient.post(this._serviceUri + '/account/contacts/delete', { ids: ids });
3512
+ return this.httpClient.post(this.core.serviceUri + '/account/contacts/delete', { ids: ids });
3302
3513
  }
3303
3514
  /**
3304
3515
  * Creates or updates a contact record.
3305
3516
  * @param item - The contact data to save.
3517
+ * @returns An observable emitting the API result wrapping the saved contact.
3306
3518
  */
3307
3519
  saveContact(item) {
3308
- return this.httpClient.post(this._serviceUri + '/account/contacts/save', item);
3520
+ return this.httpClient.post(this.core.serviceUri + '/account/contacts/save', item);
3309
3521
  }
3310
- ///
3311
- // NOTES
3312
- ///
3313
3522
  /**
3314
- * Retrieves document notes matching the given search parameters.
3523
+ * Retrieves document notes matching the given search parameters and stores the
3524
+ * returned teams in the shared state.
3315
3525
  * @param params - The notes search parameters.
3526
+ * @returns An observable emitting the API result wrapping the notes search result.
3316
3527
  */
3317
3528
  queryNotes(params) {
3318
- return this.httpClient.post(this._serviceUri + '/account/notes', params).pipe(map((r) => {
3529
+ return this.httpClient.post(this.core.serviceUri + '/account/notes', params).pipe(map((r) => {
3319
3530
  // Store teams
3320
3531
  if (r.success) {
3321
- this._teams = r.value.teams;
3532
+ this.core.setTeams(r.value.teams);
3322
3533
  }
3323
3534
  return r;
3324
3535
  }));
@@ -3326,30 +3537,122 @@ class ClipperService {
3326
3537
  /**
3327
3538
  * Deletes the specified document notes.
3328
3539
  * @param ids - The numeric IDs of the notes to remove.
3540
+ * @returns An observable emitting the API result wrapping the number of deleted notes.
3329
3541
  */
3330
3542
  deleteNotes(ids) {
3331
- return this.httpClient.post(this._serviceUri + '/account/notes/delete', { ids: ids });
3543
+ return this.httpClient.post(this.core.serviceUri + '/account/notes/delete', { ids: ids });
3332
3544
  }
3333
3545
  /**
3334
3546
  * Creates or updates a document note.
3335
3547
  * @param item - The note data to save.
3548
+ * @returns An observable emitting the API result wrapping the saved note.
3336
3549
  */
3337
3550
  saveNote(item) {
3338
- return this.httpClient.post(this._serviceUri + '/account/notes/save', item);
3551
+ return this.httpClient.post(this.core.serviceUri + '/account/notes/save', item);
3339
3552
  }
3340
3553
  /**
3341
3554
  * Retrieves the tracking history for the given document note.
3342
3555
  * @param id - The numeric ID of the note whose tracking records to retrieve.
3556
+ * @returns An observable emitting the API result wrapping the note tracking records.
3343
3557
  */
3344
3558
  getNoteTrackings(id) {
3345
- return this.httpClient.get(this._serviceUri + '/account/notes/trackings/' + id);
3559
+ return this.httpClient.get(this.core.serviceUri + '/account/notes/trackings/' + id);
3346
3560
  }
3347
3561
  /**
3348
3562
  * Gets the count of troubled tracking notifications for document notes,
3349
3563
  * which can be used to display an unread counter in the UI.
3564
+ * @returns An observable emitting the API result wrapping the troubled tracking count.
3350
3565
  */
3351
3566
  countNotesTrackingsTroubled() {
3352
- return this.httpClient.get(this._serviceUri + '/account/notes/trackings/troubled');
3567
+ return this.httpClient.get(this.core.serviceUri + '/account/notes/trackings/troubled');
3568
+ }
3569
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ClipperCollaborationService, deps: [], target: i0.ɵɵFactoryTarget.Service }); }
3570
+ static { this.ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.0.1", ngImport: i0, type: ClipperCollaborationService }); }
3571
+ }
3572
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ClipperCollaborationService, decorators: [{
3573
+ type: Service
3574
+ }] });
3575
+
3576
+ /**
3577
+ * Barrel / facade for the Clipper application.
3578
+ *
3579
+ * Exposes the feature services as named properties and re-exports the shared state
3580
+ * declared in `ClipperCoreService`. Inject `ClipperService` and reach a feature via
3581
+ * its property, e.g. `clipper.login.login(...)`, `clipper.documents.query(...)`,
3582
+ * `clipper.calendar.queryCalendar(...)`.
3583
+ */
3584
+ class ClipperService {
3585
+ constructor() {
3586
+ this.core = inject(ClipperCoreService);
3587
+ /** Authentication: login/logout, MFA, OTP. */
3588
+ this.session = inject(ClipperLoginService);
3589
+ /** Document search, references, export, dashboard, working-documents bag and saved searches. */
3590
+ this.documents = inject(ClipperDocumentsService);
3591
+ /** Account settings, password, channels, trial info and user links. */
3592
+ this.account = inject(ClipperAccountService);
3593
+ /** Archive folders and files. */
3594
+ this.archive = inject(ClipperArchiveService);
3595
+ /** Calendar deadlines. */
3596
+ this.calendar = inject(ClipperCalendarService);
3597
+ /** Teams, contacts and shared notes. */
3598
+ this.collaboration = inject(ClipperCollaborationService);
3599
+ }
3600
+ ////
3601
+ // RE-EXPORTED CORE STATE
3602
+ ////
3603
+ /** @returns The URI of the Clipper web application, or `undefined` if not set. */
3604
+ get appUri() { return this.core.appUri; }
3605
+ /** @returns The base URI of the Clipper REST API. */
3606
+ get serviceUri() { return this.core.serviceUri; }
3607
+ /** @returns The active feature flags. */
3608
+ get flags() { return this.core.flags; }
3609
+ /** @returns The current login context, or `undefined` if not authenticated. */
3610
+ get loginInfo() { return this.core.loginInfo; }
3611
+ /** @returns A read-only signal indicating whether the user is logged in. */
3612
+ get loggedIn() { return this.core.loggedIn; }
3613
+ /** @returns A signal indicating whether a login is in progress. */
3614
+ get loggingIn() { return this.core.loggingIn; }
3615
+ /** @returns A signal holding the current documents search snapshot. */
3616
+ get snapshot() { return this.core.snapshot; }
3617
+ /** @returns A signal indicating whether reference search is supported. */
3618
+ get supportsRS() { return this.core.supportsRS; }
3619
+ /** @returns A signal holding the current references search snapshot. */
3620
+ get referencesSnapshot() { return this.core.referencesSnapshot; }
3621
+ /** @returns The shared dashboard state object. */
3622
+ get dashboard() { return this.core.dashboard; }
3623
+ /** @returns A signal holding the working-documents bag. */
3624
+ get bag() { return this.core.bag; }
3625
+ /** @returns A computed signal with the number of items in the bag. */
3626
+ get bagTotal() { return this.core.bagTotal; }
3627
+ /** @returns A signal controlling the Clipper UI visibility. */
3628
+ get visible() { return this.core.visible; }
3629
+ /** @returns The shared teams collection. */
3630
+ get teams() { return this.core.teams; }
3631
+ /** @returns A signal holding the available channels. */
3632
+ get availableChannels() { return this.core.availableChannels; }
3633
+ /** @returns A computed signal with the currently active channels. */
3634
+ get activeChannels() { return this.core.activeChannels; }
3635
+ /** @returns A signal indicating whether tagging is allowed. */
3636
+ get allowTags() { return this.core.allowTags; }
3637
+ /** @returns A signal with the number of pending notes, or `undefined` if unknown. */
3638
+ get pendingNotes() { return this.core.pendingNotes; }
3639
+ ////
3640
+ // BOOTSTRAP
3641
+ ////
3642
+ /**
3643
+ * Initialises the application with the API base URI, optional app URI, and feature flags.
3644
+ * @param serviceUri - The base URI of the Clipper REST API.
3645
+ * @param appUri - Optional URI of the Clipper web application (used to build document links).
3646
+ * @param flags - Feature flags that control service behaviour. Defaults to `ClipperServiceFlags.None`.
3647
+ */
3648
+ initialize(serviceUri, appUri, flags = ClipperServiceFlags.None) {
3649
+ this.core.initialize(serviceUri, appUri, flags);
3650
+ }
3651
+ /**
3652
+ * Pings the Clipper REST API to keep the session/token alive.
3653
+ */
3654
+ ping() {
3655
+ this.core.ping();
3353
3656
  }
3354
3657
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ClipperService, deps: [], target: i0.ɵɵFactoryTarget.Service }); }
3355
3658
  static { this.ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.0.1", ngImport: i0, type: ClipperService }); }
@@ -3398,15 +3701,23 @@ class ClipperAuthInterceptor {
3398
3701
  /**
3399
3702
  * Processes an HTTP error, broadcasting a user-friendly message when appropriate.
3400
3703
  * Errors are debounced: only one message is sent per `ERROR_DEBOUNCE_MS` window.
3704
+ *
3705
+ * This handler only ever runs for requests already targeting the Clipper service
3706
+ * (see `intercept`). A transport-level failure produces an `HttpErrorResponse`
3707
+ * with status `0` and a `null` url, so the url is only validated when present —
3708
+ * that way genuine network failures still surface the "service unavailable" message.
3401
3709
  * @param error - The raw error value thrown by the HTTP layer.
3402
3710
  */
3403
3711
  handleError(error) {
3404
3712
  if (!(error instanceof HttpErrorResponse))
3405
3713
  return;
3406
- if (!error.url?.startsWith(this.clipperService.serviceUri))
3714
+ // Reject only errors that explicitly belong to a different service; a missing
3715
+ // url (network failure on a Clipper request) is allowed through.
3716
+ if (error.url && !error.url.startsWith(this.clipperService.serviceUri))
3407
3717
  return;
3408
3718
  const errorStatus = error.status;
3409
- const shouldNotify = (errorStatus > 0 && errorStatus < 500) ||
3719
+ const shouldNotify = errorStatus === 0 ||
3720
+ (errorStatus > 0 && errorStatus < 500) ||
3410
3721
  (this.clipperService.flags & ClipperServiceFlags.NotifySystemErrors) > 0;
3411
3722
  if (!shouldNotify)
3412
3723
  return;
@@ -3443,6 +3754,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
3443
3754
  type: Injectable
3444
3755
  }] });
3445
3756
 
3757
+ // Public API surface for the Clipper services.
3758
+ //
3759
+ // `ClipperService` is the facade/barrel: inject it and reach a feature via its
3760
+ // property (e.g. `clipper.login.login(...)`, `clipper.documents.query(...)`).
3761
+ // The feature and core services are exported as well so they can be injected
3762
+ // directly or referenced as types where needed.
3763
+
3446
3764
  /*
3447
3765
  * Public API Surface of ars-utils
3448
3766
  */
@@ -3451,5 +3769,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
3451
3769
  * Generated bundle index. Do not edit.
3452
3770
  */
3453
3771
 
3454
- export { ClipperArchiveCopyMode, ClipperArchiveFileStorageType, ClipperArchiveFileStorageTypes, ClipperArchiveFileType, ClipperArchiveFileTypes, ClipperArchiveFilesSearchParams, ClipperArchiveFoldersSearchParams, ClipperAuthInterceptor, ClipperAuthors, ClipperCalendarCopyMode, ClipperCalendarSearchParams, ClipperCalendarState, ClipperCalendarStates, ClipperChannel, ClipperChannelSettings, ClipperChannels, ClipperDashboard, ClipperDocumentChangeReasons, ClipperDocumentContainer, ClipperExportDocumentsFormat, ClipperFacet, ClipperMessages, ClipperModel, ClipperModels, ClipperModule, ClipperModuleGroup, ClipperModuleGroups, ClipperModules, ClipperQueryDocumentFlags, ClipperQueryReferencesMode, ClipperRecurrenceType, ClipperRecurrenceTypes, ClipperRegions, ClipperSearchCalendarSnapshotResult, ClipperSearchFacetsSnapshot, ClipperSearchParams, ClipperSearchResult, ClipperSearchUtils, ClipperSectorTypes, ClipperSectors, ClipperSelectionMode, ClipperService, ClipperServiceFlags, ClipperSort, ClipperSources, ClipperTeamInfo, ClipperTeamProduct, ClipperTeamProductPermission, ClipperUpdateChannelsStateParams, ClipperUtils, NotesColors };
3772
+ export { ClipperAccountService, ClipperArchiveCopyMode, ClipperArchiveFileStorageType, ClipperArchiveFileStorageTypes, ClipperArchiveFileType, ClipperArchiveFileTypes, ClipperArchiveFilesSearchParams, ClipperArchiveFoldersSearchParams, ClipperArchiveService, ClipperAuthInterceptor, ClipperAuthors, ClipperCalendarCopyMode, ClipperCalendarSearchParams, ClipperCalendarService, ClipperCalendarState, ClipperCalendarStates, ClipperChannel, ClipperChannelSettings, ClipperChannels, ClipperCollaborationService, ClipperDashboard, ClipperDocumentChangeReasons, ClipperDocumentContainer, ClipperDocumentsService, ClipperExportDocumentsFormat, ClipperFacet, ClipperLoginService, ClipperMessages, ClipperModel, ClipperModels, ClipperModule, ClipperModuleGroup, ClipperModuleGroups, ClipperModules, ClipperQueryDocumentFlags, ClipperQueryReferencesMode, ClipperRecurrenceType, ClipperRecurrenceTypes, ClipperRegions, ClipperSearchCalendarSnapshotResult, ClipperSearchFacetsSnapshot, ClipperSearchParams, ClipperSearchResult, ClipperSearchUtils, ClipperSectorTypes, ClipperSectors, ClipperSelectionMode, ClipperService, ClipperServiceFlags, ClipperSort, ClipperSources, ClipperTeamInfo, ClipperTeamProduct, ClipperTeamProductPermission, ClipperUpdateChannelsStateParams, ClipperUtils, NotesColors };
3455
3773
  //# sourceMappingURL=arsedizioni-ars-utils-clipper.common.mjs.map