mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2026-02-06 09:55:47 +00:00
fix: address GitHub Copilot review feedback
- Add missing dependencies to useEditor in TextView.tsx (items, getItemColor, addFilterTag) - Remove redundant useEffect that duplicated editor initialization - Update all TipTap packages to v3.15.3 for version consistency - Make YouTube video ID pattern more flexible (10-12 chars instead of exactly 11) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0ef35df335
commit
b052a877cd
376
TESTING_STRATEGY.md
Normal file
376
TESTING_STRATEGY.md
Normal file
@ -0,0 +1,376 @@
|
||||
# Teststrategie für TipTap Markdown-Migration
|
||||
|
||||
## Zusammenfassung der Analyse
|
||||
|
||||
Die TipTap-Migration umfasst folgende Kernkomponenten:
|
||||
|
||||
| Komponente | Beschreibung | Komplexität |
|
||||
|------------|--------------|-------------|
|
||||
| `lib/src/Components/TipTap/utils/preprocessMarkdown.ts` | 6-stufige Preprocessing-Pipeline | Hoch |
|
||||
| `lib/src/Components/TipTap/utils/simpleMarkdownToHtml.tsx` | Statische HTML-Konvertierung | Mittel |
|
||||
| `lib/src/Components/TipTap/extensions/Hashtag.tsx` | Custom Extension mit Tokenizer | Mittel |
|
||||
| `lib/src/Components/TipTap/extensions/ItemMention.tsx` | Custom Extension mit Tokenizer | Mittel |
|
||||
| `lib/src/Components/TipTap/extensions/VideoEmbed.tsx` | Block-Element für Videos | Mittel |
|
||||
| `lib/src/Components/Input/RichTextEditor.tsx` | Haupt-Editor-Komponente | Hoch |
|
||||
| `lib/src/Components/Map/Subcomponents/ItemPopupComponents/TextView.tsx` | Read-Only Editor | Mittel |
|
||||
| `lib/src/Components/Map/Subcomponents/ItemPopupComponents/TextViewStatic.tsx` | Lightweight Static Renderer | Niedrig |
|
||||
|
||||
---
|
||||
|
||||
## Empfohlene Teststrategie: Testing Pyramid
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ E2E Tests │ ← Wenige, kritische User Journeys
|
||||
│ (Cypress) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌────────┴────────┐
|
||||
│ Integration │ ← TipTap + Extensions zusammen
|
||||
│ Tests (Vitest) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌───────────────────┴───────────────────┐
|
||||
│ Unit Tests (Vitest) │ ← Utility-Funktionen isoliert
|
||||
│ preprocessMarkdown, simpleMarkdownToHtml │
|
||||
└────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Begründung der Strategie
|
||||
|
||||
1. **Unit Tests für Utility-Funktionen (Hauptfokus)**
|
||||
- `preprocessMarkdown.ts` und `simpleMarkdownToHtml.tsx` sind **pure Funktionen** ohne Abhängigkeiten
|
||||
- Extrem schnelle Ausführung, hohe Coverage möglich
|
||||
- Einfach zu warten und zu debuggen
|
||||
- Hier liegt die meiste **Geschäftslogik** der Markdown-Verarbeitung
|
||||
|
||||
2. **Integration Tests für TipTap Extensions**
|
||||
- Extensions benötigen einen Editor-Kontext
|
||||
- Testen der Markdown ↔ JSON ↔ HTML Roundtrips
|
||||
- Mäßiger Aufwand, gute Fehlererkennung
|
||||
|
||||
3. **E2E Tests nur für kritische User Journeys**
|
||||
- Hoher Wartungsaufwand
|
||||
- Langsame Ausführung
|
||||
- Für Smoke Tests und Regressionsschutz
|
||||
|
||||
---
|
||||
|
||||
## Detaillierte Testfälle
|
||||
|
||||
### 1. Unit Tests für `preprocessMarkdown.ts`
|
||||
|
||||
#### A) `convertNakedUrls`
|
||||
|
||||
| Testfall | Input | Expected Output |
|
||||
|----------|-------|-----------------|
|
||||
| **Happy Path** | `Check https://example.com out` | `Check [example.com](https://example.com) out` |
|
||||
| **www entfernen** | `https://www.example.com` | `[example.com](https://example.com)` |
|
||||
| **URL in Markdown-Link (Skip)** | `[link](https://example.com)` | Unverändert |
|
||||
| **URL in Autolink (Skip)** | `<https://example.com>` | Unverändert |
|
||||
| **Mehrere URLs** | `https://a.com and https://b.com` | Beide konvertiert |
|
||||
| **URL am Satzende mit Punkt** | `Visit https://example.com.` | Punkt nicht Teil der URL |
|
||||
| **URL mit Klammern** | `(https://example.com)` | Klammern korrekt behandelt |
|
||||
| **URL mit Query-Params** | `https://example.com?a=1&b=2` | Vollständig konvertiert |
|
||||
|
||||
#### B) `preprocessVideoLinks`
|
||||
|
||||
| Testfall | Input | Expected Output |
|
||||
|----------|-------|-----------------|
|
||||
| **YouTube Standard** | `<https://www.youtube.com/watch?v=abc123>` | `<video-embed provider="youtube" video-id="abc123">` |
|
||||
| **YouTube Short URL** | `<https://youtu.be/abc123>` | Korrekt konvertiert |
|
||||
| **YouTube Markdown Link** | `[Video](https://youtube.com/watch?v=abc123)` | Korrekt konvertiert |
|
||||
| **Rumble Embed** | `<https://rumble.com/embed/xyz789>` | `<video-embed provider="rumble"...>` |
|
||||
| **URL mit Extra-Params** | `<https://youtube.com/watch?v=abc&t=120>` | Nur Video-ID extrahiert |
|
||||
| **Kein Video-Link** | `<https://example.com>` | Unverändert |
|
||||
| **Gemischter Content** | `Text <https://youtu.be/x> more` | Nur Video konvertiert |
|
||||
|
||||
#### C) `preprocessHashtags`
|
||||
|
||||
| Testfall | Input | Expected Output |
|
||||
|----------|-------|-----------------|
|
||||
| **Einfacher Hashtag** | `Hello #world` | `Hello <span data-hashtag...>#world</span>` |
|
||||
| **Hashtag mit Umlauten** | `#München` | Korrekt erkannt |
|
||||
| **Hashtag mit Zahlen** | `#test123` | Korrekt erkannt |
|
||||
| **Hashtag in Link (Skip)** | `[#tag](#anchor)` | Unverändert |
|
||||
| **Hashtag nach Klammer (Skip)** | `(#section)` | Unverändert |
|
||||
| **Mehrere Hashtags** | `#one #two #three` | Alle konvertiert |
|
||||
| **Ungültiger Hashtag** | `#` | Unverändert (kein Text) |
|
||||
| **Hashtag mit Underscore** | `#my_tag` | Korrekt erkannt |
|
||||
|
||||
#### D) `preprocessItemMentions`
|
||||
|
||||
| Testfall | Input | Expected Output |
|
||||
|----------|-------|-----------------|
|
||||
| **Standard Format** | `[@Person](/item/uuid-123)` | `<span data-item-mention...>@Person</span>` |
|
||||
| **Mit Layer (Legacy)** | `[@Name](/item/layer/uuid)` | Korrekt konvertiert |
|
||||
| **Relativer Pfad** | `[@Name](item/uuid)` | Korrekt konvertiert |
|
||||
| **Mehrere Mentions** | `[@A](/item/1) und [@B](/item/2)` | Beide konvertiert |
|
||||
| **Kein Item-Link** | `[@Name](/other/path)` | Unverändert |
|
||||
| **UUID Case-Insensitive** | `[@Name](/item/ABC-def-123)` | Korrekt erkannt |
|
||||
| **Label mit Sonderzeichen** | `[@Max Müller](/item/uuid)` | Korrekt konvertiert |
|
||||
|
||||
#### E) `truncateMarkdown`
|
||||
|
||||
| Testfall | Input | Limit | Expected |
|
||||
|----------|-------|-------|----------|
|
||||
| **Unter Limit** | `Short text` | 100 | Unverändert |
|
||||
| **Über Limit (Plain)** | `A very long text...` | 10 | `A very lo...` |
|
||||
| **Hashtag nicht schneiden** | `Text #verylonghashtag more` | 15 | Vollständiger Hashtag oder davor abschneiden |
|
||||
| **Mention nicht schneiden** | `Hi [@Person](/item/x) bye` | 10 | Vollständige Mention oder davor |
|
||||
| **Link nicht schneiden** | `See [link](url) more` | 8 | Vollständiger Link oder davor |
|
||||
| **Newlines nicht zählen** | `Line1\n\nLine2` | 10 | Newlines ignoriert bei Zählung |
|
||||
| **Gemischter Content** | `#tag [@m](/item/1) text` | 20 | Tokens atomar |
|
||||
|
||||
#### F) `removeMarkdownSyntax`
|
||||
|
||||
| Testfall | Input | Expected |
|
||||
|----------|-------|----------|
|
||||
| **Bold** | `**bold**` | `bold` |
|
||||
| **Italic** | `*italic*` | `italic` |
|
||||
| **Headers** | `# Heading` | `Heading` |
|
||||
| **Links** | `[text](url)` | `text` |
|
||||
| **Item Mentions erhalten** | `[@Name](/item/x)` | Erhalten |
|
||||
| **Bilder entfernen** | `` | Leer |
|
||||
| **Code** | `` `code` `` | `code` |
|
||||
|
||||
---
|
||||
|
||||
### 2. Unit Tests für `simpleMarkdownToHtml.tsx`
|
||||
|
||||
| Testfall | Input | Expected HTML |
|
||||
|----------|-------|---------------|
|
||||
| **Bold** | `**bold**` | `<strong>bold</strong>` |
|
||||
| **Italic** | `*italic*` | `<em>italic</em>` |
|
||||
| **Link** | `[text](url)` | `<a href="url">text</a>` |
|
||||
| **External Link** | `[text](https://ext.com)` | `<a href="..." target="_blank"...>` |
|
||||
| **Header H1** | `# Title` | `<h1>Title</h1>` |
|
||||
| **Header H2-H6** | `## ... ######` | Entsprechende h-Tags |
|
||||
| **Inline Code** | `` `code` `` | `<code>code</code>` |
|
||||
| **Blockquote** | `> quote` | `<blockquote>quote</blockquote>` |
|
||||
| **Video Embed** | `<video-embed provider="youtube"...>` | `<iframe src="youtube-nocookie...">` |
|
||||
| **Hashtag mit Farbe** | Preprocessed Hashtag + Tag mit color | Style mit korrekter Farbe |
|
||||
| **Item Mention** | Preprocessed Mention + Item | Link mit korrekter Farbe |
|
||||
| **XSS Prevention** | `<script>alert('xss')</script>` | Escaped, kein Script-Tag |
|
||||
| **Newlines** | `Line1\n\nLine2` | `</p><p>` Trennung |
|
||||
|
||||
---
|
||||
|
||||
### 3. Integration Tests für TipTap Extensions
|
||||
|
||||
Diese Tests benötigen einen TipTap Editor-Kontext. Setup via `@tiptap/core`:
|
||||
|
||||
```typescript
|
||||
// Test-Setup Beispiel
|
||||
import { Editor } from '@tiptap/core'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import { Markdown } from '@tiptap/markdown'
|
||||
import { Hashtag } from './Hashtag'
|
||||
```
|
||||
|
||||
#### A) Hashtag Extension
|
||||
|
||||
| Testfall | Beschreibung |
|
||||
|----------|--------------|
|
||||
| **Markdown → JSON** | `#tag` wird zu `{ type: 'hashtag', attrs: { label: 'tag' } }` |
|
||||
| **JSON → Markdown** | Hashtag-Node wird zu `#tag` serialisiert |
|
||||
| **HTML Parse** | `<span data-hashtag data-label="x">#x</span>` wird erkannt |
|
||||
| **HTML Render** | Node rendert korrekte HTML-Struktur |
|
||||
| **Tokenizer Start** | `/(?<!\[)#[a-zA-Z]/` matched korrekt |
|
||||
| **Click Handler (read-only)** | `onTagClick` wird aufgerufen |
|
||||
| **No Click (editable)** | Kein Click-Handler im Edit-Modus |
|
||||
|
||||
#### B) ItemMention Extension
|
||||
|
||||
| Testfall | Beschreibung |
|
||||
|----------|--------------|
|
||||
| **Markdown → JSON** | `[@Name](/item/uuid)` wird zu korrektem Node |
|
||||
| **JSON → Markdown** | Node wird korrekt serialisiert |
|
||||
| **Mit Layer** | Legacy-Format wird korrekt geparst |
|
||||
| **UUID Case-Insensitive** | Groß-/Kleinschreibung egal |
|
||||
| **Farbe aus Item** | `getItemColor` wird korrekt verwendet |
|
||||
|
||||
#### C) VideoEmbed Extension
|
||||
|
||||
| Testfall | Beschreibung |
|
||||
|----------|--------------|
|
||||
| **YouTube Parse** | Autolink zu Video-Node |
|
||||
| **Rumble Parse** | Embed-URL zu Video-Node |
|
||||
| **Iframe Render** | Korrekter `youtube-nocookie.com` Embed |
|
||||
| **Paste Handler** | Video-URL wird beim Einfügen erkannt |
|
||||
|
||||
#### D) Roundtrip Tests
|
||||
|
||||
| Testfall | Flow |
|
||||
|----------|------|
|
||||
| **Markdown Roundtrip** | Markdown → Editor → `getMarkdown()` → identisch |
|
||||
| **Komplexer Content** | Text + #tag + @mention + Video → Roundtrip |
|
||||
| **Preserve Formatting** | Bold, Italic, Listen bleiben erhalten |
|
||||
|
||||
---
|
||||
|
||||
### 4. Component Tests (React Testing Library)
|
||||
|
||||
#### RichTextEditor
|
||||
|
||||
| Testfall | Beschreibung |
|
||||
|----------|--------------|
|
||||
| **Render mit Default** | Editor rendert mit initialem Content |
|
||||
| **onChange Callback** | `updateFormValue` erhält Markdown |
|
||||
| **Placeholder** | Placeholder wird angezeigt |
|
||||
| **Hashtag Suggestion** | `#` triggert Suggestion Popup |
|
||||
| **Item Mention Suggestion** | `@` triggert Suggestion Popup |
|
||||
| **Keyboard Navigation** | Arrow Keys in Suggestions |
|
||||
| **Suggestion Select** | Enter fügt Tag ein |
|
||||
|
||||
#### TextView
|
||||
|
||||
| Testfall | Beschreibung |
|
||||
|----------|--------------|
|
||||
| **Read-Only** | Editor ist nicht editierbar |
|
||||
| **Truncation** | Langer Text wird gekürzt + `...` |
|
||||
| **Hashtag Click** | `addFilterTag` wird aufgerufen |
|
||||
| **Internal Link** | React Router Navigation |
|
||||
| **External Link** | Neuer Tab |
|
||||
|
||||
#### TextViewStatic
|
||||
|
||||
| Testfall | Beschreibung |
|
||||
|----------|--------------|
|
||||
| **HTML Render** | Markdown wird zu HTML |
|
||||
| **Hashtag Farbe** | Farbe aus Tags-Array |
|
||||
| **Item Mention Link** | Korrekter `/item/` Link |
|
||||
| **Video Embed** | Iframe wird gerendert |
|
||||
|
||||
---
|
||||
|
||||
### 5. E2E Tests (Cypress)
|
||||
|
||||
Nur **kritische User Journeys** - minimaler Scope für maximale Stabilität:
|
||||
|
||||
#### A) Editor Flow
|
||||
|
||||
```typescript
|
||||
describe('Rich Text Editor', () => {
|
||||
it('should create and save content with hashtags and mentions', () => {
|
||||
// 1. Neues Item erstellen
|
||||
// 2. Text eingeben mit #tag und @mention
|
||||
// 3. Speichern
|
||||
// 4. Popup öffnen und Rendering prüfen
|
||||
})
|
||||
|
||||
it('should handle video embeds', () => {
|
||||
// YouTube URL einfügen → Video-Embed sichtbar
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### B) Display Flow
|
||||
|
||||
```typescript
|
||||
describe('Text Display', () => {
|
||||
it('should render hashtags clickable in popup', () => {
|
||||
// Item mit Hashtag öffnen
|
||||
// Hashtag klicken
|
||||
// Filter wird aktiviert
|
||||
})
|
||||
|
||||
it('should navigate to mentioned items', () => {
|
||||
// Item mit @mention öffnen
|
||||
// Mention klicken
|
||||
// Navigation zum verlinkten Item
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Priorisierung der Implementierung
|
||||
|
||||
### Phase 1: Unit Tests (Höchste Priorität)
|
||||
|
||||
| Datei | Geschätzte Tests | Grund |
|
||||
|-------|------------------|-------|
|
||||
| `preprocessMarkdown.spec.ts` | ~40 Tests | Pure Functions, schnell, hohe Coverage |
|
||||
| `simpleMarkdownToHtml.spec.ts` | ~25 Tests | Pure Function, XSS-kritisch |
|
||||
|
||||
### Phase 2: Integration Tests
|
||||
|
||||
| Datei | Geschätzte Tests | Grund |
|
||||
|-------|------------------|-------|
|
||||
| `Hashtag.spec.ts` | ~15 Tests | Custom Extension mit Tokenizer |
|
||||
| `ItemMention.spec.ts` | ~15 Tests | Custom Extension mit Tokenizer |
|
||||
| `VideoEmbed.spec.ts` | ~10 Tests | Block-Element |
|
||||
|
||||
### Phase 3: Component Tests
|
||||
|
||||
| Datei | Geschätzte Tests | Grund |
|
||||
|-------|------------------|-------|
|
||||
| `RichTextEditor.spec.tsx` | ~15 Tests | Haupt-Editor |
|
||||
| `TextView.spec.tsx` | ~10 Tests | Read-Only Variante |
|
||||
| `TextViewStatic.spec.tsx` | ~8 Tests | Lightweight Renderer |
|
||||
|
||||
### Phase 4: E2E Tests (Niedrigste Priorität)
|
||||
|
||||
| Datei | Geschätzte Tests | Grund |
|
||||
|-------|------------------|-------|
|
||||
| `editor-flow.cy.ts` | 3-5 Tests | Kritische User Journey |
|
||||
|
||||
---
|
||||
|
||||
## Edge Cases und Error Handling
|
||||
|
||||
### Besonders wichtige Grenzfälle
|
||||
|
||||
1. **Leerer Input** - Alle Funktionen sollten mit `''`, `null`, `undefined` umgehen
|
||||
2. **Sehr langer Text** - Performance bei >10.000 Zeichen
|
||||
3. **Verschachtelte Syntax** - `**#bold-hashtag**`, `[[@mention](/item/x)](url)`
|
||||
4. **Unicode** - Emojis, RTL-Text, Sonderzeichen
|
||||
5. **Malformed Markdown** - Ungeschlossene Tags: `**bold`, `[link(`
|
||||
6. **XSS Vectors** - `<script>`, Event-Handler in Links
|
||||
7. **Concurrent Tokens** - `#tag1#tag2` (ohne Leerzeichen)
|
||||
8. **URLs in Code-Blöcken** - Sollten nicht konvertiert werden
|
||||
|
||||
---
|
||||
|
||||
## Test-Setup Empfehlungen
|
||||
|
||||
### Vitest Setup erweitern (`lib/setupTest.ts`)
|
||||
|
||||
```typescript
|
||||
import '@testing-library/jest-dom'
|
||||
import { vi } from 'vitest'
|
||||
|
||||
// TipTap DOM-Mocks für Vitest (basierend auf Community-Empfehlungen)
|
||||
Range.prototype.getBoundingClientRect = () => ({
|
||||
bottom: 0, height: 0, left: 0, right: 0, top: 0, width: 0, x: 0, y: 0,
|
||||
toJSON: vi.fn(),
|
||||
})
|
||||
Range.prototype.getClientRects = () => ({
|
||||
item: () => null, length: 0, [Symbol.iterator]: vi.fn(),
|
||||
})
|
||||
Document.prototype.elementFromPoint = vi.fn()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fazit
|
||||
|
||||
Die empfohlene Strategie fokussiert auf **Unit Tests für die Markdown-Utility-Funktionen**, da hier:
|
||||
- Die meiste Geschäftslogik liegt
|
||||
- Pure Functions einfach testbar sind
|
||||
- Hohe Coverage mit geringem Aufwand erreichbar ist
|
||||
|
||||
E2E Tests sollten auf ein Minimum beschränkt bleiben und nur kritische User Journeys abdecken. Dies folgt der Testing Pyramid Best Practice und sorgt für:
|
||||
- **Schnelles Feedback** (Unit Tests <1s)
|
||||
- **Hohe Wartbarkeit** (keine flaky UI-Tests)
|
||||
- **Gute Fehlerlokalisierung** (isolierte Tests)
|
||||
|
||||
---
|
||||
|
||||
## Quellen
|
||||
|
||||
- [TipTap Testing Discussion #4008](https://github.com/ueberdosis/tiptap/discussions/4008)
|
||||
- [TipTap Jest Issue #5108](https://github.com/ueberdosis/tiptap/issues/5108)
|
||||
- [Testing TipTap CodeSandbox](https://codesandbox.io/s/testing-tiptap-p0oomz)
|
||||
- [TipTap Contributing Guide](https://tiptap.dev/docs/resources/contributing)
|
||||
@ -101,16 +101,16 @@
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.0.17",
|
||||
"@maplibre/maplibre-gl-leaflet": "^0.1.3",
|
||||
"@tiptap/core": "^3.13.0",
|
||||
"@tiptap/extension-bubble-menu": "^3.13.0",
|
||||
"@tiptap/extension-image": "^3.13.0",
|
||||
"@tiptap/extension-link": "^3.13.0",
|
||||
"@tiptap/extension-placeholder": "^3.13.0",
|
||||
"@tiptap/extension-youtube": "^3.13.0",
|
||||
"@tiptap/core": "^3.15.3",
|
||||
"@tiptap/extension-bubble-menu": "^3.15.3",
|
||||
"@tiptap/extension-image": "^3.15.3",
|
||||
"@tiptap/extension-link": "^3.15.3",
|
||||
"@tiptap/extension-placeholder": "^3.15.3",
|
||||
"@tiptap/extension-youtube": "^3.15.3",
|
||||
"@tiptap/markdown": "^3.15.3",
|
||||
"@tiptap/pm": "^3.6.5",
|
||||
"@tiptap/react": "^3.13.0",
|
||||
"@tiptap/starter-kit": "^3.13.0",
|
||||
"@tiptap/pm": "^3.15.3",
|
||||
"@tiptap/react": "^3.15.3",
|
||||
"@tiptap/starter-kit": "^3.15.3",
|
||||
"@tiptap/suggestion": "^3.15.3",
|
||||
"axios": "^1.13.2",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
|
||||
@ -86,14 +86,9 @@ export const TextView = ({
|
||||
},
|
||||
},
|
||||
},
|
||||
[innerText, tags],
|
||||
[innerText, tags, items, getItemColor, addFilterTag],
|
||||
)
|
||||
|
||||
// Update content when text changes
|
||||
useEffect(() => {
|
||||
editor.commands.setContent(innerText, { contentType: 'markdown' })
|
||||
}, [editor, innerText])
|
||||
|
||||
// Handle link clicks for internal navigation
|
||||
useEffect(() => {
|
||||
const container = containerRef.current
|
||||
|
||||
@ -6,8 +6,9 @@ import type { NodeViewProps } from '@tiptap/react'
|
||||
|
||||
// Regex patterns for video URL detection
|
||||
// Using possessive-like patterns with specific character classes to avoid ReDoS
|
||||
const YOUTUBE_REGEX = /^https?:\/\/(?:www\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})(?:&|$)/
|
||||
const YOUTUBE_SHORT_REGEX = /^https?:\/\/youtu\.be\/([a-zA-Z0-9_-]{11})(?:\?|$)/
|
||||
// YouTube IDs are typically 11 chars but we allow 10-12 for flexibility
|
||||
const YOUTUBE_REGEX = /^https?:\/\/(?:www\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]{10,12})(?:&|$)/
|
||||
const YOUTUBE_SHORT_REGEX = /^https?:\/\/youtu\.be\/([a-zA-Z0-9_-]{10,12})(?:\?|$)/
|
||||
const RUMBLE_REGEX = /^https?:\/\/rumble\.com\/embed\/([a-zA-Z0-9]+)(?:\/|$)/
|
||||
|
||||
/**
|
||||
@ -71,7 +72,7 @@ export const VideoEmbed = Node.create<VideoEmbedOptions>({
|
||||
},
|
||||
tokenize: (src: string) => {
|
||||
// Match YouTube autolinks: <https://www.youtube.com/watch?v=VIDEO_ID>
|
||||
let match = /^<https?:\/\/(?:www\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})[^>]*>/.exec(
|
||||
let match = /^<https?:\/\/(?:www\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]{10,12})[^>]*>/.exec(
|
||||
src,
|
||||
)
|
||||
if (match) {
|
||||
@ -84,7 +85,7 @@ export const VideoEmbed = Node.create<VideoEmbedOptions>({
|
||||
}
|
||||
|
||||
// Match YouTube short autolinks: <https://youtu.be/VIDEO_ID>
|
||||
match = /^<https?:\/\/youtu\.be\/([a-zA-Z0-9_-]{11})[^>]*>/.exec(src)
|
||||
match = /^<https?:\/\/youtu\.be\/([a-zA-Z0-9_-]{10,12})[^>]*>/.exec(src)
|
||||
if (match) {
|
||||
return {
|
||||
type: 'videoEmbed',
|
||||
|
||||
1179
package-lock.json
generated
1179
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user