diff --git a/frontend/core/__tests__/ArticleForm.test.jsx b/frontend/core/__tests__/ArticleForm.test.jsx
index d773260..25ca4a3 100644
--- a/frontend/core/__tests__/ArticleForm.test.jsx
+++ b/frontend/core/__tests__/ArticleForm.test.jsx
@@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
-var mockEditor = {
+const mockEditor = {
document: [
{ id: 'p1', type: 'paragraph' },
{ id: 'img1', type: 'image' },
@@ -10,15 +10,15 @@ var mockEditor = {
setTextCursorPosition: vi.fn(),
};
-var mousedownHandler = function (e) {
- var target = e.target;
+const mousedownHandler = (e) => {
+ let target = e.target;
if (target.nodeType === 3) target = target.parentNode;
if (target?.closest?.('.bn-formatting-toolbar')) return;
if (target?.closest?.('.bn-block-content[data-content-type="image"]')) return;
if (target?.closest?.('.bn-block-content[data-content-type="video"]')) return;
try {
- var doc = mockEditor.document;
- for (var i = 0; i < doc.length; i++) {
+ const doc = mockEditor.document;
+ for (let i = 0; i < doc.length; i++) {
if (doc[i].type !== 'image' && doc[i].type !== 'video') {
mockEditor.setTextCursorPosition(doc[i].id, 'start');
break;
@@ -28,54 +28,54 @@ var mousedownHandler = function (e) {
};
function createBlock(contentType) {
- var el = document.createElement('div');
+ const el = document.createElement('div');
el.className = 'bn-block-content';
el.setAttribute('data-content-type', contentType);
return el;
}
-describe('ArticleForm mousedown handler', function () {
- beforeEach(function () {
+describe('ArticleForm mousedown handler', () => {
+ beforeEach(() => {
document.body.innerHTML = '';
mockEditor.setTextCursorPosition.mockClear();
document.addEventListener('mousedown', mousedownHandler, true);
});
- afterEach(function () {
+ afterEach(() => {
document.removeEventListener('mousedown', mousedownHandler, true);
document.body.innerHTML = '';
});
- it('moves cursor to first text block when clicking outside image/video', function () {
+ it('moves cursor to first text block when clicking outside image/video', () => {
document.body.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
expect(mockEditor.setTextCursorPosition).toHaveBeenCalledOnce();
expect(mockEditor.setTextCursorPosition).toHaveBeenCalledWith('p1', 'start');
});
- it('returns early when clicking on image block', function () {
- var block = createBlock('image');
+ it('returns early when clicking on image block', () => {
+ const block = createBlock('image');
document.body.appendChild(block);
block.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
expect(mockEditor.setTextCursorPosition).not.toHaveBeenCalled();
});
- it('returns early when clicking on video block', function () {
- var block = createBlock('video');
+ it('returns early when clicking on video block', () => {
+ const block = createBlock('video');
document.body.appendChild(block);
block.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
expect(mockEditor.setTextCursorPosition).not.toHaveBeenCalled();
});
- it('returns early when clicking on formatting toolbar', function () {
- var toolbar = document.createElement('div');
+ it('returns early when clicking on formatting toolbar', () => {
+ const toolbar = document.createElement('div');
toolbar.className = 'bn-formatting-toolbar';
document.body.appendChild(toolbar);
toolbar.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
expect(mockEditor.setTextCursorPosition).not.toHaveBeenCalled();
});
- it('moves cursor to paragraph after image blocks when image clicked then outside', function () {
- var block = createBlock('video');
+ it('moves cursor to paragraph after image blocks when image clicked then outside', () => {
+ const block = createBlock('video');
document.body.appendChild(block);
block.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
expect(mockEditor.setTextCursorPosition).not.toHaveBeenCalled();
diff --git a/frontend/core/__tests__/ArticleViewer.test.jsx b/frontend/core/__tests__/ArticleViewer.test.jsx
index 3848750..57a4fdd 100644
--- a/frontend/core/__tests__/ArticleViewer.test.jsx
+++ b/frontend/core/__tests__/ArticleViewer.test.jsx
@@ -1,17 +1,17 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
function handler(e) {
- var block = document.querySelector(
+ const block = document.querySelector(
'.bn-block-content[data-content-type="image"].ProseMirror-selectednode',
);
if (!block) return;
- var imgEl = block.querySelector('img');
+ const imgEl = block.querySelector('img');
if (!imgEl) return;
e.preventDefault();
e.stopPropagation();
- var alt = imgEl.alt || '';
- var src = imgEl.getAttribute('src') || '';
- var text;
+ const alt = imgEl.alt || '';
+ const src = imgEl.getAttribute('src') || '';
+ let text;
if (src && !src.startsWith('/uploads/')) {
text = src;
} else {
@@ -21,27 +21,27 @@ function handler(e) {
new ClipboardItem({
'text/plain': new Blob([text], { type: 'text/plain' }),
}),
- ]).catch(function () {});
+ ]).catch(() => {});
}
function setImageDOM(alt, src, hasSelection) {
- var outer = document.createElement('div');
+ const outer = document.createElement('div');
outer.className = hasSelection
? 'bn-block-content ProseMirror-selectednode'
: 'bn-block-content';
outer.setAttribute('data-content-type', 'image');
- var img = document.createElement('img');
+ const img = document.createElement('img');
if (alt) img.alt = alt;
img.setAttribute('src', src);
outer.appendChild(img);
document.body.appendChild(outer);
- return { outer: outer, img: img };
+ return { outer, img };
}
-describe('ArticleViewer copy handler', function () {
- var mockWrite;
+describe('ArticleViewer copy handler', () => {
+ let mockWrite;
- beforeEach(function () {
+ beforeEach(() => {
mockWrite = vi.fn().mockResolvedValue(undefined);
vi.stubGlobal('navigator', {
clipboard: { write: mockWrite },
@@ -53,51 +53,51 @@ describe('ArticleViewer copy handler', function () {
document.addEventListener('copy', handler, true);
});
- afterEach(function () {
+ afterEach(() => {
document.removeEventListener('copy', handler, true);
vi.unstubAllGlobals();
vi.clearAllMocks();
document.body.innerHTML = '';
});
- it('writes alt text for selected upload image', async function () {
+ it('writes alt text for selected upload image', async () => {
setImageDOM('my photo', '/uploads/foo.jpg', true);
document.dispatchEvent(new ClipboardEvent('copy', { cancelable: true, bubbles: true }));
expect(mockWrite).toHaveBeenCalledOnce();
- var text = await mockWrite.mock.calls[0][0][0].items['text/plain'].text();
+ const text = await mockWrite.mock.calls[0][0][0].items['text/plain'].text();
expect(text).toBe('my photo');
});
- it('writes filename when upload image has no alt', async function () {
+ it('writes filename when upload image has no alt', async () => {
setImageDOM('', '/uploads/foo.jpg', true);
document.dispatchEvent(new ClipboardEvent('copy', { cancelable: true, bubbles: true }));
expect(mockWrite).toHaveBeenCalledOnce();
- var text = await mockWrite.mock.calls[0][0][0].items['text/plain'].text();
+ const text = await mockWrite.mock.calls[0][0][0].items['text/plain'].text();
expect(text).toBe('foo.jpg');
});
- it('writes URL for external image', async function () {
+ it('writes URL for external image', async () => {
setImageDOM('alt text', 'https://example.com/img.jpg', true);
document.dispatchEvent(new ClipboardEvent('copy', { cancelable: true, bubbles: true }));
expect(mockWrite).toHaveBeenCalledOnce();
- var text = await mockWrite.mock.calls[0][0][0].items['text/plain'].text();
+ const text = await mockWrite.mock.calls[0][0][0].items['text/plain'].text();
expect(text).toBe('https://example.com/img.jpg');
});
- it('does nothing when image lacks .ProseMirror-selectednode', function () {
+ it('does nothing when image lacks .ProseMirror-selectednode', () => {
setImageDOM('alt', '/uploads/foo.jpg', false);
document.dispatchEvent(new ClipboardEvent('copy', { cancelable: true, bubbles: true }));
expect(mockWrite).not.toHaveBeenCalled();
});
- it('does nothing when no image DOM at all', function () {
+ it('does nothing when no image DOM at all', () => {
document.dispatchEvent(new ClipboardEvent('copy', { cancelable: true, bubbles: true }));
expect(mockWrite).not.toHaveBeenCalled();
});
- it('stops event propagation for selected image', function () {
- var child = document.createElement('div');
- child.addEventListener('copy', function (e) {
+ it('stops event propagation for selected image', () => {
+ const child = document.createElement('div');
+ child.addEventListener('copy', (e) => {
expect(e.defaultPrevented).toBe(true);
expect(e.cancelBubble).toBe(true);
});
@@ -107,7 +107,7 @@ describe('ArticleViewer copy handler', function () {
});
});
-var mockEditor = {
+const mockEditor = {
document: [
{ id: 'p1', type: 'paragraph' },
{ id: 'img1', type: 'image' },
@@ -117,13 +117,13 @@ var mockEditor = {
setTextCursorPosition: vi.fn(),
};
-var viewerHandler = function (e) {
- var target = e.target;
+const viewerHandler = (e) => {
+ let target = e.target;
if (target.nodeType === 3) target = target.parentNode;
if (target?.closest?.('.bn-block-content[data-content-type="image"]')) return;
try {
- var doc = mockEditor.document;
- for (var i = 0; i < doc.length; i++) {
+ const doc = mockEditor.document;
+ for (let i = 0; i < doc.length; i++) {
if (doc[i].type !== 'image' && doc[i].type !== 'video') {
mockEditor.setTextCursorPosition(doc[i].id, 'start');
break;
@@ -133,32 +133,32 @@ var viewerHandler = function (e) {
};
function createBlock(contentType) {
- var el = document.createElement('div');
+ const el = document.createElement('div');
el.className = 'bn-block-content';
el.setAttribute('data-content-type', contentType);
return el;
}
-describe('ArticleViewer mousedown handler', function () {
- beforeEach(function () {
+describe('ArticleViewer mousedown handler', () => {
+ beforeEach(() => {
document.body.innerHTML = '';
mockEditor.setTextCursorPosition.mockClear();
document.addEventListener('mousedown', viewerHandler, true);
});
- afterEach(function () {
+ afterEach(() => {
document.removeEventListener('mousedown', viewerHandler, true);
document.body.innerHTML = '';
});
- it('moves cursor to first text block when clicking outside image', function () {
+ it('moves cursor to first text block when clicking outside image', () => {
document.body.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
expect(mockEditor.setTextCursorPosition).toHaveBeenCalledOnce();
expect(mockEditor.setTextCursorPosition).toHaveBeenCalledWith('p1', 'start');
});
- it('returns early when clicking on image block', function () {
- var block = createBlock('image');
+ it('returns early when clicking on image block', () => {
+ const block = createBlock('image');
document.body.appendChild(block);
block.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
expect(mockEditor.setTextCursorPosition).not.toHaveBeenCalled();
diff --git a/frontend/core/__tests__/CopyBlockButton.test.jsx b/frontend/core/__tests__/CopyBlockButton.test.jsx
index ed9ac1a..c587bea 100644
--- a/frontend/core/__tests__/CopyBlockButton.test.jsx
+++ b/frontend/core/__tests__/CopyBlockButton.test.jsx
@@ -11,9 +11,9 @@ vi.mock('@blocknote/react', () => ({
import { useBlockNoteEditor, useComponentsContext } from '@blocknote/react';
function render(ui) {
- var container = document.createElement('div');
- var root = createRoot(container);
- act(function () { root.render(ui); });
+ const container = document.createElement('div');
+ const root = createRoot(container);
+ act(() => { root.render(ui); });
return container;
}
@@ -29,11 +29,11 @@ function mockButton(props) {
});
}
-describe('CopyBlockButton', function () {
- var mockWrite;
- var lastItems;
+describe('CopyBlockButton', () => {
+ let mockWrite;
+ let lastItems;
- beforeEach(function () {
+ beforeEach(() => {
vi.useFakeTimers();
mockWrite = vi.fn().mockResolvedValue(undefined);
lastItems = null;
@@ -45,7 +45,7 @@ describe('CopyBlockButton', function () {
}));
});
- afterEach(function () {
+ afterEach(() => {
vi.useRealTimers();
vi.unstubAllGlobals();
vi.clearAllMocks();
@@ -53,7 +53,7 @@ describe('CopyBlockButton', function () {
});
function setup(blockData) {
- var editor = {
+ const editor = {
getSelection: vi.fn().mockReturnValue({ blocks: [blockData] }),
getTextCursorPosition: vi.fn(),
};
@@ -64,145 +64,145 @@ describe('CopyBlockButton', function () {
return editor;
}
- describe('null guards', function () {
- it('returns null when editor is null', function () {
+ describe('null guards', () => {
+ it('returns null when editor is null', () => {
useBlockNoteEditor.mockReturnValue(null);
useComponentsContext.mockReturnValue({ FormattingToolbar: { Button: mockButton } });
- var container = render(React.createElement(CopyBlockButton));
+ const container = render(React.createElement(CopyBlockButton));
expect(container.innerHTML).toBe('');
});
- it('returns null when Components is null', function () {
+ it('returns null when Components is null', () => {
setup(makeBlock());
useComponentsContext.mockReturnValue(null);
- var container = render(React.createElement(CopyBlockButton));
+ const container = render(React.createElement(CopyBlockButton));
expect(container.innerHTML).toBe('');
});
- it('returns null when block is null', function () {
+ it('returns null when block is null', () => {
setup(null);
- var container = render(React.createElement(CopyBlockButton));
+ const container = render(React.createElement(CopyBlockButton));
expect(container.innerHTML).toBe('');
});
- it('handles selection error gracefully', function () {
- var editor = { getSelection: vi.fn().mockImplementation(function () { throw new Error('fail'); }) };
+ it('handles selection error gracefully', () => {
+ const editor = { getSelection: vi.fn().mockImplementation(() => { throw new Error('fail'); }) };
useBlockNoteEditor.mockReturnValue(editor);
useComponentsContext.mockReturnValue({ FormattingToolbar: { Button: mockButton } });
- var container = render(React.createElement(CopyBlockButton));
+ const container = render(React.createElement(CopyBlockButton));
expect(container.innerHTML).toBe('');
});
});
- describe('tooltip', function () {
- it('shows "Copy" for image block', function () {
+ describe('tooltip', () => {
+ it('shows "Copy" for image block', () => {
setup(makeBlock({ type: 'image' }));
- var container = render(React.createElement(CopyBlockButton));
- var btn = container.querySelector('[data-testid="copy-btn"]');
+ const container = render(React.createElement(CopyBlockButton));
+ const btn = container.querySelector('[data-testid="copy-btn"]');
expect(btn.dataset.tooltip).toBe('Copy');
});
- it('shows "Copy" for video block', function () {
+ it('shows "Copy" for video block', () => {
setup(makeBlock({ type: 'video' }));
- var container = render(React.createElement(CopyBlockButton));
- var btn = container.querySelector('[data-testid="copy-btn"]');
+ const container = render(React.createElement(CopyBlockButton));
+ const btn = container.querySelector('[data-testid="copy-btn"]');
expect(btn.dataset.tooltip).toBe('Copy');
});
- it('shows "Copy Block" for other block types', function () {
+ it('shows "Copy Block" for other block types', () => {
setup(makeBlock({ type: 'codeBlock' }));
- var container = render(React.createElement(CopyBlockButton));
- var btn = container.querySelector('[data-testid="copy-btn"]');
+ const container = render(React.createElement(CopyBlockButton));
+ const btn = container.querySelector('[data-testid="copy-btn"]');
expect(btn.dataset.tooltip).toBe('Copy Block');
});
});
- describe('plainText per block type', function () {
- it('image upload → writes alt text', async function () {
+ describe('plainText per block type', () => {
+ it('image upload → writes alt text', async () => {
setup(makeBlock({ type: 'image', props: { url: '/uploads/foo.jpg', alt: 'my photo' } }));
- var container = render(React.createElement(CopyBlockButton));
- act(function () { container.querySelector('[data-testid="copy-btn"]').click(); });
- var text = await lastItems['text/plain'].text();
+ const container = render(React.createElement(CopyBlockButton));
+ act(() => { container.querySelector('[data-testid="copy-btn"]').click(); });
+ const text = await lastItems['text/plain'].text();
expect(text).toBe('my photo');
});
- it('image upload without alt → writes name', async function () {
+ it('image upload without alt → writes name', async () => {
setup(makeBlock({ type: 'image', props: { url: '/uploads/foo.jpg', name: 'Foo' } }));
- var container = render(React.createElement(CopyBlockButton));
- act(function () { container.querySelector('[data-testid="copy-btn"]').click(); });
- var text = await lastItems['text/plain'].text();
+ const container = render(React.createElement(CopyBlockButton));
+ act(() => { container.querySelector('[data-testid="copy-btn"]').click(); });
+ const text = await lastItems['text/plain'].text();
expect(text).toBe('Foo');
});
- it('image external URL → writes URL (ignores alt)', async function () {
+ it('image external URL → writes URL (ignores alt)', async () => {
setup(makeBlock({ type: 'image', props: { url: 'https://example.com/img.jpg', alt: 'alt text' } }));
- var container = render(React.createElement(CopyBlockButton));
- act(function () { container.querySelector('[data-testid="copy-btn"]').click(); });
- var text = await lastItems['text/plain'].text();
+ const container = render(React.createElement(CopyBlockButton));
+ act(() => { container.querySelector('[data-testid="copy-btn"]').click(); });
+ const text = await lastItems['text/plain'].text();
expect(text).toBe('https://example.com/img.jpg');
});
- it('video → writes URL', async function () {
+ it('video → writes URL', async () => {
setup(makeBlock({ type: 'video', props: { url: 'https://youtube.com/watch?v=abc' } }));
- var container = render(React.createElement(CopyBlockButton));
- act(function () { container.querySelector('[data-testid="copy-btn"]').click(); });
- var text = await lastItems['text/plain'].text();
+ const container = render(React.createElement(CopyBlockButton));
+ act(() => { container.querySelector('[data-testid="copy-btn"]').click(); });
+ const text = await lastItems['text/plain'].text();
expect(text).toBe('https://youtube.com/watch?v=abc');
});
- it('other block (code) → writes URL', async function () {
+ it('other block (code) → writes URL', async () => {
setup(makeBlock({ type: 'codeBlock', props: { url: '' } }));
- var container = render(React.createElement(CopyBlockButton));
- act(function () { container.querySelector('[data-testid="copy-btn"]').click(); });
- var text = await lastItems['text/plain'].text();
+ const container = render(React.createElement(CopyBlockButton));
+ act(() => { container.querySelector('[data-testid="copy-btn"]').click(); });
+ const text = await lastItems['text/plain'].text();
expect(text).toBe('');
});
});
- describe('ClipboardItem', function () {
- it('contains text/plain and text/html entries', function () {
+ describe('ClipboardItem', () => {
+ it('contains text/plain and text/html entries', () => {
setup(makeBlock({ type: 'image', props: { url: '/uploads/foo.jpg', alt: 'img' } }));
- var container = render(React.createElement(CopyBlockButton));
- act(function () { container.querySelector('[data-testid="copy-btn"]').click(); });
+ const container = render(React.createElement(CopyBlockButton));
+ act(() => { container.querySelector('[data-testid="copy-btn"]').click(); });
expect(lastItems).toHaveProperty('text/plain');
expect(lastItems).toHaveProperty('text/html');
expect(Object.keys(lastItems).length).toBe(2);
});
- it('has correct text/html marker', async function () {
+ it('has correct text/html marker', async () => {
setup(makeBlock({ type: 'paragraph', props: { url: '' }, content: 'hello' }));
- var container = render(React.createElement(CopyBlockButton));
- act(function () { container.querySelector('[data-testid="copy-btn"]').click(); });
- var html = await lastItems['text/html'].text();
+ const container = render(React.createElement(CopyBlockButton));
+ act(() => { container.querySelector('[data-testid="copy-btn"]').click(); });
+ const html = await lastItems['text/html'].text();
expect(html).toContain('blocknote-block');
expect(html).toContain('paragraph');
});
});
- describe('toast', function () {
- it('shows toast on click', function () {
+ describe('toast', () => {
+ it('shows toast on click', () => {
setup(makeBlock({ type: 'paragraph' }));
- var container = render(React.createElement(CopyBlockButton));
- act(function () { container.querySelector('[data-testid="copy-btn"]').click(); });
- var toast = document.querySelector('.toast');
+ const container = render(React.createElement(CopyBlockButton));
+ act(() => { container.querySelector('[data-testid="copy-btn"]').click(); });
+ const toast = document.querySelector('.toast');
expect(toast).not.toBeNull();
expect(toast.textContent).toBe('Copied to clipboard');
});
- it('removes old toast before creating new one', function () {
+ it('removes old toast before creating new one', () => {
setup(makeBlock({ type: 'paragraph' }));
- var container = render(React.createElement(CopyBlockButton));
- var btn = container.querySelector('[data-testid="copy-btn"]');
- act(function () { btn.click(); });
- act(function () { btn.click(); });
- var toasts = document.querySelectorAll('.toast');
+ const container = render(React.createElement(CopyBlockButton));
+ const btn = container.querySelector('[data-testid="copy-btn"]');
+ act(() => { btn.click(); });
+ act(() => { btn.click(); });
+ const toasts = document.querySelectorAll('.toast');
expect(toasts.length).toBe(1);
});
- it('removes toast after 2800ms', function () {
+ it('removes toast after 2800ms', () => {
setup(makeBlock({ type: 'paragraph' }));
- var container = render(React.createElement(CopyBlockButton));
- act(function () { container.querySelector('[data-testid="copy-btn"]').click(); });
+ const container = render(React.createElement(CopyBlockButton));
+ act(() => { container.querySelector('[data-testid="copy-btn"]').click(); });
expect(document.querySelector('.toast')).not.toBeNull();
vi.advanceTimersByTime(2800);
expect(document.querySelector('.toast')).toBeNull();
diff --git a/frontend/core/__tests__/custom-code-block-spec.test.js b/frontend/core/__tests__/custom-code-block-spec.test.js
index dde2daf..7c3ba0b 100644
--- a/frontend/core/__tests__/custom-code-block-spec.test.js
+++ b/frontend/core/__tests__/custom-code-block-spec.test.js
@@ -646,7 +646,7 @@ describe('createCustomCodeBlockSpec', () => {
spec.implementation.render(block, editor);
const deleteBtn = outerDiv.querySelector('.code-delete-btn');
- deleteBtn.click();
+ deleteBtn.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
expect(editor.removeBlocks).toHaveBeenCalledTimes(1);
expect(editor.removeBlocks).toHaveBeenCalledWith([block]);
diff --git a/frontend/core/__tests__/custom-formatting-toolbar.test.js b/frontend/core/__tests__/custom-formatting-toolbar.test.js
index 5608080..530bf3e 100644
--- a/frontend/core/__tests__/custom-formatting-toolbar.test.js
+++ b/frontend/core/__tests__/custom-formatting-toolbar.test.js
@@ -2,49 +2,49 @@ import { describe, it, expect } from 'vitest';
import { filterVideoToolbarItems, filterImageToolbarItems } from '../components/CustomFormattingToolbar';
-var MockReplace = function MockReplace() {};
-var MockDownload = function MockDownload() {};
-var MockCaption = function MockCaption() {};
-var MockRename = function MockRename() {};
-var MockPreview = function MockPreview() {};
-var MockNormal = function MockNormal() {};
+const MockReplace = () => {};
+const MockDownload = () => {};
+const MockCaption = () => {};
+const MockRename = () => {};
+const MockPreview = () => {};
+const MockNormal = () => {};
-var HIDDEN = [MockReplace, MockDownload, MockCaption, MockRename, MockPreview];
+const HIDDEN = [MockReplace, MockDownload, MockCaption, MockRename, MockPreview];
-describe('filterVideoToolbarItems', function () {
- it('filters out FileReplaceButton', function () {
+describe('filterVideoToolbarItems', () => {
+ it('filters out FileReplaceButton', () => {
expect(filterVideoToolbarItems([{ type: MockReplace }], HIDDEN)).toEqual([]);
});
- it('filters out FileDownloadButton', function () {
+ it('filters out FileDownloadButton', () => {
expect(filterVideoToolbarItems([{ type: MockDownload }], HIDDEN)).toEqual([]);
});
- it('filters out FileCaptionButton', function () {
+ it('filters out FileCaptionButton', () => {
expect(filterVideoToolbarItems([{ type: MockCaption }], HIDDEN)).toEqual([]);
});
- it('filters out FileRenameButton', function () {
+ it('filters out FileRenameButton', () => {
expect(filterVideoToolbarItems([{ type: MockRename }], HIDDEN)).toEqual([]);
});
- it('filters out FilePreviewButton', function () {
+ it('filters out FilePreviewButton', () => {
expect(filterVideoToolbarItems([{ type: MockPreview }], HIDDEN)).toEqual([]);
});
- it('filters out items with textAlignment prop', function () {
- var items = [{ type: MockNormal, props: { textAlignment: 'right' } }];
+ it('filters out items with textAlignment prop', () => {
+ const items = [{ type: MockNormal, props: { textAlignment: 'right' } }];
expect(filterVideoToolbarItems(items, HIDDEN)).toEqual([]);
});
- it('keeps normal buttons', function () {
- var items = [{ type: MockNormal, props: {} }, { type: MockNormal }];
+ it('keeps normal buttons', () => {
+ const items = [{ type: MockNormal, props: {} }, { type: MockNormal }];
expect(filterVideoToolbarItems(items, HIDDEN)).toHaveLength(2);
});
- it('filters multiple items together', function () {
- var items = [
+ it('filters multiple items together', () => {
+ const items = [
{ type: MockNormal },
{ type: MockReplace },
{ type: MockDownload },
@@ -57,13 +57,13 @@ describe('filterVideoToolbarItems', function () {
expect(filterVideoToolbarItems(items, HIDDEN)).toHaveLength(2);
});
- it('returns empty array for empty input', function () {
+ it('returns empty array for empty input', () => {
expect(filterVideoToolbarItems([], HIDDEN)).toEqual([]);
});
- it('uses default hidden types when none provided', function () {
- var MockUnknown = function MockUnknown() {};
- var items = [{ type: MockUnknown }, { type: MockNormal }];
+ it('uses default hidden types when none provided', () => {
+ const MockUnknown = () => {};
+ const items = [{ type: MockUnknown }, { type: MockNormal }];
// default HIDDEN_VIDEO_BUTTONS = real File*Button from @blocknote/react
// These won't match our mocks, so both items pass
// This just verifies the fallback doesn't crash
@@ -71,12 +71,12 @@ describe('filterVideoToolbarItems', function () {
});
});
-describe('filterImageToolbarItems', function () {
- it('filters out FileDownloadButton', function () {
+describe('filterImageToolbarItems', () => {
+ it('filters out FileDownloadButton', () => {
expect(filterImageToolbarItems([{ type: MockDownload }], [MockDownload])).toEqual([]);
});
- it('keeps normal buttons', function () {
+ it('keeps normal buttons', () => {
expect(filterImageToolbarItems([{ type: MockNormal }])).toHaveLength(1);
});
});
diff --git a/frontend/core/__tests__/useCodeBlockGapClick.test.js b/frontend/core/__tests__/useCodeBlockGapClick.test.js
index 2c11b0e..4f4d22b 100644
--- a/frontend/core/__tests__/useCodeBlockGapClick.test.js
+++ b/frontend/core/__tests__/useCodeBlockGapClick.test.js
@@ -8,20 +8,20 @@ function TestHarness({ editorRef }) {
return null;
}
-var roots = [];
+let roots = [];
function render(ui) {
- var container = document.createElement('div');
+ const container = document.createElement('div');
container.id = 'test-root';
- var root = createRoot(container);
+ const root = createRoot(container);
roots.push(root);
- act(function () { root.render(ui); });
+ act(() => { root.render(ui); });
document.body.appendChild(container);
return container;
}
-afterEach(function () {
- roots.forEach(function (r) { r.unmount(); });
+afterEach(() => {
+ roots.forEach((r) => { r.unmount(); });
roots = [];
vi.useRealTimers();
document.body.innerHTML = '';
@@ -29,7 +29,7 @@ afterEach(function () {
});
function addBlockToDOM(props = {}) {
- var {
+ const {
blockId = 'block',
top = 0,
bottom = 100,
@@ -41,7 +41,7 @@ function addBlockToDOM(props = {}) {
useMediaWrapper = false,
} = props;
- var inner = document.createElement('div');
+ const inner = document.createElement('div');
if (useMediaWrapper) {
inner.className = 'bn-visual-media-wrapper';
} else {
@@ -49,22 +49,22 @@ function addBlockToDOM(props = {}) {
if (contentType) inner.setAttribute('data-content-type', contentType);
}
- var blockOuter = document.createElement('div');
+ const blockOuter = document.createElement('div');
blockOuter.className = 'bn-block-outer';
blockOuter.setAttribute('data-id', blockId);
vi.spyOn(blockOuter, 'getBoundingClientRect').mockReturnValue({
- top: top, bottom: bottom, left: left, right: right,
- width: width, height: height,
+ top, bottom, left, right,
+ width, height,
x: left, y: top,
- toJSON: function () {},
+ toJSON() {},
});
blockOuter.appendChild(inner);
- var container = document.getElementById('test-root') || document.body;
+ const container = document.getElementById('test-root') || document.body;
container.appendChild(blockOuter);
- return { inner: inner, blockOuter: blockOuter };
+ return { inner, blockOuter };
}
describe('useCodeBlockGapClick', () => {
@@ -266,17 +266,17 @@ describe('useCodeBlockGapClick', () => {
it('inserts paragraph when clicking below video block', () => {
vi.useFakeTimers();
- var insertBlocks = vi.fn(function () { return [{ id: 'new-vid-p' }]; });
- var focus = vi.fn();
- var documentBlocks = [{ id: 'vid-1', type: 'video' }];
- var editorRef = {
- current: { insertBlocks: insertBlocks, focus: focus, document: documentBlocks },
+ const insertBlocks = vi.fn(() => [{ id: 'new-vid-p' }]);
+ const focus = vi.fn();
+ const documentBlocks = [{ id: 'vid-1', type: 'video' }];
+ const editorRef = {
+ current: { insertBlocks, focus, document: documentBlocks },
};
- var container = render(React.createElement(TestHarness, { editorRef: editorRef }));
+ const container = render(React.createElement(TestHarness, { editorRef }));
addBlockToDOM({ blockId: 'vid-1', bottom: 100, useMediaWrapper: true });
- var event = new MouseEvent('mousedown', {
+ const event = new MouseEvent('mousedown', {
clientY: 108,
bubbles: true,
cancelable: true,
@@ -297,17 +297,17 @@ describe('useCodeBlockGapClick', () => {
it('inserts paragraph when clicking above video block', () => {
vi.useFakeTimers();
- var insertBlocks = vi.fn(function () { return [{ id: 'new-vid-p2' }]; });
- var focus = vi.fn();
- var documentBlocks = [{ id: 'vid-2', type: 'video' }];
- var editorRef = {
- current: { insertBlocks: insertBlocks, focus: focus, document: documentBlocks },
+ const insertBlocks = vi.fn(() => [{ id: 'new-vid-p2' }]);
+ const focus = vi.fn();
+ const documentBlocks = [{ id: 'vid-2', type: 'video' }];
+ const editorRef = {
+ current: { insertBlocks, focus, document: documentBlocks },
};
- var container = render(React.createElement(TestHarness, { editorRef: editorRef }));
+ const container = render(React.createElement(TestHarness, { editorRef }));
addBlockToDOM({ blockId: 'vid-2', top: 100, bottom: 200, useMediaWrapper: true });
- var event = new MouseEvent('mousedown', {
+ const event = new MouseEvent('mousedown', {
clientY: 92,
bubbles: true,
cancelable: true,
@@ -328,20 +328,20 @@ describe('useCodeBlockGapClick', () => {
it('skips insertion above video block when adjacent paragraph is empty', () => {
vi.useFakeTimers();
- var insertBlocks = vi.fn();
- var focus = vi.fn();
- var documentBlocks = [
+ const insertBlocks = vi.fn();
+ const focus = vi.fn();
+ const documentBlocks = [
{ id: 'p-before-vid', type: 'paragraph' },
{ id: 'vid-3', type: 'video' },
];
- var editorRef = {
- current: { document: documentBlocks, insertBlocks: insertBlocks, focus: focus },
+ const editorRef = {
+ current: { document: documentBlocks, insertBlocks, focus },
};
- var container = render(React.createElement(TestHarness, { editorRef: editorRef }));
+ const container = render(React.createElement(TestHarness, { editorRef }));
addBlockToDOM({ blockId: 'vid-3', top: 100, bottom: 200, useMediaWrapper: true });
- var event = new MouseEvent('mousedown', {
+ const event = new MouseEvent('mousedown', {
clientY: 92,
bubbles: true,
cancelable: true,
@@ -355,13 +355,13 @@ describe('useCodeBlockGapClick', () => {
});
it('does not insert when clicking far below video block', () => {
- var insertBlocks = vi.fn();
- var editorRef = { current: { insertBlocks: insertBlocks, focus: vi.fn(), setTextCursorPosition: vi.fn(), document: [] } };
+ const insertBlocks = vi.fn();
+ const editorRef = { current: { insertBlocks, focus: vi.fn(), setTextCursorPosition: vi.fn(), document: [] } };
- var container = render(React.createElement(TestHarness, { editorRef: editorRef }));
+ const container = render(React.createElement(TestHarness, { editorRef }));
addBlockToDOM({ blockId: 'vid-4', bottom: 100, useMediaWrapper: true });
- var event = new MouseEvent('mousedown', {
+ const event = new MouseEvent('mousedown', {
clientY: 200,
bubbles: true,
cancelable: true,
@@ -373,17 +373,17 @@ describe('useCodeBlockGapClick', () => {
it('inserts paragraph when clicking below image block', () => {
vi.useFakeTimers();
- var insertBlocks = vi.fn(function () { return [{ id: 'new-img-p' }]; });
- var focus = vi.fn();
- var documentBlocks = [{ id: 'img-1', type: 'image' }];
- var editorRef = {
- current: { insertBlocks: insertBlocks, focus: focus, document: documentBlocks },
+ const insertBlocks = vi.fn(() => [{ id: 'new-img-p' }]);
+ const focus = vi.fn();
+ const documentBlocks = [{ id: 'img-1', type: 'image' }];
+ const editorRef = {
+ current: { insertBlocks, focus, document: documentBlocks },
};
- var container = render(React.createElement(TestHarness, { editorRef: editorRef }));
+ const container = render(React.createElement(TestHarness, { editorRef }));
addBlockToDOM({ blockId: 'img-1', bottom: 100, contentType: 'image' });
- var event = new MouseEvent('mousedown', {
+ const event = new MouseEvent('mousedown', {
clientY: 108,
bubbles: true,
cancelable: true,
@@ -419,29 +419,29 @@ describe('useCodeBlockGapClick', () => {
render(React.createElement(TestHarness, { editorRef }));
- var p = document.createElement('p');
+ const p = document.createElement('p');
p.innerHTML = '
';
document.body.appendChild(p);
- var sel = window.getSelection();
- var range = document.createRange();
+ const sel = window.getSelection();
+ const range = document.createRange();
range.setStart(p, 0);
range.setEnd(p, 1);
sel.removeAllRanges();
sel.addRange(range);
expect(range.collapsed).toBe(false);
- var event = new KeyboardEvent('keydown', {
+ const event = new KeyboardEvent('keydown', {
key: 'Backspace', cancelable: true, bubbles: true,
});
- var preventSpy = vi.spyOn(event, 'preventDefault');
- var stopSpy = vi.spyOn(event, 'stopPropagation');
+ const preventSpy = vi.spyOn(event, 'preventDefault');
+ const stopSpy = vi.spyOn(event, 'stopPropagation');
document.dispatchEvent(event);
expect(preventSpy).toHaveBeenCalled();
expect(stopSpy).toHaveBeenCalled();
- var selAfter = window.getSelection();
+ const selAfter = window.getSelection();
expect(selAfter.rangeCount).toBe(1);
expect(selAfter.getRangeAt(0).collapsed).toBe(true);
});
@@ -461,20 +461,20 @@ describe('useCodeBlockGapClick', () => {
render(React.createElement(TestHarness, { editorRef }));
- var p = document.createElement('p');
+ const p = document.createElement('p');
document.body.appendChild(p);
- var sel = window.getSelection();
- var range = document.createRange();
+ const sel = window.getSelection();
+ const range = document.createRange();
range.setStart(p, 0);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
- var event = new KeyboardEvent('keydown', {
+ const event = new KeyboardEvent('keydown', {
key: 'Backspace', cancelable: true, bubbles: true,
});
- var preventSpy = vi.spyOn(event, 'preventDefault');
+ const preventSpy = vi.spyOn(event, 'preventDefault');
document.dispatchEvent(event);
expect(preventSpy).not.toHaveBeenCalled();
diff --git a/frontend/core/__tests__/video-dict.test.js b/frontend/core/__tests__/video-dict.test.js
index 1771b7f..0863ec8 100644
--- a/frontend/core/__tests__/video-dict.test.js
+++ b/frontend/core/__tests__/video-dict.test.js
@@ -39,46 +39,46 @@ function makeMockEditor() {
}
-describe('applyVideoDictOverrides', function () {
- it('renames slash menu title to YouTube', function () {
- var editor = makeMockEditor();
+describe('applyVideoDictOverrides', () => {
+ it('renames slash menu title to YouTube', () => {
+ const editor = makeMockEditor();
applyVideoDictOverrides(editor);
expect(editor.dictionary.slash_menu.video.title).toBe('YouTube');
});
- it('sets slash menu subtext', function () {
- var editor = makeMockEditor();
+ it('sets slash menu subtext', () => {
+ const editor = makeMockEditor();
applyVideoDictOverrides(editor);
expect(editor.dictionary.slash_menu.video.subtext).toBe('Paste a YouTube video URL');
});
- it('adds youtube and yt aliases', function () {
- var editor = makeMockEditor();
+ it('adds youtube and yt aliases', () => {
+ const editor = makeMockEditor();
applyVideoDictOverrides(editor);
expect(editor.dictionary.slash_menu.video.aliases).toContain('youtube');
expect(editor.dictionary.slash_menu.video.aliases).toContain('yt');
});
- it('renames file panel embed tab title', function () {
- var editor = makeMockEditor();
+ it('renames file panel embed tab title', () => {
+ const editor = makeMockEditor();
applyVideoDictOverrides(editor);
expect(editor.dictionary.file_panel.embed.title).toBe('YouTube URL');
});
- it('updates embed button text for video', function () {
- var editor = makeMockEditor();
+ it('updates embed button text for video', () => {
+ const editor = makeMockEditor();
applyVideoDictOverrides(editor);
expect(editor.dictionary.file_panel.embed.embed_button.video).toBe('Embed YouTube video');
});
- it('updates embed placeholder', function () {
- var editor = makeMockEditor();
+ it('updates embed placeholder', () => {
+ const editor = makeMockEditor();
applyVideoDictOverrides(editor);
expect(editor.dictionary.file_panel.embed.url_placeholder).toBe('Paste YouTube video link');
});
- it('updates add button text for video', function () {
- var editor = makeMockEditor();
+ it('updates add button text for video', () => {
+ const editor = makeMockEditor();
applyVideoDictOverrides(editor);
expect(editor.dictionary.file_blocks.add_button_text.video).toBe('Add YouTube video URL');
});
diff --git a/frontend/core/__tests__/video-override-spec.test.js b/frontend/core/__tests__/video-override-spec.test.js
index 8cb972c..a7d2d45 100644
--- a/frontend/core/__tests__/video-override-spec.test.js
+++ b/frontend/core/__tests__/video-override-spec.test.js
@@ -10,15 +10,15 @@ function makeEditor(editable) {
return { isEditable: editable, removeBlocks: vi.fn() };
}
-describe('createYouTubeVideoSpec', function () {
- var origRender, origToExternalHTML, videoSpec;
+describe('createYouTubeVideoSpec', () => {
+ let origRender, origToExternalHTML, videoSpec;
- beforeEach(function () {
- document.querySelectorAll('.toast').forEach(function (t) { t.remove(); });
- origRender = vi.fn(function () {
+ beforeEach(() => {
+ document.querySelectorAll('.toast').forEach((t) => { t.remove(); });
+ origRender = vi.fn(() => {
return { dom: document.createElement('div') };
});
- origToExternalHTML = vi.fn(function () {
+ origToExternalHTML = vi.fn(() => {
return { dom: document.createElement('a') };
});
videoSpec = {
@@ -27,23 +27,23 @@ describe('createYouTubeVideoSpec', function () {
vi.useFakeTimers();
});
- afterEach(function () {
- document.querySelectorAll('.toast').forEach(function (t) { t.remove(); });
+ afterEach(() => {
+ document.querySelectorAll('.toast').forEach((t) => { t.remove(); });
vi.useRealTimers();
});
- it('returns spec with same shape', function () {
- var spec = createYouTubeVideoSpec(videoSpec);
+ it('returns spec with same shape', () => {
+ const spec = createYouTubeVideoSpec(videoSpec);
expect(spec).toHaveProperty('implementation');
expect(spec.implementation).toHaveProperty('render');
expect(spec.implementation).toHaveProperty('toExternalHTML');
});
- describe('render', function () {
- it('returns bn-block-content with iframe for YouTube URL', function () {
- var spec = createYouTubeVideoSpec(videoSpec);
- var editor = makeEditor(true);
- var result = spec.implementation.render(makeBlock('https://www.youtube.com/watch?v=eme8BnMFthI'), editor);
+ describe('render', () => {
+ it('returns bn-block-content with iframe for YouTube URL', () => {
+ const spec = createYouTubeVideoSpec(videoSpec);
+ const editor = makeEditor(true);
+ const result = spec.implementation.render(makeBlock('https://www.youtube.com/watch?v=eme8BnMFthI'), editor);
expect(result.dom.className).toContain('bn-block-content');
expect(result.dom.querySelector('iframe')).toBeTruthy();
@@ -51,87 +51,87 @@ describe('createYouTubeVideoSpec', function () {
expect(origRender).not.toHaveBeenCalled();
});
- it('returns iframe without pointerEvents in viewer', function () {
- var spec = createYouTubeVideoSpec(videoSpec);
- var editor = makeEditor(false);
- var result = spec.implementation.render(makeBlock('https://youtu.be/abcdefghijk'), editor);
+ it('returns iframe without pointerEvents in viewer', () => {
+ const spec = createYouTubeVideoSpec(videoSpec);
+ const editor = makeEditor(false);
+ const result = spec.implementation.render(makeBlock('https://youtu.be/abcdefghijk'), editor);
- var iframe = result.dom.querySelector('iframe');
+ const iframe = result.dom.querySelector('iframe');
expect(iframe.style.pointerEvents).toBe('');
});
- it('shows toast and calls removeBlocks for non-YouTube URL in editor', function () {
- var spec = createYouTubeVideoSpec(videoSpec);
- var editor = makeEditor(true);
+ it('shows toast and calls removeBlocks for non-YouTube URL in editor', () => {
+ const spec = createYouTubeVideoSpec(videoSpec);
+ const editor = makeEditor(true);
spec.implementation.render(makeBlock('https://vimeo.com/123'), editor);
vi.advanceTimersByTime(0);
expect(origRender).not.toHaveBeenCalled();
- var toast = document.querySelector('.toast');
+ const toast = document.querySelector('.toast');
expect(toast).toBeTruthy();
expect(toast.textContent).toContain('Only YouTube');
expect(editor.removeBlocks).toHaveBeenCalledWith(['b1']);
});
- it('returns display:none div for non-YouTube URL in viewer', function () {
- var spec = createYouTubeVideoSpec(videoSpec);
- var editor = makeEditor(false);
- var result = spec.implementation.render(makeBlock('https://vimeo.com/123'), editor);
+ it('returns display:none div for non-YouTube URL in viewer', () => {
+ const spec = createYouTubeVideoSpec(videoSpec);
+ const editor = makeEditor(false);
+ const result = spec.implementation.render(makeBlock('https://vimeo.com/123'), editor);
expect(result.dom.style.display).toBe('none');
expect(origRender).not.toHaveBeenCalled();
expect(document.querySelector('.toast')).toBeNull();
});
- it('calls origRender when url is empty', function () {
- var spec = createYouTubeVideoSpec(videoSpec);
- var editor = makeEditor(true);
- var result = spec.implementation.render(makeBlock(''), editor);
+ it('calls origRender when url is empty', () => {
+ const spec = createYouTubeVideoSpec(videoSpec);
+ const editor = makeEditor(true);
+ const result = spec.implementation.render(makeBlock(''), editor);
expect(origRender).toHaveBeenCalledTimes(1);
});
- it('calls origRender when url is undefined', function () {
- var spec = createYouTubeVideoSpec(videoSpec);
- var editor = makeEditor(true);
- var block = { id: 'b1', props: {} };
- var result = spec.implementation.render(block, editor);
+ it('calls origRender when url is undefined', () => {
+ const spec = createYouTubeVideoSpec(videoSpec);
+ const editor = makeEditor(true);
+ const block = { id: 'b1', props: {} };
+ const result = spec.implementation.render(block, editor);
expect(origRender).toHaveBeenCalledTimes(1);
});
- it('calls origRender when url is null', function () {
- var spec = createYouTubeVideoSpec(videoSpec);
- var editor = makeEditor(true);
- var result = spec.implementation.render(makeBlock(null), editor);
+ it('calls origRender when url is null', () => {
+ const spec = createYouTubeVideoSpec(videoSpec);
+ const editor = makeEditor(true);
+ const result = spec.implementation.render(makeBlock(null), editor);
expect(origRender).toHaveBeenCalledTimes(1);
});
});
- describe('toExternalHTML', function () {
- it('returns iframe for YouTube URL', function () {
- var spec = createYouTubeVideoSpec(videoSpec);
- var result = spec.implementation.toExternalHTML(makeBlock('https://www.youtube.com/watch?v=eme8BnMFthI'));
+ describe('toExternalHTML', () => {
+ it('returns iframe for YouTube URL', () => {
+ const spec = createYouTubeVideoSpec(videoSpec);
+ const result = spec.implementation.toExternalHTML(makeBlock('https://www.youtube.com/watch?v=eme8BnMFthI'));
expect(result.dom.tagName).toBe('IFRAME');
expect(result.dom.src).toContain('youtube.com/embed/');
expect(origToExternalHTML).not.toHaveBeenCalled();
});
- it('calls origToExternalHTML for non-YouTube URL', function () {
- var spec = createYouTubeVideoSpec(videoSpec);
- var result = spec.implementation.toExternalHTML(makeBlock('https://vimeo.com/123'));
+ it('calls origToExternalHTML for non-YouTube URL', () => {
+ const spec = createYouTubeVideoSpec(videoSpec);
+ const result = spec.implementation.toExternalHTML(makeBlock('https://vimeo.com/123'));
expect(origToExternalHTML).toHaveBeenCalledTimes(1);
});
- it('calls origToExternalHTML when url is empty', function () {
- var spec = createYouTubeVideoSpec(videoSpec);
- var result = spec.implementation.toExternalHTML(makeBlock(''));
+ it('calls origToExternalHTML when url is empty', () => {
+ const spec = createYouTubeVideoSpec(videoSpec);
+ const result = spec.implementation.toExternalHTML(makeBlock(''));
expect(origToExternalHTML).toHaveBeenCalledTimes(1);
});
diff --git a/frontend/core/components/ArticleForm.jsx b/frontend/core/components/ArticleForm.jsx
index 604c2e1..66c2e79 100644
--- a/frontend/core/components/ArticleForm.jsx
+++ b/frontend/core/components/ArticleForm.jsx
@@ -110,12 +110,12 @@ function BlockNoteEditor({ initialContent, onReady }) {
}),
});
- useEffect(function () {
+ useEffect(() => {
if (editor) {
applyVideoDictOverrides(editor);
document.querySelectorAll(
'[data-content-type="video"] .bn-add-file-button-text',
- ).forEach(function(el) {
+ ).forEach((el) => {
if (el.textContent === 'Add video') {
el.textContent = 'Add YouTube video URL';
}
@@ -126,7 +126,7 @@ function BlockNoteEditor({ initialContent, onReady }) {
const handleSelectionChange = useCallback(() => {
let blockType;
try {
- var pos = editor.getTextCursorPosition();
+ const pos = editor.getTextCursorPosition();
blockType = pos.block?.type;
if (blockType === 'image') {
editor.portalElement?.classList.add('image-selected');
@@ -137,24 +137,24 @@ function BlockNoteEditor({ initialContent, onReady }) {
editor.portalElement?.classList.remove('image-selected');
}
requestAnimationFrame(() => {
- document.querySelectorAll('.ProseMirror-selectednode').forEach(function(el) {
+ document.querySelectorAll('.ProseMirror-selectednode').forEach((el) => {
if (el.classList.contains('bn-visual-media-wrapper') && el.getBoundingClientRect().height < 1) {
- var dir = arrowDirRef.current;
+ const dir = arrowDirRef.current;
arrowDirRef.current = null;
if (dir && editor) {
- var block;
- var container = el.closest('.bn-block') || el.closest('[data-id]');
- var dataId = container?.getAttribute('data-id');
- if (dataId) block = editor.document?.find(function(b) { return b.id === dataId; });
+ let block;
+ const container = el.closest('.bn-block') || el.closest('[data-id]');
+ const dataId = container?.getAttribute('data-id');
+ if (dataId) block = editor.document?.find((b) => b.id === dataId);
if (block) {
- var idx = editor.document?.indexOf(block);
- var target = (idx != null && idx >= 0)
+ const idx = editor.document?.indexOf(block);
+ const target = (idx != null && idx >= 0)
? (dir === 'ArrowDown' ? editor.document?.[idx + 1] : editor.document?.[idx - 1])
: null;
if (target) {
- var targetEl = document.querySelector('[data-id="' + target.id + '"]');
+ const targetEl = document.querySelector('[data-id="' + target.id + '"]');
if (targetEl) {
- var rect = targetEl.getBoundingClientRect();
+ const rect = targetEl.getBoundingClientRect();
window.scrollTo({ top: window.scrollY + rect.top - 100, behavior: 'instant' });
}
editor.setTextCursorPosition(target.id, 'start');
@@ -167,13 +167,13 @@ function BlockNoteEditor({ initialContent, onReady }) {
}
});
if (arrowDirRef.current && editor) {
- var dir = arrowDirRef.current;
+ const dir = arrowDirRef.current;
arrowDirRef.current = null;
- var pos = editor.getTextCursorPosition();
+ const pos = editor.getTextCursorPosition();
if (pos?.block) {
- var targetEl = document.querySelector('[data-id="' + pos.block.id + '"]');
+ const targetEl = document.querySelector('[data-id="' + pos.block.id + '"]');
if (targetEl) {
- var rect = targetEl.getBoundingClientRect();
+ const rect = targetEl.getBoundingClientRect();
window.scrollTo({ top: window.scrollY + rect.top - 100, behavior: 'instant' });
}
}
@@ -208,7 +208,7 @@ function BlockNoteEditor({ initialContent, onReady }) {
useEffect(() => {
if (!editor) return;
- const handler = function (e) {
+ const handler = (e) => {
const html = e.clipboardData.getData('text/html');
if (html && html.includes('blocknote-block')) {
e.preventDefault();
@@ -223,13 +223,13 @@ function BlockNoteEditor({ initialContent, onReady }) {
catch { return; }
editor.updateBlock(block, data);
if (data.type === 'image') {
- document.querySelectorAll('.ProseMirror-selectednode').forEach(function (el) {
+ document.querySelectorAll('.ProseMirror-selectednode').forEach((el) => {
el.classList.remove('ProseMirror-selectednode');
});
editor.domElement?.blur();
}
if (data.type === 'video') {
- document.querySelectorAll('.ProseMirror-selectednode').forEach(function (el) {
+ document.querySelectorAll('.ProseMirror-selectednode').forEach((el) => {
el.classList.remove('ProseMirror-selectednode');
});
try {
@@ -245,8 +245,8 @@ function BlockNoteEditor({ initialContent, onReady }) {
} catch {}
}
}
- requestAnimationFrame(function () {
- document.querySelectorAll('.ProseMirror-selectednode').forEach(function (el) {
+ requestAnimationFrame(() => {
+ document.querySelectorAll('.ProseMirror-selectednode').forEach((el) => {
el.classList.remove('ProseMirror-selectednode');
});
});
@@ -257,7 +257,7 @@ function BlockNoteEditor({ initialContent, onReady }) {
useEffect(() => {
if (!editor) return;
- const handler = function (e) {
+ const handler = (e) => {
let block;
try { block = editor.getSelection()?.blocks?.[0] ?? editor.getTextCursorPosition().block; }
catch { return; }
@@ -280,18 +280,18 @@ function BlockNoteEditor({ initialContent, onReady }) {
'text/plain': new Blob([text], { type: 'text/plain' }),
'text/html': new Blob([html], { type: 'text/html' }),
};
- navigator.clipboard.write([new ClipboardItem(items)]).catch(function () {});
+ navigator.clipboard.write([new ClipboardItem(items)]).catch(() => {});
};
document.addEventListener('copy', handler, true);
return () => document.removeEventListener('copy', handler, true);
}, [editor]);
- useEffect(function () {
+ useEffect(() => {
if (!editor) return;
- const handler = function (e) {
+ const handler = (e) => {
let target = e.target;
if (target.nodeType === 3) target = target.parentNode;
- if (target?.closest?.('.bn-formatting-toolbar, .bn-panel')) return;
+ if (target?.closest?.('#bn-grid-suggestion-menu, .bn-formatting-toolbar, .bn-panel, em-emoji-picker')) return;
if (target?.closest?.('.bn-block-content[data-content-type="image"]')) return;
if (target?.closest?.('.bn-block-content[data-content-type="video"]')) return;
try {
@@ -305,7 +305,7 @@ function BlockNoteEditor({ initialContent, onReady }) {
} catch {}
};
document.addEventListener('mousedown', handler, true);
- return function () { document.removeEventListener('mousedown', handler, true); };
+ return () => { document.removeEventListener('mousedown', handler, true); };
}, [editor]);
return (
diff --git a/frontend/core/components/ArticleViewer.jsx b/frontend/core/components/ArticleViewer.jsx
index 38e5093..357a4bc 100644
--- a/frontend/core/components/ArticleViewer.jsx
+++ b/frontend/core/components/ArticleViewer.jsx
@@ -55,8 +55,8 @@ function BlockNoteViewer({ initialContent }) {
}),
});
- useEffect(function () {
- const handler = function (e) {
+ useEffect(() => {
+ const handler = (e) => {
const block = document.querySelector(
'.bn-block-content[data-content-type="image"].ProseMirror-selectednode',
);
@@ -77,15 +77,15 @@ function BlockNoteViewer({ initialContent }) {
new ClipboardItem({
'text/plain': new Blob([text], { type: 'text/plain' }),
}),
- ]).catch(function () {});
+ ]).catch(() => {});
};
document.addEventListener('copy', handler, true);
- return function () { document.removeEventListener('copy', handler, true); };
+ return () => { document.removeEventListener('copy', handler, true); };
}, []);
- useEffect(function () {
+ useEffect(() => {
if (!editor) return;
- const handler = function (e) {
+ const handler = (e) => {
let target = e.target;
if (target.nodeType === 3) target = target.parentNode;
if (target?.closest?.('.bn-block-content[data-content-type="image"]')) return;
@@ -100,7 +100,7 @@ function BlockNoteViewer({ initialContent }) {
} catch {}
};
document.addEventListener('mousedown', handler, true);
- return function () { document.removeEventListener('mousedown', handler, true); };
+ return () => { document.removeEventListener('mousedown', handler, true); };
}, [editor]);
return ;
diff --git a/frontend/core/components/CopyBlockButton.jsx b/frontend/core/components/CopyBlockButton.jsx
index feef585..4df4b03 100644
--- a/frontend/core/components/CopyBlockButton.jsx
+++ b/frontend/core/components/CopyBlockButton.jsx
@@ -2,11 +2,11 @@ import React from 'react';
import { useBlockNoteEditor, useComponentsContext } from '@blocknote/react';
export default function CopyBlockButton() {
- var editor = useBlockNoteEditor();
- var Components = useComponentsContext();
+ const editor = useBlockNoteEditor();
+ const Components = useComponentsContext();
if (!editor || !Components) return null;
- var block;
+ let block;
try {
block = editor.getSelection()?.blocks?.[0] ?? editor.getTextCursorPosition().block;
} catch { return null; }
@@ -16,10 +16,10 @@ export default function CopyBlockButton() {
return (
';
- var plainText = '';
+ onClick={() => {
+ const data = { type: block.type, props: block.props, content: block.content };
+ const html = '';
+ let plainText = '';
if (block.type === 'image') {
if (block.props?.url && !block.props.url.startsWith('/uploads/')) {
plainText = block.props.url;
@@ -31,18 +31,18 @@ export default function CopyBlockButton() {
} else {
plainText = block.props?.url || '';
}
- var items = {
+ const items = {
'text/plain': new Blob([plainText], { type: 'text/plain' }),
'text/html': new Blob([html], { type: 'text/html' }),
};
- navigator.clipboard.write([new ClipboardItem(items)]).catch(function () {});
- var old = document.querySelector('.toast');
+ navigator.clipboard.write([new ClipboardItem(items)]).catch(() => {});
+ const old = document.querySelector('.toast');
if (old) old.remove();
- var toast = document.createElement('div');
+ const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = 'Copied to clipboard';
document.body.appendChild(toast);
- setTimeout(function () { if (toast.parentElement) toast.remove(); }, 2800);
+ setTimeout(() => { if (toast.parentElement) toast.remove(); }, 2800);
}}
icon={