feat: media picker and image removal
This commit is contained in:
1 parent
008b0dfbb9
commit
b3793d5dbb
54 files changed
+3769
-1412
No files matched your search
@@ -61,6 +61,23 @@ describe('Selecting an inserted image reveals its delete button', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('The remove button is labelled and neutral', () => {
|
||||
it('reads "Remove image" with a trash icon and is not styled as destructive', async () => {
|
||||
const { wrapper, getEditor } = mountHarness()
|
||||
activeWrapper = wrapper
|
||||
const editor = getEditor()
|
||||
editor.commands.setArticleImage({ src: 'photo.png', alt: '', imageId: 10 })
|
||||
editor.commands.setNodeSelection(findArticleImagePos(editor, 10) as number)
|
||||
await flush()
|
||||
|
||||
const button = deleteButton()!
|
||||
|
||||
expect(button.textContent?.trim()).toBe('Remove image')
|
||||
expect(button.className).not.toContain('text-error')
|
||||
expect(button.className).toContain('border-accented')
|
||||
})
|
||||
})
|
||||
|
||||
describe('An image with no data-image-id gets no delete affordance', () => {
|
||||
it('renders no delete button even when the node is selected', async () => {
|
||||
const { wrapper, getEditor } = mountHarness()
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { bodyImageIds } from '../bodyImageIds'
|
||||
|
||||
describe('bodyImageIds', () => {
|
||||
it('lists the ids of the images that carry data-image-id', () => {
|
||||
const body =
|
||||
'<p>a</p><img src="/a.jpg" alt="" data-image-id="1"><p>b</p><img data-image-id="4" src="/b.svg">'
|
||||
|
||||
expect(bodyImageIds(body)).toEqual([1, 4])
|
||||
})
|
||||
|
||||
it('ignores images without an id and non numeric ids', () => {
|
||||
const body =
|
||||
'<img src="/a.jpg"><img src="/b.jpg" data-image-id="abc"><img data-image-id="">'
|
||||
|
||||
expect(bodyImageIds(body)).toEqual([])
|
||||
})
|
||||
|
||||
it('returns nothing for a body without images', () => {
|
||||
expect(bodyImageIds('<p>Hello</p>')).toEqual([])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
const IMAGE_ID_SELECTOR = 'img[data-image-id]'
|
||||
const IMAGE_ID_ATTRIBUTE = 'data-image-id'
|
||||
|
||||
export function bodyImageCount(html: string): number {
|
||||
return new DOMParser().parseFromString(html, 'text/html').querySelectorAll('img').length
|
||||
}
|
||||
|
||||
export function bodyImageIds(html: string): number[] {
|
||||
const document = new DOMParser().parseFromString(html, 'text/html')
|
||||
return Array.from(document.querySelectorAll(IMAGE_ID_SELECTOR))
|
||||
.map((image) => Number.parseInt(image.getAttribute(IMAGE_ID_ATTRIBUTE) ?? '', 10))
|
||||
.filter((id) => !Number.isNaN(id))
|
||||
}
|
||||
@@ -76,3 +76,11 @@ export function joinNames(names: string[]) {
|
||||
}
|
||||
return `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`
|
||||
}
|
||||
|
||||
export function libraryMatchText(
|
||||
filename: string,
|
||||
existing: { created_at: string; uploaded_by: string },
|
||||
) {
|
||||
const by = existing.uploaded_by ? ` by ${existing.uploaded_by}` : ''
|
||||
return `${filename} matches an image uploaded on ${formatMediaDate(existing.created_at)}${by}.`
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export const UNDERLINE_TAB_TRIGGER =
|
||||
'h-9 px-0 py-0 data-[state=inactive]:text-toned in-[[data-slot=list]:not(:has([data-slot=indicator]))]:data-[state=active]:after:bottom-0 in-[[data-slot=list]:not(:has([data-slot=indicator]))]:data-[state=active]:after:h-0.5 max-md:h-11 max-md:text-[0.9375rem]'
|
||||
Reference in new issue
Block a user