diff --git a/src/app/editor/components/block/blocks/bookmark-block.component.ts b/src/app/editor/components/block/blocks/bookmark-block.component.ts index b7ec1e0..3662ab4 100644 --- a/src/app/editor/components/block/blocks/bookmark-block.component.ts +++ b/src/app/editor/components/block/blocks/bookmark-block.component.ts @@ -127,7 +127,19 @@ import { UrlPreviewService } from '../../../services/url-preview.service'; `, }) export class BookmarkBlockComponent implements AfterViewInit, OnDestroy { - @Input({ required: true }) block!: Block; + private _block!: Block; + + @Input({ required: true }) + set block(value: Block) { + this._block = value; + // Update viewMode immediately when block changes + this.viewMode = this.props.viewMode || 'card'; + } + + get block(): Block { + return this._block; + } + @Output() update = new EventEmitter(); @Input() compact = false; @@ -145,9 +157,7 @@ export class BookmarkBlockComponent implements AfterViewInit, OnDestroy { return this.block.props; } - get viewMode(): 'card' | 'tile' | 'cover' { - return this.props.viewMode || 'card'; - } + viewMode: 'card' | 'tile' | 'cover' = 'card'; get backgroundColor(): string | null { const meta: any = this.block.meta || {}; diff --git a/src/app/editor/components/block/blocks/embed-block.component.ts b/src/app/editor/components/block/blocks/embed-block.component.ts index caf6d30..e0538bd 100644 --- a/src/app/editor/components/block/blocks/embed-block.component.ts +++ b/src/app/editor/components/block/blocks/embed-block.component.ts @@ -12,8 +12,8 @@ import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; @if (props.url) {
- @if (showHandle) { - + +
Redimensionner
-
-
-
-
- } +
+
+
+
+
{{ props.url }} @@ -188,17 +191,54 @@ export class EmbedBlockComponent { getSafeUrl(): SafeResourceUrl { // Transform URLs for embedding let url = this.props.url; - - // YouTube - if (url.includes('youtube.com/watch')) { - const videoId = new URL(url).searchParams.get('v'); - return this.sanitizer.bypassSecurityTrustResourceUrl(`https://www.youtube.com/embed/${videoId}`); + + // Normalize to handle common YouTube URL shapes and generate + // standard https://www.youtube.com/embed/VIDEO_ID URLs. + try { + const u = new URL(url); + const host = u.host; + + // Standard watch URL: https://www.youtube.com/watch?v=VIDEO_ID + if (host.includes('youtube.com') && u.pathname === '/watch') { + const videoId = u.searchParams.get('v'); + if (videoId) { + return this.sanitizer.bypassSecurityTrustResourceUrl( + `https://www.youtube.com/embed/${videoId}?rel=0` + ); + } + } + + // Short URL: https://youtu.be/VIDEO_ID + if (host === 'youtu.be') { + const parts = u.pathname.split('/').filter(Boolean); + const videoId = parts[0]; + if (videoId) { + return this.sanitizer.bypassSecurityTrustResourceUrl( + `https://www.youtube.com/embed/${videoId}?rel=0` + ); + } + } + + // Shorts URL: https://www.youtube.com/shorts/VIDEO_ID + if (host.includes('youtube.com') && u.pathname.startsWith('/shorts/')) { + const parts = u.pathname.split('/').filter(Boolean); + const videoId = parts[1]; + if (videoId) { + return this.sanitizer.bypassSecurityTrustResourceUrl( + `https://www.youtube.com/embed/${videoId}?rel=0` + ); + } + } + + // Already an embed URL: keep it but sanitize + if (host.includes('youtube.com') && u.pathname.startsWith('/embed/')) { + return this.sanitizer.bypassSecurityTrustResourceUrl(url); + } + } catch { + // If URL parsing fails, fallback to the raw value below. } - if (url.includes('youtu.be/')) { - const videoId = url.split('youtu.be/')[1].split('?')[0]; - return this.sanitizer.bypassSecurityTrustResourceUrl(`https://www.youtube.com/embed/${videoId}`); - } - + + // Fallback: trust the original URL as-is (for other providers). return this.sanitizer.bypassSecurityTrustResourceUrl(url); } diff --git a/src/app/features/tests/tests-panel.component.ts b/src/app/features/tests/tests-panel.component.ts index 0ee13d1..810b3be 100644 --- a/src/app/features/tests/tests-panel.component.ts +++ b/src/app/features/tests/tests-panel.component.ts @@ -31,12 +31,39 @@ interface TestResult {

-
- -
- +
+
+ +
-

🔭 All APIs Explorer

+

⚡ Quick checks

+
+ + + + + + +
+

+ Results and timings appear on the right. Use filters there to focus on failed calls. +

+
+ +
+

🔭 API Explorer

@@ -53,7 +80,6 @@ interface TestResult {
-
@@ -102,27 +128,44 @@ interface TestResult {
-
-

Test Results

- +
+

Test Results

+

No tests yet. Use a quick check or the explorer to start.

+

Newest first • {{ results().length }} result{{ results().length > 1 ? 's' : '' }}

+
+
+
+ + + + +
+ +
-
-
+
+
{{ result.method }} {{ result.endpoint }} {{ result.status.toUpperCase() }} ({{ result.duration }}ms)
-
+
+ {{ result.timestamp | date:'mediumTime' }} + +
+
{{ result.response ? (result.response | json) : result.error }}
@@ -130,7 +173,8 @@ interface TestResult {
-

No tests run yet. Click a button above to start testing!

+

No tests run yet.

+

Start with a quick health check or run the full smoke suite.

@@ -174,11 +218,10 @@ interface TestResult { margin: 0 auto; } - .panel-content { - display: grid; - gap: 30px; - grid-template-columns: minmax(0, 1fr) 520px; - align-items: start; + .panel-layout { + display: flex; + flex-direction: column; + gap: 20px; } .test-sections { @@ -200,6 +243,22 @@ interface TestResult { color: var(--text-main); } + .quick-actions { + display: flex; + flex-wrap: wrap; + gap: 10px; + } + + .quick-actions__full { + flex: 1 1 100%; + } + + .quick-actions__hint { + margin-top: 8px; + font-size: 12px; + color: var(--text-muted); + } + .test-group { display: flex; flex-wrap: wrap; @@ -322,11 +381,10 @@ interface TestResult { border: 1px solid var(--border); border-radius: 8px; background: var(--card); - position: sticky; - top: 20px; - max-height: calc(100vh - 60px); display: grid; grid-template-rows: auto 1fr; + position: static; + z-index: 0; } .results-header { @@ -344,6 +402,55 @@ interface TestResult { color: var(--text-main); } + .results-subtitle { + margin: 4px 0 0; + font-size: 12px; + color: var(--text-muted); + } + + .results-tools { + display: flex; + gap: 10px; + align-items: center; + } + + .results-filters { + display: flex; + flex-wrap: wrap; + gap: 6px; + } + + .filter-chip { + padding: 2px 10px; + border-radius: 999px; + border: 1px solid var(--border); + background: var(--surface-1); + font-size: 12px; + cursor: pointer; + color: var(--text-muted); + } + + .filter-chip--success { + border-color: color-mix(in srgb, var(--success) 70%, var(--border)); + color: var(--success); + } + + .filter-chip--error { + border-color: color-mix(in srgb, var(--danger) 70%, var(--border)); + color: var(--danger); + } + + .filter-chip--running { + border-color: color-mix(in srgb, var(--warning) 70%, var(--border)); + color: var(--warning); + } + + .filter-chip--active { + background: color-mix(in srgb, var(--primary) 25%, transparent); + color: var(--text-main); + border-color: color-mix(in srgb, var(--primary) 80%, var(--border)); + } + .clear-btn { padding: 6px 12px; background: var(--danger); @@ -365,8 +472,7 @@ interface TestResult { .results-list { overflow-y: auto; - height: 100%; - max-height: none; + max-height: 500px; } .result-item { @@ -454,6 +560,29 @@ interface TestResult { color: var(--text-muted); } + .result-meta { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + margin-top: 4px; + } + + .result-timestamp { + font-size: 12px; + color: var(--text-muted); + } + + .result-toggle { + padding: 2px 10px; + border-radius: 999px; + border: 1px solid var(--border); + background: transparent; + font-size: 12px; + cursor: pointer; + color: var(--text-muted); + } + .result-details { margin-top: 10px; } @@ -529,13 +658,6 @@ interface TestResult { } @media (max-width: 1024px) { - .panel-content { - grid-template-columns: 1fr; - } - .results-panel { - position: static; - max-height: none; - } .results-list { max-height: 400px; } @@ -552,13 +674,21 @@ interface TestResult { padding: 12px 12px 12px 12px; } - .tests-panel--fullscreen .panel-content { + .tests-panel--fullscreen .panel-layout { + display: grid; + grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); + gap: 24px; + align-items: start; height: calc(100vh - 100px); } .tests-panel--fullscreen .test-sections { overflow: auto; } + + .tests-panel--fullscreen .results-panel { + align-self: stretch; + } `] }) export class TestsPanelComponent { @@ -574,6 +704,10 @@ export class TestsPanelComponent { sseEvents = signal>([]); isRunning = signal(false); + // UI state for results panel + private statusFilter = signal<'all' | 'success' | 'error' | 'running'>('all'); + private expandedResults = signal>(new Set()); + // SSE connection private sseConnection: EventSource | null = null; isFullscreen = signal(false); @@ -726,6 +860,39 @@ export class TestsPanelComponent { return sp.toString(); } + // ---------- Results helpers ---------- + + setStatusFilter(filter: 'all' | 'success' | 'error' | 'running'): void { + this.statusFilter.set(filter); + } + + isStatusFilterActive(filter: 'all' | 'success' | 'error' | 'running'): boolean { + return this.statusFilter() === filter; + } + + getFilteredResults(): TestResult[] { + const filter = this.statusFilter(); + const all = this.results(); + if (filter === 'all') { + return all; + } + return all.filter(r => r.status === filter); + } + + toggleResultDetails(result: TestResult): void { + const current = new Set(this.expandedResults()); + if (current.has(result)) { + current.delete(result); + } else { + current.add(result); + } + this.expandedResults.set(current); + } + + isResultExpanded(result: TestResult): boolean { + return this.expandedResults().has(result); + } + async runDynamicRequest(): Promise { const ep = this.currentEndpoint; if (!ep) return; diff --git a/vault/folder-4/NewPage.md b/vault/folder-4/NewPage.md new file mode 100644 index 0000000..57b6972 --- /dev/null +++ b/vault/folder-4/NewPage.md @@ -0,0 +1,17 @@ +--- +titre: NewPage +auteur: Bruno Charest +creation_date: 2025-11-21T02:01:06.468Z +modification_date: 2025-11-20T21:01:07-04:00 +catégorie: "" +tags: [] +aliases: [] +status: en-cours +publish: false +favoris: false +template: false +task: false +archive: false +draft: false +private: false +--- diff --git a/vault/folder-4/NewPage.md.bak b/vault/folder-4/NewPage.md.bak new file mode 100644 index 0000000..354eafe --- /dev/null +++ b/vault/folder-4/NewPage.md.bak @@ -0,0 +1,15 @@ +--- +titre: "NewPage" +auteur: "Bruno Charest" +creation_date: "2025-11-21T02:01:06.468Z" +modification_date: "2025-11-21T02:01:06.468Z" +status: "en-cours" +publish: false +favoris: false +template: false +task: false +archive: false +draft: false +private: false +--- + diff --git a/vault/test-file.md b/vault/test-file.md index cf577a2..0d7d452 100644 --- a/vault/test-file.md +++ b/vault/test-file.md @@ -1,8 +1,8 @@ --- titre: test-file auteur: Bruno Charest -creation_date: 2025-10-23T13:00:42-04:00 -modification_date: 2025-10-23T13:00:42-04:00 +creation_date: 2025-11-20T16:27:20-04:00 +modification_date: 2025-11-20T16:27:20-04:00 catégorie: "" tags: [] aliases: [] diff --git a/vault/test-file.md.bak b/vault/test-file.md.bak new file mode 100644 index 0000000..1d825a7 --- /dev/null +++ b/vault/test-file.md.bak @@ -0,0 +1,3 @@ +# Test File + +This is a test file for API testing. \ No newline at end of file diff --git a/vault/tests/nimbus-editor-snapshot.md b/vault/tests/nimbus-editor-snapshot.md index 63eb81a..402b67e 100644 --- a/vault/tests/nimbus-editor-snapshot.md +++ b/vault/tests/nimbus-editor-snapshot.md @@ -2,29 +2,21 @@ title: "Éditeur Nimbus — Section Tests" nimbusEditor: true documentModelFormat: "block-model-v1" +tags: + - test2 + - configuration + - bruno + - titi --- ```json { "id": "block_1763149113471_461xyut80", "title": "Page Tests", - "blocks": [ - { - "id": "block_1763664525710_yg13o2ra5", - "type": "embed", - "props": { - "url": "https://www.youtube.com/watch?v=9z1GInFvwA0", - "provider": "generic" - }, - "meta": { - "createdAt": "2025-11-20T18:48:45.710Z", - "updatedAt": "2025-11-20T18:48:53.317Z" - } - } - ], + "blocks": [], "meta": { "createdAt": "2025-11-14T19:38:33.471Z", - "updatedAt": "2025-11-20T18:48:53.317Z" + "updatedAt": "2025-11-21T02:28:15.835Z" } } ```