Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
187 commits
Select commit Hold shift + click to select a range
679017c
fix: correct wiki edit links redirection
Feb 10, 2026
c335a29
test(e2e): support class-based wiki edit link selector
NagariaHussain Feb 10, 2026
81948a8
Merge pull request #513 from gajjug004/fix/wiki-edit-links
NagariaHussain Feb 10, 2026
edebd72
fix: allow Ctrl/Cmd+Click to open sidebar links in new tab
Feb 10, 2026
183a73b
Merge pull request #514 from gajjug004/fix/sidebar-tab-cmd-click
NagariaHussain Feb 10, 2026
66f1084
fix: wiki prefix missing in redirect after login
NagariaHussain Feb 11, 2026
9f8105b
fix: prevent duplicate leaf slugs in change requests
NagariaHussain Feb 11, 2026
5736371
fix: link in editor styling
NagariaHussain Feb 11, 2026
0631f0f
fix: archive btn not visible in contribution banner
ruchamahabal Feb 11, 2026
db714c2
fix: contain DiffViewer Shadow DOM within stacking context
Feb 12, 2026
a4e75df
Merge pull request #517 from gajjug004/fix/diff-viewer-dialog-overlay…
NagariaHussain Feb 12, 2026
eb68ff7
fix: reset page on archiving change request
ruchamahabal Feb 16, 2026
b9a574b
Merge branch 'develop' into fix-archive
ruchamahabal Feb 16, 2026
430237a
Revert "fix: prevent duplicate leaf slugs in change requests"
Feb 16, 2026
41b354f
Merge pull request #520 from gajjug004/revert/fix-duplicate-leaf-slug…
NagariaHussain Feb 16, 2026
0ffffee
Merge pull request #516 from ruchamahabal/fix-archive
NagariaHussain Feb 16, 2026
81f0deb
refactor: remove dead WikiPagePatch code and unused utility functions
NagariaHussain Feb 16, 2026
4a26a44
fix: sync desk edits to revision system (Phase 1)
NagariaHussain Feb 16, 2026
8d26ef5
refactor: consolidate 4 merge strategies into merge_content_three_way…
NagariaHussain Feb 16, 2026
8a4c6e8
refactor: simplify get_or_create_draft_change_request into focused he…
NagariaHussain Feb 16, 2026
abc3de2
refactor: delta-based overlay revisions for O(1) CR creation (Phase 4)
NagariaHussain Feb 16, 2026
63117de
refactor: optimized merge with fast-forward path and delta-only apply…
NagariaHussain Feb 16, 2026
73e7b2b
Merge remote-tracking branch 'upstream/develop' into refactor/contrib…
NagariaHussain Feb 16, 2026
e7674ba
chore: trigger merge status refresh
NagariaHussain Feb 16, 2026
e0c62dc
fix: delete wiki documents after saves to prevent NestedSetChildExist…
NagariaHussain Feb 17, 2026
1065401
chore: delete docs file
NagariaHussain Feb 17, 2026
60be8c0
chore: move reorder perf test to separate benchmark module
NagariaHussain Feb 17, 2026
91a07cf
Merge pull request #524 from NagariaHussain/refactor/contributions
NagariaHussain Feb 17, 2026
e216fd6
fix: add ignore permission
vishwajeet-13 Feb 17, 2026
12b3c24
Merge pull request #526 from vishwajeet-13/fix/sidebar-content
NagariaHussain Feb 17, 2026
ff189dd
fix: no change request error
vishwajeet-13 Feb 17, 2026
940dcf7
Merge branch 'develop' into fix/no-changes-error
NagariaHussain Feb 17, 2026
ab2534d
Merge pull request #528 from vishwajeet-13/fix/no-changes-error
NagariaHussain Feb 17, 2026
b9dd71c
feat: add table dropdown in markdown toolbar
Rl0007 Feb 17, 2026
587547c
fix: sidebar open by default
vishwajeet-13 Feb 17, 2026
7e4b46f
Merge branch 'develop' into feat/table-alter-dropdown
NagariaHussain Feb 17, 2026
8d27e57
Merge pull request #530 from vishwajeet-13/fix/sidebar-open-by-default
NagariaHussain Feb 17, 2026
2508462
Merge pull request #532 from Rl0007/feat/table-alter-dropdown
NagariaHussain Feb 17, 2026
54853db
fix: access denied flash
vishwajeet-13 Feb 17, 2026
557827a
Merge pull request #535 from vishwajeet-13/fix/access-denied-while-lo…
NagariaHussain Feb 17, 2026
6f604fe
feat: search in wiki space list
NagariaHussain Feb 17, 2026
0f2c2d3
fix: no search results
NagariaHussain Feb 17, 2026
be1ce06
fix: e2e test selectors for space creation, page buttons, and editor
NagariaHussain Feb 17, 2026
dba137c
Merge pull request #533 from frappe/feat/space-search
NagariaHussain Feb 17, 2026
0500f76
fix: auto refresh page while editing
vishwajeet-13 Feb 17, 2026
21a2708
feat: preserve blank lines in markdown editor round-trips
NagariaHussain Feb 17, 2026
8c5ed4e
Merge pull request #538 from vishwajeet-13/fix/auto-refresh-while-edit
NagariaHussain Feb 17, 2026
f6ef853
fix: bubble menu hidden behind sticky toolbar
Feb 17, 2026
f5b5855
Merge pull request #539 from frappe/feat/markdown-breaks
NagariaHussain Feb 17, 2026
6a27a68
Merge pull request #540 from gajjug004/fix/bubble-menu-hidden-behind-…
NagariaHussain Feb 17, 2026
6ffe288
fix: add debounce to tackle sidebar re-order race condition
Rl0007 Feb 17, 2026
b559bff
Merge pull request #541 from Rl0007/fix/sidebar-reorder
NagariaHussain Feb 17, 2026
49fc94c
revert: bubble menu hidden behind sticky toolbar #540
Feb 18, 2026
974cbe7
Merge pull request #545 from gajjug004/revert/bubble-menu-sticky-tool…
NagariaHussain Feb 18, 2026
f605bf8
feat: langauge selector in code block
NagariaHussain Feb 18, 2026
b960315
Merge branch 'develop' into feat/language-in-code
NagariaHussain Feb 18, 2026
618a1d6
fix: don't optimize image uploads by default (#556)
ruchamahabal Feb 18, 2026
68a0919
fix: use 'html' as value for html language option in code block
NagariaHussain Feb 18, 2026
bcb431c
Merge pull request #554 from frappe/feat/language-in-code
NagariaHussain Feb 18, 2026
7211bd7
fix: revert cmd + click fix
NagariaHussain Feb 19, 2026
a0bf6d8
test: sidebar links should not lead to full page reload
NagariaHussain Feb 19, 2026
12a5db2
feat: disable merge during rearrange
Rl0007 Feb 19, 2026
28cd38e
fix: remove redundant check
Rl0007 Feb 19, 2026
68aaebb
fix: sidebar-title-icon-misalignment
vishwajeet-13 Feb 19, 2026
393d2ed
Merge pull request #558 from vishwajeet-13/fix/sidebar-title-icon-mis…
NagariaHussain Feb 19, 2026
08795cd
feat: UI for merge conflict resolution
NagariaHussain Feb 20, 2026
67773b1
fix: position conflict resolution checkboxes above diff columns
NagariaHussain Feb 20, 2026
ce6a48f
fix: harden merge conflict resolution flow
NagariaHussain Feb 20, 2026
71c1687
Merge branch 'develop' into feat/mcr
NagariaHussain Feb 20, 2026
8f6aa56
Merge pull request #561 from frappe/feat/mcr
NagariaHussain Feb 20, 2026
46414a7
Merge pull request #557 from Rl0007/fix/sidebar-reorder
NagariaHussain Feb 20, 2026
df290f8
fix: reparent orphaned children when parent is auto-deleted during merge
NagariaHussain Feb 23, 2026
a212010
fix: skip sort_order auto-assign during merge to preserve sidebar order
NagariaHussain Feb 23, 2026
a5ef57c
feat: add Pinia state management with useUserStore (Phase 1 tracer bu…
NagariaHussain Feb 24, 2026
95b4865
feat: widen useUserStore and migrate all user-related consumers (Phas…
NagariaHussain Feb 24, 2026
ac193d2
feat: add useSessionStore and delete data/ layer (Phase 3)
NagariaHussain Feb 24, 2026
64752a2
feat: add useChangeRequestStore and migrate all CR consumers (Phase 4)
NagariaHussain Feb 24, 2026
5868d36
chore: mark Phase 5 (cleanup) done — Pinia migration complete
NagariaHussain Feb 24, 2026
8dfac84
refactor: eliminate redundant API calls (Phase 1)
NagariaHussain Feb 24, 2026
9ed0855
refactor: structural refactors — composables, store API, prop cleanup…
NagariaHussain Feb 24, 2026
35bd129
perf: eliminate deep clone and deduplicate loadChanges (Phase 3)
NagariaHussain Feb 24, 2026
b52b668
chore: remove plans folder
NagariaHussain Feb 24, 2026
5f0d937
ux: add skeleton loaders to sidebar and content panels
NagariaHussain Feb 24, 2026
4ef8570
fix: use window.location.href for login redirects instead of invalid …
NagariaHussain Feb 24, 2026
8762dff
fix: clear stale currentCrPage on page/CR context changes
NagariaHussain Feb 24, 2026
95f0d2a
fix: remove duplicate loadChanges() calls in archive and merge handlers
NagariaHussain Feb 24, 2026
1c27b3f
feat: edit route via dialog, fix route generation and post-merge sync
NagariaHussain Feb 25, 2026
ef46ba4
Merge pull request #566 from NagariaHussain/r/pinia
NagariaHussain Feb 25, 2026
914ca39
fix: don't render external links
NagariaHussain Feb 26, 2026
13c78b1
fix: don't index external links
NagariaHussain Feb 26, 2026
42c0950
chore: add screenshots dir to .gitignore
NagariaHussain Feb 26, 2026
8f5363f
test: external links treatment in backend
NagariaHussain Feb 26, 2026
b39f63c
fix: don't strip underscores
NagariaHussain Feb 26, 2026
6bd376e
fix: update toc on spa navigation
NagariaHussain Feb 26, 2026
d661bc1
fix: escape html in toc
NagariaHussain Feb 26, 2026
8abc1b0
fix(sidebar): highlight active route
imgullu786 Mar 3, 2026
209c3c7
refactor: nav items in Sidebar
imgullu786 Mar 3, 2026
d55ab2a
Merge pull request #576 from imgullu786/fix/sidebar-active-state
NagariaHussain Mar 4, 2026
490ede0
fix: add dark mode syntax highlighting to wiki editor
NagariaHussain Mar 9, 2026
e186a7a
Merge pull request #580 from NagariaHussain/fix/editor-dark-mode-synt…
NagariaHussain Mar 9, 2026
2c02a6c
fix(perms): security issues
NagariaHussain Mar 9, 2026
58cb327
feat: rich callouts
NagariaHussain Mar 9, 2026
6b06e77
fix(test): use correct selectors and setContent API in callout tests
NagariaHussain Mar 9, 2026
ac43800
Merge pull request #581 from frappe/rich-callouts
NagariaHussain Mar 9, 2026
679185e
fix: header should be sticky
NagariaHussain Mar 9, 2026
7593c65
fix: handle indented callout rendering
NagariaHussain Mar 11, 2026
c775d8d
fix: redirect after login
NagariaHussain Mar 11, 2026
fd6b852
test: failing UI test
NagariaHussain Mar 11, 2026
e13b3b4
fix: add border styling to images in prose and wiki editor
NagariaHussain Mar 16, 2026
845137f
feat: Image Viewer
NagariaHussain Mar 16, 2026
45fa908
test: add e2e tests for image viewer lightbox
NagariaHussain Mar 16, 2026
dbf814c
fix: toc line height
NagariaHussain Mar 16, 2026
5969f52
Merge pull request #583 from frappe/feat/image-viewer
NagariaHussain Mar 16, 2026
7e99ca8
fix: remove border from lightbox image
NagariaHussain Mar 16, 2026
39832a5
feat: return plain markdown when Accept header requests text/markdown
NagariaHussain Mar 18, 2026
8e5e843
test: add integration tests for markdown content negotiation
NagariaHussain Mar 19, 2026
f7ae27f
fix: reload document after save in v3 migration to prevent TimestampM…
NagariaHussain Mar 24, 2026
211dd8c
Merge pull request #588 from NagariaHussain/fix/v3-migration-timestam…
NagariaHussain Mar 24, 2026
944ed23
Merge branch 'develop' into feat/markdown-content-negotiation
NagariaHussain Mar 24, 2026
f5e6d3b
fix: use full semgrep rule ID in nosemgrep comments
NagariaHussain Mar 25, 2026
8f9e9c1
Merge pull request #587 from NagariaHussain/feat/markdown-content-neg…
NagariaHussain Mar 25, 2026
34e5958
Merge branch 'version-3' into develop
NagariaHussain Mar 25, 2026
fe36fdd
chore: remove bench_reorder benchmark file
NagariaHussain Mar 25, 2026
4029219
fix: remove all perm rule from cr doctype
NagariaHussain Mar 25, 2026
3dfcdf3
chore: add nosemgrep comments for intentional manual db commits
NagariaHussain Mar 25, 2026
c854489
chore: add nosemgrep comment for intentional guest-whitelisted method
NagariaHussain Mar 25, 2026
8c31d4f
Merge pull request #584 from frappe/develop
NagariaHussain Mar 25, 2026
8e19b2c
fix: optimize contributions page by using standard list resource
NagariaHussain Mar 31, 2026
24f84bb
fix: sort pending reviews by latest first
NagariaHussain Mar 31, 2026
dee03cf
Merge pull request #592 from frappe/fix/contributions-page-perf
NagariaHussain Mar 31, 2026
437bc4d
fix: use flex layout for full-height contributions list
NagariaHussain Mar 31, 2026
4999822
Merge pull request #593 from frappe/fix/contributions-page-perf
NagariaHussain Mar 31, 2026
b7f8700
fix: remove no-op save causing timestamp mismatch
NagariaHussain Apr 2, 2026
c8a518a
Merge pull request #594 from frappe/develop
NagariaHussain Apr 2, 2026
e2bceee
fix: eliminate layout shifts on public wiki page load
NagariaHussain Apr 6, 2026
1886d0a
fix: use explicit persist key for sidebar collapse state
NagariaHussain Apr 6, 2026
0421984
Merge pull request #595 from frappe/fix/layout-shifts
NagariaHussain Apr 6, 2026
b289558
fix: version
vishwajeet-13 Apr 15, 2026
c8096d0
Merge pull request #598 from vishwajeet-13/fix/version-in-installed-apps
NagariaHussain Apr 15, 2026
864e0af
fix: make sidebar full-height with integrated space switcher
NagariaHussain Apr 17, 2026
853eff6
fix: add accessibility attributes to space switcher button
NagariaHussain Apr 18, 2026
5d64eb0
Merge branch 'develop' into fix/sidebar-full-height-layout
NagariaHussain Apr 18, 2026
764fe9b
Merge pull request #601 from frappe/fix/sidebar-full-height-layout
NagariaHussain Apr 18, 2026
7263680
feat: add log out button to sidebar header menu
NagariaHussain Apr 22, 2026
a09ef22
Merge pull request #603 from NagariaHussain/feat/logout-button
NagariaHussain Apr 22, 2026
2aa70e5
feat: add "View in Desk" menu item for Wiki Managers
NagariaHussain Apr 22, 2026
790a218
Merge pull request #604 from NagariaHussain/feat/view-in-desk
NagariaHussain Apr 22, 2026
318c5a1
fix: render tables whose inline-code cells contain a pipe
NagariaHussain Apr 22, 2026
1e3481c
Merge pull request #605 from NagariaHussain/fix/table-pipe-inline-code
NagariaHussain Apr 22, 2026
b131f6f
fix: strip trailing whitespace in code blocks
NagariaHussain Apr 22, 2026
bde8d03
Merge pull request #606 from NagariaHussain/fix/code-block-trailing-w…
NagariaHussain Apr 22, 2026
f7dc012
feat: add iframe embed support for YouTube, Vimeo, Loom, and friends
NagariaHussain Apr 22, 2026
c746f24
feat: match Frappe LMS embed provider list
NagariaHussain Apr 22, 2026
c452b6c
test(e2e): cover iframe embed parsing, preview, round-trip, and URL i…
NagariaHussain Apr 22, 2026
8fc221a
refactor: use frappe-ui Button/TextInput in iframe placeholder
NagariaHussain Apr 22, 2026
1cff8f8
Merge pull request #608 from NagariaHussain/feat/iframe-embed-extension
NagariaHussain Apr 22, 2026
2222a58
refactor: remove unused fields and cache logic from Wiki Settings
NagariaHussain Apr 28, 2026
6be0dd2
chore: more dead code
NagariaHussain Apr 28, 2026
e084886
feat: respect enable_table_of_contents setting when rendering wiki pages
NagariaHussain Apr 28, 2026
b072330
feat: inject Wiki Settings.head_html into all rendered wiki pages
NagariaHussain Apr 28, 2026
be6d8fc
chore: add field for head html
NagariaHussain Apr 28, 2026
b930c96
Merge pull request #613 from NagariaHussain/refactor/cleanup-wiki-set…
NagariaHussain Apr 28, 2026
997e4b7
chore: set head_html field options to HTML for syntax highlighting
NagariaHussain Apr 28, 2026
02c8eec
Merge pull request #614 from NagariaHussain/feat/head-html-options-html
NagariaHussain Apr 28, 2026
72bd324
fix: skip duplicate leaf creation during v3 migration
NagariaHussain May 4, 2026
282fec3
refactor: simplify in_migrate check in v3 migration dedupe
NagariaHussain May 4, 2026
d2c7d91
Merge pull request #617 from NagariaHussain/develop
NagariaHussain May 4, 2026
cbbec83
Revert "refactor: simplify in_migrate check in v3 migration dedupe"
NagariaHussain May 7, 2026
9126c2c
Revert "fix: skip duplicate leaf creation during v3 migration"
NagariaHussain May 7, 2026
b76af1e
fix: make v3 wiki migration resumable (#620)
NagariaHussain May 7, 2026
13e6925
Merge pull request #621 from frappe/develop
NagariaHussain May 8, 2026
01add4c
feat: add wiki document pdf downloads
NagariaHussain May 13, 2026
fb70e9a
feat: show wiki download loading state
NagariaHussain May 13, 2026
4c2e9d0
refactor: rename standard wiki document print format
NagariaHussain May 14, 2026
862f8ba
refactor: simplify wiki pdf format rollout
NagariaHussain May 14, 2026
38c10c1
refactor: clean up wiki pdf download
NagariaHussain May 15, 2026
5978f59
Merge pull request #624 from NagariaHussain/feature/download-pdf
NagariaHussain May 15, 2026
e66229a
fix: wiki pdf download follow-ups
NagariaHussain May 15, 2026
97015e5
fix: reset bootstrap print defaults on pdf blockquote
NagariaHussain May 15, 2026
d4bc0e9
Merge pull request #625 from NagariaHussain/fix/wiki-pdf-followups
NagariaHussain May 15, 2026
7b6e808
fix: style callouts in wiki pdf download
NagariaHussain May 15, 2026
76f63d5
Merge pull request #626 from NagariaHussain/fix/wiki-pdf-callout-styles
NagariaHussain May 15, 2026
cc021d1
fix(wiki): order feedback reactions bad -> good
gajjug004 May 20, 2026
30b4ac7
Merge pull request #628 from gajjug004/fix/wiki-feedback-reaction-order
NagariaHussain May 20, 2026
27874a7
fix: prevent indexing wiki app routes
NagariaHussain May 25, 2026
58dfa18
Merge pull request #632 from NagariaHussain/fix/noindex-wiki-app-routes
NagariaHussain May 25, 2026
6420bd3
Merge pull request #633 from frappe/develop
NagariaHussain May 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ wiki/www/wiki.html
wiki/public/css/tailwind.css
playwright-report/
test-results/
e2e/.auth/
e2e/.auth/
.env
screenshots/
173 changes: 173 additions & 0 deletions e2e/tests/callout-rich-text.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { expect, test } from '@playwright/test';

test.describe('Callout Rich Text Editing', () => {
/**
* Helper: navigate to a space and create a new page, returning the editor locator.
*/
async function createPageAndOpenEditor(
page: import('@playwright/test').Page,
pageTitle: string,
) {
await page.goto('/wiki');
await page.waitForLoadState('networkidle');

const spaceLink = page.locator('a[href*="/wiki/spaces/"]').first();
await expect(spaceLink).toBeVisible({ timeout: 5000 });
await spaceLink.click();
await page.waitForLoadState('networkidle');

const createFirstPage = page.locator(
'button:has-text("Create First Page")',
);
const newPageButton = page.locator('button[title="New Page"]');

if (await createFirstPage.isVisible({ timeout: 2000 }).catch(() => false)) {
await createFirstPage.click();
} else {
await newPageButton.click();
}

await page.getByLabel('Title').fill(pageTitle);
await page
.getByRole('dialog')
.getByRole('button', { name: 'Save Draft' })
.click();
await page.waitForLoadState('networkidle');

await page.locator('aside').getByText(pageTitle, { exact: true }).click();

const editor = page.locator('.ProseMirror, [contenteditable="true"]');
await expect(editor).toBeVisible({ timeout: 10000 });
return editor;
}

test('callout should round-trip inline markdown (bold, italic, links)', async ({
page,
}) => {
const pageTitle = `callout-rt-${Date.now()}`;
await createPageAndOpenEditor(page, pageTitle);

const result = await page.evaluate(() => {
const ed = document.querySelector('.ProseMirror') as HTMLElement & {
editor?: {
commands: {
setContent: (c: string, o?: object) => void;
};
getMarkdown: () => string;
getHTML: () => string;
getJSON: () => {
type: string;
content: { type: string; attrs?: Record<string, unknown> }[];
};
};
};
const editor = ed?.editor;
if (!editor) return { error: 'editor not found' };

// Set content with a callout using markdown syntax
const calloutContent =
'This has **bold** and *italic* and [a link](https://example.com)';
editor.commands.setContent(`:::note[Test]\n${calloutContent}\n:::`, {
contentType: 'markdown',
});

const md1 = editor.getMarkdown();

// Round-trip: parse the output back
editor.commands.setContent(md1, { contentType: 'markdown' });
const md2 = editor.getMarkdown();
const json = editor.getJSON();

// Find the callout block in the JSON
const calloutNode = json.content?.find(
(n: { type: string }) => n.type === 'calloutBlock',
);

return {
md1,
md2,
roundTrip: md1 === md2,
calloutContent: calloutNode?.attrs?.content,
hasCallout: !!calloutNode,
};
});

expect(result).not.toHaveProperty('error');
expect(result.hasCallout).toBe(true);

// Content should preserve inline markdown
expect(result.calloutContent).toContain('**bold**');
expect(result.calloutContent).toContain('*italic*');
expect(result.calloutContent).toContain('[a link](https://example.com)');

// Round-trip should be stable
expect(result.roundTrip).toBe(true);
});

test('callout view mode should render formatted HTML preview', async ({
page,
}) => {
const pageTitle = `callout-preview-${Date.now()}`;
await createPageAndOpenEditor(page, pageTitle);

// Set content with a callout using markdown syntax
await page.evaluate(() => {
const ed = document.querySelector('.ProseMirror') as HTMLElement & {
editor?: {
commands: { setContent: (c: string, o?: object) => void };
};
};
ed?.editor?.commands.setContent(
':::tip\nUse **bold** for emphasis and *italic* for style\n:::',
{ contentType: 'markdown' },
);
});

// The callout should render in view mode (not editing) with formatted text
const calloutContent = page.locator(
'.callout-block-wrapper .callout-content-text',
);
await expect(calloutContent).toBeVisible({ timeout: 5000 });

// Check that bold and italic are rendered as HTML
const html = await calloutContent.innerHTML();
expect(html).toContain('<strong>bold</strong>');
expect(html).toContain('<em>italic</em>');
});

test('callout sub-editor should appear on double-click', async ({ page }) => {
const pageTitle = `callout-edit-${Date.now()}`;
await createPageAndOpenEditor(page, pageTitle);

// Set content with a callout using markdown syntax
await page.evaluate(() => {
const ed = document.querySelector('.ProseMirror') as HTMLElement & {
editor?: {
commands: { setContent: (c: string, o?: object) => void };
};
};
ed?.editor?.commands.setContent(':::note\nSome content here\n:::', {
contentType: 'markdown',
});
});

// Double-click the callout content area to enter edit mode
const calloutContent = page
.locator('.callout-block-wrapper .callout-content-text')
.first();
await expect(calloutContent).toBeVisible({ timeout: 5000 });
await calloutContent.dblclick();

// The sub-editor (a nested ProseMirror instance) and toolbar should appear
const subEditor = page.locator(
'.callout-block-wrapper .callout-sub-editor-content',
);
await expect(subEditor).toBeVisible({ timeout: 5000 });

// Toolbar buttons (B, I, Link) should be visible
const toolbar = page.locator(
'.callout-block-wrapper .flex.items-center.gap-0\\.5',
);
await expect(toolbar).toBeVisible();
});
});
186 changes: 186 additions & 0 deletions e2e/tests/iframe-embed.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { expect, test } from '@playwright/test';

/**
* Covers the iframe embed extension added for frappe/wiki#599.
*
* The fixture below is exactly the iframe YouTube's Share → Embed dialog
* produces today — full attribute set (width, height, allow, referrerpolicy,
* allowfullscreen). That's the realistic paste we need to support.
*/
const IFRAME_FIXTURE =
'<iframe width="560" height="315" ' +
'src="https://www.youtube.com/embed/QDia3e12czc?si=8or3Lz5IEeelsdcF" ' +
'title="YouTube video player" frameborder="0" ' +
'allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" ' +
'referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>';

const IFRAME_SRC =
'https://www.youtube.com/embed/QDia3e12czc?si=8or3Lz5IEeelsdcF';

declare global {
interface Window {
wikiEditor: {
commands: {
setContent: (
content: string,
options?: { contentType?: string },
) => void;
setIframe?: (attrs: Record<string, unknown>) => boolean;
};
getMarkdown: () => string;
getJSON: () => {
type: string;
content: { type: string; attrs?: Record<string, unknown> }[];
};
};
}
}

/**
* Create a draft page and open the editor. Mirrors the helper in
* image-viewer.spec.ts — duplicated here rather than exported so changes
* to one test don't ripple into others.
*/
async function createDraftAndOpenEditor(
page: import('@playwright/test').Page,
title: string,
) {
await page.goto('/wiki');
await page.waitForLoadState('networkidle');

const spaceLink = page.locator('a[href*="/wiki/spaces/"]').first();
await expect(spaceLink).toBeVisible({ timeout: 5000 });
await spaceLink.click();
await page.waitForLoadState('networkidle');

const createFirstPage = page.locator('button:has-text("Create First Page")');
const newPageButton = page.locator('button[title="New Page"]');

if (await createFirstPage.isVisible({ timeout: 2000 }).catch(() => false)) {
await createFirstPage.click();
} else {
await newPageButton.click();
}

await page.getByLabel('Title').fill(title);
await page
.getByRole('dialog')
.getByRole('button', { name: 'Save Draft' })
.click();
await page.waitForLoadState('networkidle');

await page.locator('aside').getByText(title, { exact: true }).click();

const editor = page.locator('.ProseMirror, [contenteditable="true"]');
await expect(editor).toBeVisible({ timeout: 10000 });

await page.waitForFunction(() => window.wikiEditor !== undefined, {
timeout: 10000,
});
return editor;
}

test.describe('Iframe embed extension', () => {
test('parses a YouTube iframe HTML block from markdown into a node', async ({
page,
}) => {
await createDraftAndOpenEditor(page, `iframe-parse-${Date.now()}`);

const result = await page.evaluate((html) => {
window.wikiEditor.commands.setContent(html, { contentType: 'markdown' });
const json = window.wikiEditor.getJSON();
const block = json.content?.find((n) => n.type === 'iframeBlock');
return {
hasBlock: !!block,
src: block?.attrs?.src as string | undefined,
allowfullscreen: block?.attrs?.allowfullscreen as boolean | undefined,
title: block?.attrs?.title as string | undefined,
width: block?.attrs?.width as string | undefined,
height: block?.attrs?.height as string | undefined,
};
}, IFRAME_FIXTURE);

expect(result.hasBlock).toBe(true);
expect(result.src).toBe(IFRAME_SRC);
expect(result.allowfullscreen).toBe(true);
expect(result.title).toBe('YouTube video player');
expect(result.width).toBe('560');
expect(result.height).toBe('315');
});

test('renders the iframe preview inside the editor', async ({ page }) => {
await createDraftAndOpenEditor(page, `iframe-preview-${Date.now()}`);

await page.evaluate((html) => {
window.wikiEditor.commands.setContent(html, { contentType: 'markdown' });
}, IFRAME_FIXTURE);

const preview = page.locator(
'.iframe-block-wrapper iframe[src*="youtube.com/embed"]',
);
await expect(preview).toBeVisible({ timeout: 5000 });
await expect(preview).toHaveAttribute('src', IFRAME_SRC);
});

test('round-trips iframe markdown without mutating the src', async ({
page,
}) => {
await createDraftAndOpenEditor(page, `iframe-roundtrip-${Date.now()}`);

const { md1, md2 } = await page.evaluate((html) => {
window.wikiEditor.commands.setContent(html, { contentType: 'markdown' });
const md1 = window.wikiEditor.getMarkdown();
// Second pass: re-parse the serialized markdown and re-serialize.
// This is the cycle that used to compound-escape before the extension.
window.wikiEditor.commands.setContent(md1, { contentType: 'markdown' });
const md2 = window.wikiEditor.getMarkdown();
return { md1, md2 };
}, IFRAME_FIXTURE);

// Both passes preserve the raw src — no &lt;, no &amp;lt; leaking in.
expect(md1).toContain(`src="${IFRAME_SRC}"`);
expect(md1).not.toMatch(/&lt;iframe|&amp;lt;/);
expect(md2).toContain(`src="${IFRAME_SRC}"`);
expect(md2).not.toMatch(/&lt;iframe|&amp;lt;/);

// Serialization must be idempotent — a second round-trip shouldn't drift.
expect(md2).toBe(md1);
});

test('accepts the full iframe tag in the /embed URL input', async ({
page,
}) => {
await createDraftAndOpenEditor(page, `iframe-slash-${Date.now()}`);

// Insert an empty placeholder via the extension command (skips the
// slash-menu fuzzy-find noise and tests the URL input directly).
await page.evaluate(() => {
const editor = window.wikiEditor as unknown as {
commands: { insertIframePlaceholder: () => boolean };
};
editor.commands.insertIframePlaceholder();
});

const placeholderInput = page
.locator('.iframe-block-wrapper')
.getByPlaceholder('https://');
await expect(placeholderInput).toBeVisible({ timeout: 5000 });

await placeholderInput.fill(IFRAME_FIXTURE);
await page
.locator('.iframe-block-wrapper')
.getByRole('button', { name: 'Embed' })
.click();

const preview = page.locator(
'.iframe-block-wrapper iframe[src*="youtube.com/embed"]',
);
await expect(preview).toBeVisible({ timeout: 5000 });
await expect(preview).toHaveAttribute('src', IFRAME_SRC);

// Saved markdown reflects the attrs pulled from the pasted iframe HTML.
const md = await page.evaluate(() => window.wikiEditor.getMarkdown());
expect(md).toContain(`src="${IFRAME_SRC}"`);
expect(md).toContain('title="YouTube video player"');
});
});
Loading