diff --git a/apps/webapp/cypress/e2e/chatroom/attachments.cy.ts b/apps/webapp/cypress/e2e/chatroom/attachments.cy.ts index 172c7269d..2cc165419 100644 --- a/apps/webapp/cypress/e2e/chatroom/attachments.cy.ts +++ b/apps/webapp/cypress/e2e/chatroom/attachments.cy.ts @@ -623,6 +623,8 @@ describe('chatroom attachments', () => { url: storagePath, type: 'image', name: 'secret.png', + width: 32, + height: 32, spoiler: true } ], @@ -634,7 +636,10 @@ describe('chatroom attachments', () => { assertImageControlReady() waitForStorageSignIfPending() - cy.get(`[data-msg-id="${messageId}"] [data-testid="feed-spoiler-reveal"]`).should('exist') + // Match the storage fixture dimensions so image loading cannot move the native click target. + cy.get( + `[data-msg-id="${messageId}"] [data-testid="feed-spoiler-reveal"] img` + ).should('have.prop', 'naturalWidth', 32) cy.get(`[data-msg-id="${messageId}"] [data-testid="feed-spoiler-reveal"]`).realClick() cy.get('[data-testid="chat-media-gallery"]').should('not.exist') cy.get(`[data-msg-id="${messageId}"] [data-testid="feed-image-open"]`, { diff --git a/bun.lock b/bun.lock index cf623bc51..6388e5c8b 100644 --- a/bun.lock +++ b/bun.lock @@ -287,7 +287,7 @@ }, "extensions/extension-hypermultimedia": { "name": "@docs.plus/extension-hypermultimedia", - "version": "2.0.0", + "version": "2.1.0", "dependencies": { "@floating-ui/dom": "^1.8.0", }, diff --git a/context7.json b/context7.json index 72d3fab5c..e4b574dd9 100644 --- a/context7.json +++ b/context7.json @@ -27,7 +27,7 @@ "rules": [ "Use Bun for every package operation: `bun add`, `bun run`, `bunx`. Never npm, yarn, pnpm or npx, and never write `npm install` in an example.", "Install a docs.plus Tiptap extension with `bun add @docs.plus/extension-`. The five are extension-hyperlink, extension-hypermultimedia, extension-indent, extension-inline-code and extension-placeholder.", - "All five docs.plus extensions are at 2.0.0 and need @tiptap/core and @tiptap/pm at ^3.22.3.", + "Read each extension's README and package.json for its version and Tiptap peer requirements. Resolve catalog references from the root package.json.", "With extension-hyperlink set `StarterKit.configure({ link: false })`. StarterKit v3 bundles its own link mark claiming the same commands and the same a[href] parse rule. The clash is silent.", "With extension-inline-code set `StarterKit.configure({ code: false })`. Both marks render and both bind Mod-e. The clash is silent.", "With extension-placeholder, remove the Tiptap built-in placeholder from the extensions array. Both register the name `placeholder`.", diff --git a/extensions/extension-hyperlink/README.md b/extensions/extension-hyperlink/README.md index 074531e61..13f2a383b 100644 --- a/extensions/extension-hyperlink/README.md +++ b/extensions/extension-hyperlink/README.md @@ -142,6 +142,16 @@ editor.chain().focus().setHyperlink({ href: 'https://example.com' }).run() editor.getAttributes('hyperlink').href // read the current href ``` +### Links on images + +The mark can wrap an inline image when the parent schema permits marks. +For `HyperMultimediaKit`, configure `Image: { inline: true }`, select the image, then call `setHyperlink({ href })`. +Use `editHyperlink({ newURL })` to change its URL without replacing the image with text. +HTML export and import keep the image inside its anchor. + +The prebuilt edit form requires link text. Use a host URL form for image-only links. +Block media and custom node views need separate integration checks; they are not covered by this inline-image recipe. + ## Keyboard shortcuts | Shortcut | Context | Action | @@ -323,6 +333,35 @@ Three ways to use them: - **Open them from outside the editor** — call [the openers](#openers) from a toolbar button or a React component. - **Replace one or all of them** — pass your own factory into the matching slot. See [Bring your own popover](#bring-your-own-popover). For a popover that is not anchored to a hyperlink, or to observe popover state from outside, see [Advanced](#advanced). +### Visible action labels + +The prebuilt preview makes no metadata request and needs no `/api/metadata` endpoint. +Its buttons use icons, accessible names, and hover/focus tooltips. +To show text beside every icon, wrap the existing factory: + +```ts +Hyperlink.configure({ + popovers: { + previewHyperlink: (options) => { + const root = previewHyperlinkPopover(options) + root.style.flexWrap = 'wrap' + root.style.maxWidth = 'calc(100vw - 32px)' + for (const button of root.querySelectorAll('button')) { + const label = document.createElement('span') + label.textContent = button.getAttribute('aria-label') + button.append(label) + button.style.width = 'auto' + button.style.padding = '0 10px' + } + return root + } + } +}) +``` + +This keeps the built-in actions and gives touch users visible labels without waiting for a tooltip. +For metadata cards, supply a custom preview factory and fetch metadata through your own service. + ### Popover-factory option shapes Every factory takes one `options` argument. The shape depends on the slot. diff --git a/extensions/extension-hyperlink/cypress/e2e/README.md b/extensions/extension-hyperlink/cypress/e2e/README.md index 69f605883..6428bf29d 100644 --- a/extensions/extension-hyperlink/cypress/e2e/README.md +++ b/extensions/extension-hyperlink/cypress/e2e/README.md @@ -4,6 +4,9 @@ Clean-room Cypress suite against `test/playground/main.ts` via `@docs.plus/playg ## Specs +Reported integrations also run through `transaction-edit.cy.ts` and `labeled-preview.cy.ts`. +These cover direct and chained edits, visible preview labels, and operation without a metadata service. + | Spec | What it proves | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `create.cy.ts` | Mod-k create popover: lifecycle, URL validation/normalization (bare domains, phones, emails), collapsed-caret insert, documented DOM contract | diff --git a/extensions/extension-hyperlink/cypress/e2e/labeled-preview.cy.ts b/extensions/extension-hyperlink/cypress/e2e/labeled-preview.cy.ts new file mode 100644 index 000000000..5c4dc9a11 --- /dev/null +++ b/extensions/extension-hyperlink/cypress/e2e/labeled-preview.cy.ts @@ -0,0 +1,29 @@ +describe('preview without a metadata service', () => { + it('shows visible action labels and removes a link with the documented factory wrapper', () => { + cy.viewport(375, 812) + const metadataRequest = cy.spy().as('metadataRequest') + cy.intercept('**/api/metadata*', metadataRequest) + cy.visit('/?popover=labels') + cy.window().should('have.property', '_editor') + cy.setEditorContent('

Example

') + cy.get('#editor a').click() + cy.getVisibleFloatingPopover() + .find('.hyperlink-preview-popover') + .should(($preview) => { + const bounds = $preview[0].getBoundingClientRect() + expect(bounds.left).to.be.at.least(0) + expect(bounds.right).to.be.at.most(375) + }) + .within(() => { + for (const label of ['Copy link', 'Edit link', 'Remove link']) { + cy.contains('button span', label).should('be.visible') + } + cy.contains('button', 'Remove link').click() + }) + cy.get('#editor a').should('not.exist') + cy.get('#editor p').should('have.text', 'Example') + cy.get('@metadataRequest').should('not.have.been.called') + }) +}) + +export {} diff --git a/extensions/extension-hyperlink/cypress/e2e/transaction-edit.cy.ts b/extensions/extension-hyperlink/cypress/e2e/transaction-edit.cy.ts new file mode 100644 index 000000000..49ac00f0a --- /dev/null +++ b/extensions/extension-hyperlink/cypress/e2e/transaction-edit.cy.ts @@ -0,0 +1,21 @@ +describe('transaction-safe hyperlink edits', () => { + for (const mode of ['command', 'chain'] as const) { + it(`edits text and URL through the ${mode} entry point without a mismatched transaction`, () => { + cy.visitPlayground() + cy.setEditorContent( + '

Before Original after.

' + ) + cy.selectText('Original') + cy.getEditor().then((editor) => { + const attrs = { newURL: 'https://google.com', newText: 'Testing' } + if (mode === 'command') editor.commands.editHyperlink(attrs) + else editor.chain().focus().extendMarkRange('hyperlink').editHyperlink(attrs).run() + }) + cy.get('#editor a').should('have.attr', 'href', 'https://google.com') + cy.get('#editor a strong').should('have.text', 'Testing') + cy.get('#editor p').should('have.text', 'Before Testing after.') + }) + } +}) + +export {} diff --git a/extensions/extension-hyperlink/test/playground/main.ts b/extensions/extension-hyperlink/test/playground/main.ts index bf9ccf725..a550ad879 100644 --- a/extensions/extension-hyperlink/test/playground/main.ts +++ b/extensions/extension-hyperlink/test/playground/main.ts @@ -108,11 +108,29 @@ function byoPreviewHyperlink(options: HyperlinkModule.PreviewHyperlinkOptions): return root } +function labeledPreviewHyperlink(options: HyperlinkModule.PreviewHyperlinkOptions): HTMLElement { + const root = previewHyperlinkPopover(options) + root.style.flexWrap = 'wrap' + root.style.maxWidth = 'calc(100vw - 32px)' + for (const button of root.querySelectorAll('button')) { + const label = document.createElement('span') + label.textContent = button.getAttribute('aria-label') + button.append(label) + button.style.width = 'auto' + button.style.padding = '0 10px' + } + return root +} + const popovers = useCustomPopovers ? { previewHyperlink: byoPreviewHyperlink, createHyperlink: byoCreateHyperlink } : noPopovers ? { previewHyperlink: null, createHyperlink: null } - : { previewHyperlink: previewHyperlinkPopover, createHyperlink: createHyperlinkPopover } + : { + previewHyperlink: + params.get('popover') === 'labels' ? labeledPreviewHyperlink : previewHyperlinkPopover, + createHyperlink: createHyperlinkPopover + } const editor = new Editor({ element, diff --git a/extensions/extension-hypermultimedia/CHANGELOG.md b/extensions/extension-hypermultimedia/CHANGELOG.md index 130a48707..58de4f8b2 100644 --- a/extensions/extension-hypermultimedia/CHANGELOG.md +++ b/extensions/extension-hypermultimedia/CHANGELOG.md @@ -6,15 +6,27 @@ here. Entries from 2.0.0 onward follow historical Conventional Commits format. The project adheres to [Semantic Versioning](https://semver.org/). -## [Unreleased] +## [2.1.0] — 2026-09-17 + +### Highlights + +- Unlisted Vimeo links keep their access hashes through insertion and HTML round-trips. +- Hosts can import `isSafeMediaSrc` to validate media URLs before insertion. +- X fallback markup keeps the configured post options when oEmbed fails. ### Added - Export `isSafeMediaSrc` from the package entry. Markdown import uses it to refuse an unsafe URL before it mints a media node. +### Changed + +- Require `@tiptap/core` and `@tiptap/pm` `^3.31.3`, and update the `@floating-ui/dom` runtime dependency to `^1.8.0`. + ### Fixed +- Keep X theme, width, language, privacy, alignment, and hidden-content options in fallback markup after an oEmbed failure. + - Preserve the path hash in unlisted Vimeo URLs through insertion and HTML round-trips. Query-string hashes keep precedence. Addresses [the standalone contribution](https://github.com/HMarzban/extension-hypermultimedia/pull/4). diff --git a/extensions/extension-hypermultimedia/README.md b/extensions/extension-hypermultimedia/README.md index d00ce3b0d..fd261169c 100644 --- a/extensions/extension-hypermultimedia/README.md +++ b/extensions/extension-hypermultimedia/README.md @@ -218,6 +218,19 @@ The media toolbar, the paste path, the insert commands and the caption each carr - **A caption survives HTML round-trip for `image` only.** Every other node keeps the editable caption and the attribute, but emits no `
`, so clipboard copy and the toolbar Copy action drop the text. Markdown export drops every caption. See [Caption](#caption). - **The paste handler drops pasted image files silently without a listener.** Add an `editorFileUpload` listener and insert the nodes yourself — see [Image file paste (`editorFileUpload`)](#image-file-paste-editorfileupload). +### React Native and WebView hosts + +The package runs inside a browser document. It does not provide a React Native bridge. +Browser touch tests do not establish compatibility with TenTap or a specific Android or iOS WebView. + +Use the current node names and commands from [Migrating from 1.x](#migrating-from-1x). +Load `styles.css` inside the WebView document. +For X, configure `X`, call `setX`, and size posts with `maxwidth`; X has no resize gripper. +Provider and video taps keep their player controls, so expose a host action to open editing controls on touch devices. + +When reporting a bridge problem, include the TenTap, WebView, Tiptap, and extension versions, device OS, editor HTML, bridge setup, and failing action. +Use a minimal runnable app. A viewport resize or browser touch simulation cannot replace a device reproduction. + ## Styling The package ships one stylesheet. It carries the resize gripper, the loading shell, the media toolbar, the caption, and the `x`, `loom` and `spotify` embed styles. @@ -441,6 +454,10 @@ Provider embeds resolve options in two layers. Kit defaults come from `HyperMult **X** sizes through the oEmbed `maxwidth` presets Compact `280`, Standard `400` and Wide `550`. The toolbar Post options menu switches both `maxwidth` and `theme`. `maxwidth` defaults to `400`. `theme` defaults to `'light'`, `lang` to `'en'`, `hide_media` and `hide_thread` to `false`. An X post reads its own `theme` attribute, not the page `color-scheme` — see [Theming](#theming). `dnt` defaults to `true` and is a kit option only, because `AddXOptions` omits it. The `align` attribute is a schema attribute passed straight to oEmbed, with no kit option and no `setX` field. The `x` node has no drag-resize. +When X's oEmbed endpoint fails, fallback markup keeps the configured widget options. +If `widgets.js` also fails, a canonical post link stays visible. +Keep browser CORS protection enabled; see [X network failures](https://github.com/docs-plus/docs.plus/tree/main/extensions/extension-hypermultimedia/src/nodes/x#network-failures). + ## Media toolbar Hovering a media node on a fine-pointer device opens the media toolbar at the node's top-right corner. Common actions sit inline, and the rest live behind a `…` overflow menu. Icon-only buttons show a floating tooltip on hover or focus. diff --git a/extensions/extension-hypermultimedia/cypress/e2e/README.md b/extensions/extension-hypermultimedia/cypress/e2e/README.md index 210eae0a6..663eef6a1 100644 --- a/extensions/extension-hypermultimedia/cypress/e2e/README.md +++ b/extensions/extension-hypermultimedia/cypress/e2e/README.md @@ -15,6 +15,10 @@ Clean-room Cypress suite against `test/playground/main.ts` via `@docs.plus/playg ## Supporting specs +Reported integrations also run through `nodes/text-focus.cy.ts`, `serialization/linked-image.cy.ts`, and `lifecycle/x-oembed-fallback.cy.ts`. +These cover text focus, inline image links, and visible X fallback markup with its configured widget options. +Browser checks do not verify React Native or TenTap on a device. + | Area | Specs | | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Nodes | `nodes/insert-nodes.cy.ts`, `nodes/nested-context.cy.ts` (gripper + toolbar stay aligned to media inside blockquote / list items) | diff --git a/extensions/extension-hypermultimedia/cypress/e2e/lifecycle/x-oembed-fallback.cy.ts b/extensions/extension-hypermultimedia/cypress/e2e/lifecycle/x-oembed-fallback.cy.ts new file mode 100644 index 000000000..9803cf0bd --- /dev/null +++ b/extensions/extension-hypermultimedia/cypress/e2e/lifecycle/x-oembed-fallback.cy.ts @@ -0,0 +1,40 @@ +const STATUS_URL = 'https://x.com/jack/status/20' +const WRAPPER = '#editor .hypermultimedia--x__content' + +describe('X oEmbed failure', () => { + beforeEach(() => { + cy.intercept('GET', 'https://publish.x.com/oembed*', { forceNetworkError: true }).as('oembed') + cy.intercept('GET', 'https://platform.twitter.com/widgets.js', { forceNetworkError: true }) + cy.visitPlayground('xOptions=custom') + cy.setEditorContent('

') + }) + + for (const entry of ['command', 'paste'] as const) { + it(`keeps a visible link and widget options after ${entry} when both X endpoints fail`, () => { + if (entry === 'command') { + cy.getEditor().then((editor) => editor.commands.setX({ src: STATUS_URL })) + } else { + cy.get('#editor .ProseMirror').focus() + cy.pastePlainText(STATUS_URL) + } + + cy.wait('@oembed') + cy.expectMediaLoadingReady() + cy.get(`${WRAPPER} .hm-media-slot`).should('have.css', 'opacity', '1') + cy.get(`${WRAPPER} blockquote.twitter-tweet`).should(($quote) => { + expect($quote.attr('data-theme')).to.equal('dark') + expect($quote.attr('data-width')).to.equal('400') + expect($quote.attr('data-lang')).to.equal('fr') + expect($quote.attr('data-dnt')).to.equal('false') + expect($quote.attr('data-cards')).to.equal('hidden') + expect($quote.attr('data-conversation')).to.equal('none') + }) + cy.get(`${WRAPPER} blockquote a`) + .should('be.visible') + .and('have.attr', 'href', STATUS_URL) + .and('have.text', STATUS_URL) + }) + } +}) + +export {} diff --git a/extensions/extension-hypermultimedia/cypress/e2e/nodes/text-focus.cy.ts b/extensions/extension-hypermultimedia/cypress/e2e/nodes/text-focus.cy.ts new file mode 100644 index 000000000..2f9b2423b --- /dev/null +++ b/extensions/extension-hypermultimedia/cypress/e2e/nodes/text-focus.cy.ts @@ -0,0 +1,36 @@ +describe('text focus beside media', () => { + it('keeps paragraph and heading clicks editable when the image extension is active', () => { + cy.visitPlayground() + cy.setEditorContent('

Paragraph

Heading

') + cy.getEditor().then((editor) => { + editor.commands.focus('end') + editor.commands.setImage({ src: 'https://example.com/photo.png', width: 200, height: 150 }) + }) + + for (const selector of ['p', 'h2']) { + cy.get(`#editor ${selector}`).first().realClick({ position: 'left' }) + cy.get('#editor .ProseMirror').should('have.focus') + cy.get(`#editor ${selector}`) + .first() + .then(($block) => { + const target = $block[0] + const rect = target.getBoundingClientRect() + const win = target.ownerDocument.defaultView! + target.dispatchEvent( + new win.MouseEvent('click', { + bubbles: true, + cancelable: true, + clientX: rect.left + 4, + clientY: rect.top + rect.height / 2 + }) + ) + }) + cy.get('#editor .ProseMirror').should('have.focus') + cy.realType('typed ') + cy.get(`#editor ${selector}`).first().should('contain.text', 'typed ') + } + cy.nodeCount('image').should('eq', 1) + }) +}) + +export {} diff --git a/extensions/extension-hypermultimedia/cypress/e2e/serialization/linked-image.cy.ts b/extensions/extension-hypermultimedia/cypress/e2e/serialization/linked-image.cy.ts new file mode 100644 index 000000000..3661f79aa --- /dev/null +++ b/extensions/extension-hypermultimedia/cypress/e2e/serialization/linked-image.cy.ts @@ -0,0 +1,34 @@ +import type {} from '@docs.plus/extension-hyperlink' + +const IMAGE_URL = 'https://example.com/photo.png' + +describe('hyperlink marks on inline images', () => { + it('preserves the image through link creation, URL editing, HTML round-trip, and unlinking', () => { + cy.visitPlayground('inlineImage=true') + cy.setEditorContent(`

Example

`) + cy.getEditor().then((editor) => { + editor.chain().setNodeSelection(1).setHyperlink({ href: 'https://example.com' }).run() + }) + cy.get('#editor a img').should('have.attr', 'src', IMAGE_URL) + + cy.getEditor().then((editor) => { + editor + .chain() + .setNodeSelection(1) + .extendMarkRange('hyperlink') + .editHyperlink({ newURL: 'https://changed.example' }) + .run() + editor.commands.setContent(editor.getHTML()) + }) + cy.get('#editor a').should('have.attr', 'href', 'https://changed.example') + cy.get('#editor a img').should('have.attr', 'src', IMAGE_URL) + + cy.getEditor().then((editor) => { + editor.chain().setNodeSelection(1).unsetHyperlink().run() + }) + cy.get('#editor a').should('not.exist') + cy.get('#editor img').should('have.attr', 'src', IMAGE_URL) + }) +}) + +export {} diff --git a/extensions/extension-hypermultimedia/package.json b/extensions/extension-hypermultimedia/package.json index 4575fdaba..db997e238 100644 --- a/extensions/extension-hypermultimedia/package.json +++ b/extensions/extension-hypermultimedia/package.json @@ -1,6 +1,6 @@ { "name": "@docs.plus/extension-hypermultimedia", - "version": "2.0.0", + "version": "2.1.0", "description": "Tiptap extension for embedded multimedia: images, audio, video, YouTube, Vimeo, SoundCloud, Spotify, Loom, and X — with resize, captions, and a media toolbar", "main": "./dist/index.cjs", "module": "./dist/index.js", diff --git a/extensions/extension-hypermultimedia/src/nodes/x/README.md b/extensions/extension-hypermultimedia/src/nodes/x/README.md index 10cff75e5..a757bbabb 100644 --- a/extensions/extension-hypermultimedia/src/nodes/x/README.md +++ b/extensions/extension-hypermultimedia/src/nodes/x/README.md @@ -125,6 +125,16 @@ HyperMultimediaKit.configure({ }) ``` +## Network failures + +If oEmbed fails, the node supplies a blockquote and canonical post link to `widgets.js`. +That fallback keeps the theme, width, language, privacy, alignment, and hidden-content options. +If the script also fails, the post link stays visible. + +Keep browser CORS protection enabled. The fallback needs no browser extension or unrestricted proxy. +Your host controls CSP and WebView navigation permissions. +A visible fallback link does not prove that X permits the full widget in that environment. + ## Sizing X embeds size through the oEmbed `maxwidth` attribute, not the resize gripper. The media toolbar exposes three presets — Compact (280), Standard (400, default), Wide (550) — and `setX({ maxwidth })` accepts any of those values per insert. diff --git a/extensions/extension-hypermultimedia/src/nodes/x/helper.ts b/extensions/extension-hypermultimedia/src/nodes/x/helper.ts index ea58a673d..8ad966d75 100644 --- a/extensions/extension-hypermultimedia/src/nodes/x/helper.ts +++ b/extensions/extension-hypermultimedia/src/nodes/x/helper.ts @@ -268,15 +268,27 @@ function sanitizeXEmbedHtml(html: string): string { } /** Minimal blockquote widgets.js needs when oEmbed is empty or scrubbed away. */ -function seedXEmbedMarkup(wrapper: HTMLElement, statusUrl: string): void { +function seedXEmbedMarkup(wrapper: HTMLElement, params: Record): void { if (hasRenderableXEmbed(wrapper)) return - const url = normalizeXUrl(statusUrl) + const url = normalizeXUrl(String(params.url ?? '')) if (!url) return wrapper.replaceChildren() const blockquote = document.createElement('blockquote') blockquote.className = 'twitter-tweet' + // widgets.js reads these attributes when oEmbed cannot supply its own markup. + for (const [parameter, attribute] of [ + ['theme', 'data-theme'], + ['maxwidth', 'data-width'], + ['lang', 'data-lang'], + ['align', 'data-align'] + ]) { + if (params[parameter] != null) blockquote.setAttribute(attribute, String(params[parameter])) + } + blockquote.dataset.dnt = params.dnt === 0 ? 'false' : 'true' + if (params.hide_media) blockquote.dataset.cards = 'hidden' + if (params.hide_thread) blockquote.dataset.conversation = 'none' const anchor = document.createElement('a') anchor.href = url anchor.textContent = url @@ -311,8 +323,6 @@ export async function mountXEmbed( ): Promise { if (signal?.aborted) return false - const statusUrl = String(params.url ?? '') - try { const html = await fetchOEmbedHtml(params, signal) if (signal?.aborted) return false @@ -321,7 +331,7 @@ export async function mountXEmbed( // oEmbed may fail; fall back to a blockquote seed for widgets.js. } - seedXEmbedMarkup(wrapper, statusUrl) + seedXEmbedMarkup(wrapper, params) if (signal?.aborted) return false diff --git a/extensions/extension-hypermultimedia/test/playground/main.ts b/extensions/extension-hypermultimedia/test/playground/main.ts index 82883aa67..5033d614a 100644 --- a/extensions/extension-hypermultimedia/test/playground/main.ts +++ b/extensions/extension-hypermultimedia/test/playground/main.ts @@ -36,7 +36,7 @@ const editor = new Editor({ shouldAutoLink: (url: string) => !isMediaUrl(url) }), HyperMultimediaKit.configure({ - Image: { inline: false }, + Image: { inline: params.get('inlineImage') === 'true' }, Video: true, Audio: true, Youtube: true, @@ -44,7 +44,10 @@ const editor = new Editor({ SoundCloud: true, Spotify: true, Loom: true, - X: true, + X: + params.get('xOptions') === 'custom' + ? { theme: 'dark', lang: 'fr', hide_media: true, hide_thread: true, dnt: false } + : true, loadingShell, isUploadedMedia: () => uploadedParam })