2026-03-20 19:17:19 +01:00
|
|
|
/* global Node, HTMLElement */
|
|
|
|
|
|
Add Event entity, create event page, and custom WYSIWYG editor component
- Create Event entity with fields: account, title, description (text), startAt, endAt, address, zipcode, city, eventMainPicture (VichUploader), isOnline, createdAt, updatedAt
- Create EventRepository
- Add migration for event table with all columns
- Add "Creer un evenement" button on account events tab
- Add create event page (/mon-compte/evenement/creer) with full form
- Build custom web component <e-ticket-editor> WYSIWYG editor:
- Toolbar: bold, italic, underline, paragraph, bullet list, remove formatting
- contentEditable div with HTML sync to hidden textarea
- HTML sanitizer (strips disallowed tags, XSS safe)
- Neo-brutalist CSS styling
- CSP compliant (no inline styles)
- Register editor in app.js via customElements.define
- Add editor CSS in app.scss
- Add 16 Event entity tests (all fields + isOnline + picture upload + updatedAt)
- Add 16 editor JS tests (sanitizer + custom element lifecycle)
- Add 3 AccountController tests (create event page, submit, access control)
- Update placeholders to generic examples (no association-specific data)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 12:49:24 +01:00
|
|
|
const TOOLBAR_ACTIONS = [
|
|
|
|
|
{ command: 'bold', icon: '<b>B</b>', title: 'Gras' },
|
|
|
|
|
{ command: 'italic', icon: '<i>I</i>', title: 'Italique' },
|
|
|
|
|
{ command: 'underline', icon: '<u>S</u>', title: 'Souligne' },
|
|
|
|
|
{ separator: true },
|
|
|
|
|
{ command: 'formatBlock', value: 'P', icon: 'P', title: 'Paragraphe' },
|
|
|
|
|
{ separator: true },
|
|
|
|
|
{ command: 'insertUnorderedList', icon: '•', title: 'Liste a puces' },
|
|
|
|
|
{ separator: true },
|
|
|
|
|
{ command: 'removeFormat', icon: '❌', title: 'Supprimer le formatage' },
|
|
|
|
|
]
|
|
|
|
|
|
2026-03-20 16:43:52 +01:00
|
|
|
const ALLOWED_TAGS = new Set([
|
Add Event entity, create event page, and custom WYSIWYG editor component
- Create Event entity with fields: account, title, description (text), startAt, endAt, address, zipcode, city, eventMainPicture (VichUploader), isOnline, createdAt, updatedAt
- Create EventRepository
- Add migration for event table with all columns
- Add "Creer un evenement" button on account events tab
- Add create event page (/mon-compte/evenement/creer) with full form
- Build custom web component <e-ticket-editor> WYSIWYG editor:
- Toolbar: bold, italic, underline, paragraph, bullet list, remove formatting
- contentEditable div with HTML sync to hidden textarea
- HTML sanitizer (strips disallowed tags, XSS safe)
- Neo-brutalist CSS styling
- CSP compliant (no inline styles)
- Register editor in app.js via customElements.define
- Add editor CSS in app.scss
- Add 16 Event entity tests (all fields + isOnline + picture upload + updatedAt)
- Add 16 editor JS tests (sanitizer + custom element lifecycle)
- Add 3 AccountController tests (create event page, submit, access control)
- Update placeholders to generic examples (no association-specific data)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 12:49:24 +01:00
|
|
|
'p', 'br', 'b', 'strong', 'i', 'em', 'u',
|
|
|
|
|
'ul', 'li',
|
2026-03-20 16:43:52 +01:00
|
|
|
])
|
Add Event entity, create event page, and custom WYSIWYG editor component
- Create Event entity with fields: account, title, description (text), startAt, endAt, address, zipcode, city, eventMainPicture (VichUploader), isOnline, createdAt, updatedAt
- Create EventRepository
- Add migration for event table with all columns
- Add "Creer un evenement" button on account events tab
- Add create event page (/mon-compte/evenement/creer) with full form
- Build custom web component <e-ticket-editor> WYSIWYG editor:
- Toolbar: bold, italic, underline, paragraph, bullet list, remove formatting
- contentEditable div with HTML sync to hidden textarea
- HTML sanitizer (strips disallowed tags, XSS safe)
- Neo-brutalist CSS styling
- CSP compliant (no inline styles)
- Register editor in app.js via customElements.define
- Add editor CSS in app.scss
- Add 16 Event entity tests (all fields + isOnline + picture upload + updatedAt)
- Add 16 editor JS tests (sanitizer + custom element lifecycle)
- Add 3 AccountController tests (create event page, submit, access control)
- Update placeholders to generic examples (no association-specific data)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 12:49:24 +01:00
|
|
|
|
Complete TASK_CHECKUP: security, UX, tests, coverage, accessibility, config externalization
Billetterie:
- Partial refund support (STATUS_PARTIALLY_REFUNDED, refundedAmount field, migration)
- Race condition fix: PESSIMISTIC_WRITE lock on stock decrement in transaction
- Idempotency key on PaymentIntent::create, reuse existing PI if stripeSessionId set
- Disable checkout when event ended (server 400 + template hide)
- Webhook deduplication via cache (24h TTL on stripe event.id)
- Email validation (filter_var) in OrderController guest flow
- JSON cart validation (structure check before processing)
- Invitation expiration after 7 days (isExpired method + landing page message)
- Stripe Checkout fallback when JS fails to load (noscript + redirect)
Config externalization:
- Move Stripe fees (STRIPE_FEE_RATE, STRIPE_FEE_FIXED) and admin email (ADMIN_EMAIL) to .env/services.yaml
- Replace all hardcoded contact@e-cosplay.fr across 13 files
- MailerService: getAdminEmail()/getAdminFrom(), default $from=null resolves to admin
UX & Accessibility:
- ARIA tabs: role=tablist/tab/tabpanel, aria-selected, keyboard nav (arrows, Home, End)
- aria-label on cart +/- buttons and editor toolbar buttons
- tabindex=0 on editor toolbar buttons for keyboard access
- data-confirm handler in app.js (was only in admin.js)
- Cart error feedback on checkout failure
- Billet designer save feedback (loading/success/error states)
- Stock polling every 30s with rupture/low stock badges
- Back to event link on payment page
Security:
- HTML sanitizer: BLOCKED_TAGS list (script, style, iframe, svg, etc.) - content fully removed
- Stripe polling timeout (15s max) with fallback redirect
- Rate limiting on public order access (20/5min)
- .catch() on all fetch() calls (sortable, billet-designer)
Tests (92% PHP, 100% JS lines):
- PCOV added to dev Dockerfile
- Test DB setup: .env.test with DATABASE_URL, Redis auth, Meilisearch key
- Rate limiter disabled in test env
- Makefile: test_db_setup, test_db_reset, run_test_php, run_test_coverage_php/js
- New tests: InvitationFlowTest (21), AuditServiceTest (4), ExportServiceTest (9), InvoiceServiceTest (4)
- New tests: SuspendedUserSubscriberTest, RateLimiterSubscriberTest, MeilisearchServiceTest
- New tests: Stripe webhook payment_failed (6) + charge.refunded (6)
- New tests: BilletBuyer refund, User suspended, OrganizerInvitation expiration
- JS tests: stock polling (6), data-confirm (2), copy-url restore (1), editor ARIA (2), XSS (9), tabs keyboard (9)
- ESLint + PHP CS Fixer: 0 errors
- SonarQube exclusions aligned with vitest coverage config
Infra:
- Meilisearch consistency command (app:meilisearch:check-consistency --fix) + cron daily 3am
- MeilisearchService: getAllDocumentIds(), listIndexes()
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 11:14:06 +01:00
|
|
|
const BLOCKED_TAGS = new Set([
|
|
|
|
|
'script', 'style', 'iframe', 'object', 'embed', 'form', 'input', 'select', 'textarea',
|
|
|
|
|
'link', 'meta', 'noscript', 'template', 'svg', 'math',
|
|
|
|
|
])
|
|
|
|
|
|
Add Event entity, create event page, and custom WYSIWYG editor component
- Create Event entity with fields: account, title, description (text), startAt, endAt, address, zipcode, city, eventMainPicture (VichUploader), isOnline, createdAt, updatedAt
- Create EventRepository
- Add migration for event table with all columns
- Add "Creer un evenement" button on account events tab
- Add create event page (/mon-compte/evenement/creer) with full form
- Build custom web component <e-ticket-editor> WYSIWYG editor:
- Toolbar: bold, italic, underline, paragraph, bullet list, remove formatting
- contentEditable div with HTML sync to hidden textarea
- HTML sanitizer (strips disallowed tags, XSS safe)
- Neo-brutalist CSS styling
- CSP compliant (no inline styles)
- Register editor in app.js via customElements.define
- Add editor CSS in app.scss
- Add 16 Event entity tests (all fields + isOnline + picture upload + updatedAt)
- Add 16 editor JS tests (sanitizer + custom element lifecycle)
- Add 3 AccountController tests (create event page, submit, access control)
- Update placeholders to generic examples (no association-specific data)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 12:49:24 +01:00
|
|
|
export function sanitizeHtml(html) {
|
|
|
|
|
const container = document.createElement('div')
|
|
|
|
|
container.innerHTML = html
|
|
|
|
|
const fragment = sanitizeNode(container)
|
|
|
|
|
const wrapper = document.createElement('div')
|
|
|
|
|
wrapper.appendChild(fragment)
|
|
|
|
|
|
|
|
|
|
return wrapper.innerHTML.trim()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function sanitizeNode(node) {
|
|
|
|
|
const fragment = document.createDocumentFragment()
|
|
|
|
|
|
|
|
|
|
for (const child of Array.from(node.childNodes)) {
|
|
|
|
|
if (child.nodeType === Node.TEXT_NODE) {
|
|
|
|
|
fragment.appendChild(document.createTextNode(child.textContent))
|
|
|
|
|
} else if (child.nodeType === Node.ELEMENT_NODE) {
|
|
|
|
|
const tagName = child.tagName.toLowerCase()
|
Complete TASK_CHECKUP: security, UX, tests, coverage, accessibility, config externalization
Billetterie:
- Partial refund support (STATUS_PARTIALLY_REFUNDED, refundedAmount field, migration)
- Race condition fix: PESSIMISTIC_WRITE lock on stock decrement in transaction
- Idempotency key on PaymentIntent::create, reuse existing PI if stripeSessionId set
- Disable checkout when event ended (server 400 + template hide)
- Webhook deduplication via cache (24h TTL on stripe event.id)
- Email validation (filter_var) in OrderController guest flow
- JSON cart validation (structure check before processing)
- Invitation expiration after 7 days (isExpired method + landing page message)
- Stripe Checkout fallback when JS fails to load (noscript + redirect)
Config externalization:
- Move Stripe fees (STRIPE_FEE_RATE, STRIPE_FEE_FIXED) and admin email (ADMIN_EMAIL) to .env/services.yaml
- Replace all hardcoded contact@e-cosplay.fr across 13 files
- MailerService: getAdminEmail()/getAdminFrom(), default $from=null resolves to admin
UX & Accessibility:
- ARIA tabs: role=tablist/tab/tabpanel, aria-selected, keyboard nav (arrows, Home, End)
- aria-label on cart +/- buttons and editor toolbar buttons
- tabindex=0 on editor toolbar buttons for keyboard access
- data-confirm handler in app.js (was only in admin.js)
- Cart error feedback on checkout failure
- Billet designer save feedback (loading/success/error states)
- Stock polling every 30s with rupture/low stock badges
- Back to event link on payment page
Security:
- HTML sanitizer: BLOCKED_TAGS list (script, style, iframe, svg, etc.) - content fully removed
- Stripe polling timeout (15s max) with fallback redirect
- Rate limiting on public order access (20/5min)
- .catch() on all fetch() calls (sortable, billet-designer)
Tests (92% PHP, 100% JS lines):
- PCOV added to dev Dockerfile
- Test DB setup: .env.test with DATABASE_URL, Redis auth, Meilisearch key
- Rate limiter disabled in test env
- Makefile: test_db_setup, test_db_reset, run_test_php, run_test_coverage_php/js
- New tests: InvitationFlowTest (21), AuditServiceTest (4), ExportServiceTest (9), InvoiceServiceTest (4)
- New tests: SuspendedUserSubscriberTest, RateLimiterSubscriberTest, MeilisearchServiceTest
- New tests: Stripe webhook payment_failed (6) + charge.refunded (6)
- New tests: BilletBuyer refund, User suspended, OrganizerInvitation expiration
- JS tests: stock polling (6), data-confirm (2), copy-url restore (1), editor ARIA (2), XSS (9), tabs keyboard (9)
- ESLint + PHP CS Fixer: 0 errors
- SonarQube exclusions aligned with vitest coverage config
Infra:
- Meilisearch consistency command (app:meilisearch:check-consistency --fix) + cron daily 3am
- MeilisearchService: getAllDocumentIds(), listIndexes()
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 11:14:06 +01:00
|
|
|
if (BLOCKED_TAGS.has(tagName)) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2026-03-20 16:43:52 +01:00
|
|
|
if (ALLOWED_TAGS.has(tagName)) {
|
Complete TASK_CHECKUP: security, UX, tests, coverage, accessibility, config externalization
Billetterie:
- Partial refund support (STATUS_PARTIALLY_REFUNDED, refundedAmount field, migration)
- Race condition fix: PESSIMISTIC_WRITE lock on stock decrement in transaction
- Idempotency key on PaymentIntent::create, reuse existing PI if stripeSessionId set
- Disable checkout when event ended (server 400 + template hide)
- Webhook deduplication via cache (24h TTL on stripe event.id)
- Email validation (filter_var) in OrderController guest flow
- JSON cart validation (structure check before processing)
- Invitation expiration after 7 days (isExpired method + landing page message)
- Stripe Checkout fallback when JS fails to load (noscript + redirect)
Config externalization:
- Move Stripe fees (STRIPE_FEE_RATE, STRIPE_FEE_FIXED) and admin email (ADMIN_EMAIL) to .env/services.yaml
- Replace all hardcoded contact@e-cosplay.fr across 13 files
- MailerService: getAdminEmail()/getAdminFrom(), default $from=null resolves to admin
UX & Accessibility:
- ARIA tabs: role=tablist/tab/tabpanel, aria-selected, keyboard nav (arrows, Home, End)
- aria-label on cart +/- buttons and editor toolbar buttons
- tabindex=0 on editor toolbar buttons for keyboard access
- data-confirm handler in app.js (was only in admin.js)
- Cart error feedback on checkout failure
- Billet designer save feedback (loading/success/error states)
- Stock polling every 30s with rupture/low stock badges
- Back to event link on payment page
Security:
- HTML sanitizer: BLOCKED_TAGS list (script, style, iframe, svg, etc.) - content fully removed
- Stripe polling timeout (15s max) with fallback redirect
- Rate limiting on public order access (20/5min)
- .catch() on all fetch() calls (sortable, billet-designer)
Tests (92% PHP, 100% JS lines):
- PCOV added to dev Dockerfile
- Test DB setup: .env.test with DATABASE_URL, Redis auth, Meilisearch key
- Rate limiter disabled in test env
- Makefile: test_db_setup, test_db_reset, run_test_php, run_test_coverage_php/js
- New tests: InvitationFlowTest (21), AuditServiceTest (4), ExportServiceTest (9), InvoiceServiceTest (4)
- New tests: SuspendedUserSubscriberTest, RateLimiterSubscriberTest, MeilisearchServiceTest
- New tests: Stripe webhook payment_failed (6) + charge.refunded (6)
- New tests: BilletBuyer refund, User suspended, OrganizerInvitation expiration
- JS tests: stock polling (6), data-confirm (2), copy-url restore (1), editor ARIA (2), XSS (9), tabs keyboard (9)
- ESLint + PHP CS Fixer: 0 errors
- SonarQube exclusions aligned with vitest coverage config
Infra:
- Meilisearch consistency command (app:meilisearch:check-consistency --fix) + cron daily 3am
- MeilisearchService: getAllDocumentIds(), listIndexes()
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 11:14:06 +01:00
|
|
|
// createElement produces a bare element — no attributes from source are copied,
|
|
|
|
|
// which strips onclick, style, class, id, onerror, etc. by design.
|
Add Event entity, create event page, and custom WYSIWYG editor component
- Create Event entity with fields: account, title, description (text), startAt, endAt, address, zipcode, city, eventMainPicture (VichUploader), isOnline, createdAt, updatedAt
- Create EventRepository
- Add migration for event table with all columns
- Add "Creer un evenement" button on account events tab
- Add create event page (/mon-compte/evenement/creer) with full form
- Build custom web component <e-ticket-editor> WYSIWYG editor:
- Toolbar: bold, italic, underline, paragraph, bullet list, remove formatting
- contentEditable div with HTML sync to hidden textarea
- HTML sanitizer (strips disallowed tags, XSS safe)
- Neo-brutalist CSS styling
- CSP compliant (no inline styles)
- Register editor in app.js via customElements.define
- Add editor CSS in app.scss
- Add 16 Event entity tests (all fields + isOnline + picture upload + updatedAt)
- Add 16 editor JS tests (sanitizer + custom element lifecycle)
- Add 3 AccountController tests (create event page, submit, access control)
- Update placeholders to generic examples (no association-specific data)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 12:49:24 +01:00
|
|
|
const el = document.createElement(tagName)
|
|
|
|
|
el.appendChild(sanitizeNode(child))
|
|
|
|
|
fragment.appendChild(el)
|
|
|
|
|
} else {
|
|
|
|
|
fragment.appendChild(sanitizeNode(child))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fragment
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class ETicketEditor extends HTMLElement {
|
|
|
|
|
connectedCallback() {
|
|
|
|
|
const textarea = this.querySelector('textarea')
|
|
|
|
|
if (!textarea) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._textarea = textarea
|
|
|
|
|
textarea.style.display = 'none'
|
|
|
|
|
|
|
|
|
|
this._buildToolbar()
|
|
|
|
|
this._buildContentArea()
|
|
|
|
|
|
|
|
|
|
if (textarea.value) {
|
|
|
|
|
this._content.innerHTML = textarea.value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._content.addEventListener('input', () => this._sync())
|
|
|
|
|
this._content.addEventListener('keydown', (e) => this._onKeydown(e))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_buildToolbar() {
|
|
|
|
|
const toolbar = document.createElement('div')
|
|
|
|
|
toolbar.classList.add('ete-toolbar')
|
|
|
|
|
|
|
|
|
|
for (const action of TOOLBAR_ACTIONS) {
|
|
|
|
|
if (action.separator) {
|
|
|
|
|
const sep = document.createElement('span')
|
|
|
|
|
sep.classList.add('ete-separator')
|
|
|
|
|
toolbar.appendChild(sep)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const btn = document.createElement('button')
|
|
|
|
|
btn.type = 'button'
|
|
|
|
|
btn.classList.add('ete-btn')
|
|
|
|
|
btn.innerHTML = action.icon
|
|
|
|
|
btn.title = action.title
|
Complete TASK_CHECKUP: security, UX, tests, coverage, accessibility, config externalization
Billetterie:
- Partial refund support (STATUS_PARTIALLY_REFUNDED, refundedAmount field, migration)
- Race condition fix: PESSIMISTIC_WRITE lock on stock decrement in transaction
- Idempotency key on PaymentIntent::create, reuse existing PI if stripeSessionId set
- Disable checkout when event ended (server 400 + template hide)
- Webhook deduplication via cache (24h TTL on stripe event.id)
- Email validation (filter_var) in OrderController guest flow
- JSON cart validation (structure check before processing)
- Invitation expiration after 7 days (isExpired method + landing page message)
- Stripe Checkout fallback when JS fails to load (noscript + redirect)
Config externalization:
- Move Stripe fees (STRIPE_FEE_RATE, STRIPE_FEE_FIXED) and admin email (ADMIN_EMAIL) to .env/services.yaml
- Replace all hardcoded contact@e-cosplay.fr across 13 files
- MailerService: getAdminEmail()/getAdminFrom(), default $from=null resolves to admin
UX & Accessibility:
- ARIA tabs: role=tablist/tab/tabpanel, aria-selected, keyboard nav (arrows, Home, End)
- aria-label on cart +/- buttons and editor toolbar buttons
- tabindex=0 on editor toolbar buttons for keyboard access
- data-confirm handler in app.js (was only in admin.js)
- Cart error feedback on checkout failure
- Billet designer save feedback (loading/success/error states)
- Stock polling every 30s with rupture/low stock badges
- Back to event link on payment page
Security:
- HTML sanitizer: BLOCKED_TAGS list (script, style, iframe, svg, etc.) - content fully removed
- Stripe polling timeout (15s max) with fallback redirect
- Rate limiting on public order access (20/5min)
- .catch() on all fetch() calls (sortable, billet-designer)
Tests (92% PHP, 100% JS lines):
- PCOV added to dev Dockerfile
- Test DB setup: .env.test with DATABASE_URL, Redis auth, Meilisearch key
- Rate limiter disabled in test env
- Makefile: test_db_setup, test_db_reset, run_test_php, run_test_coverage_php/js
- New tests: InvitationFlowTest (21), AuditServiceTest (4), ExportServiceTest (9), InvoiceServiceTest (4)
- New tests: SuspendedUserSubscriberTest, RateLimiterSubscriberTest, MeilisearchServiceTest
- New tests: Stripe webhook payment_failed (6) + charge.refunded (6)
- New tests: BilletBuyer refund, User suspended, OrganizerInvitation expiration
- JS tests: stock polling (6), data-confirm (2), copy-url restore (1), editor ARIA (2), XSS (9), tabs keyboard (9)
- ESLint + PHP CS Fixer: 0 errors
- SonarQube exclusions aligned with vitest coverage config
Infra:
- Meilisearch consistency command (app:meilisearch:check-consistency --fix) + cron daily 3am
- MeilisearchService: getAllDocumentIds(), listIndexes()
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 11:14:06 +01:00
|
|
|
btn.setAttribute('aria-label', action.title)
|
|
|
|
|
btn.tabIndex = 0
|
Add Event entity, create event page, and custom WYSIWYG editor component
- Create Event entity with fields: account, title, description (text), startAt, endAt, address, zipcode, city, eventMainPicture (VichUploader), isOnline, createdAt, updatedAt
- Create EventRepository
- Add migration for event table with all columns
- Add "Creer un evenement" button on account events tab
- Add create event page (/mon-compte/evenement/creer) with full form
- Build custom web component <e-ticket-editor> WYSIWYG editor:
- Toolbar: bold, italic, underline, paragraph, bullet list, remove formatting
- contentEditable div with HTML sync to hidden textarea
- HTML sanitizer (strips disallowed tags, XSS safe)
- Neo-brutalist CSS styling
- CSP compliant (no inline styles)
- Register editor in app.js via customElements.define
- Add editor CSS in app.scss
- Add 16 Event entity tests (all fields + isOnline + picture upload + updatedAt)
- Add 16 editor JS tests (sanitizer + custom element lifecycle)
- Add 3 AccountController tests (create event page, submit, access control)
- Update placeholders to generic examples (no association-specific data)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 12:49:24 +01:00
|
|
|
btn.addEventListener('mousedown', (e) => {
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
this._exec(action)
|
|
|
|
|
})
|
|
|
|
|
toolbar.appendChild(btn)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.insertBefore(toolbar, this._textarea)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_buildContentArea() {
|
|
|
|
|
const content = document.createElement('div')
|
|
|
|
|
content.classList.add('ete-content')
|
|
|
|
|
content.setAttribute('contenteditable', 'true')
|
|
|
|
|
content.setAttribute('role', 'textbox')
|
|
|
|
|
content.setAttribute('aria-multiline', 'true')
|
|
|
|
|
|
|
|
|
|
const placeholder = this._textarea.getAttribute('placeholder')
|
|
|
|
|
if (placeholder) {
|
|
|
|
|
content.dataset.placeholder = placeholder
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.insertBefore(content, this._textarea)
|
|
|
|
|
this._content = content
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_exec(action) {
|
|
|
|
|
if (action.value) {
|
|
|
|
|
document.execCommand(action.command, false, action.value)
|
|
|
|
|
} else {
|
|
|
|
|
document.execCommand(action.command, false, null)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._sync()
|
|
|
|
|
this._content.focus()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onKeydown(e) {
|
|
|
|
|
if (e.key === 'Tab') {
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_sync() {
|
|
|
|
|
const html = sanitizeHtml(this._content.innerHTML)
|
|
|
|
|
this._textarea.value = html
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getHtml() {
|
|
|
|
|
return this._textarea.value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function registerEditor() {
|
|
|
|
|
if (!globalThis.customElements.get('e-ticket-editor')) {
|
|
|
|
|
globalThis.customElements.define('e-ticket-editor', ETicketEditor)
|
|
|
|
|
}
|
|
|
|
|
}
|