Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UBER-520: Fix images drag & drop #3453

Merged
merged 1 commit into from
Jun 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
154 changes: 93 additions & 61 deletions packages/text-editor/src/components/imageExt.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { getEmbeddedLabel } from '@hcengineering/platform'
import { getFileUrl } from '@hcengineering/presentation'
import { getEmbeddedLabel, getMetadata } from '@hcengineering/platform'
import presentation, { getFileUrl } from '@hcengineering/presentation'
import { Action, IconSize, Menu, getEventPositionElement, getIconSize2x, showPopup } from '@hcengineering/ui'
import { Node, createNodeFromContent, mergeAttributes, nodeInputRule } from '@tiptap/core'
import { Plugin, PluginKey } from 'prosemirror-state'
import plugin from '../plugin'

import { Node as ProseMirrorNode } from '@tiptap/pm/model'
import { Fragment, Node as ProseMirrorNode } from '@tiptap/pm/model'
import { EditorView } from 'prosemirror-view'

/**
* @public
Expand Down Expand Up @@ -40,6 +41,14 @@ declare module '@tiptap/core' {
*/
export const inputRegex = /(?:^|\s)(!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\))$/

function getType (type: string): 'image' | 'other' {
if (type.startsWith('image/')) {
return 'image'
}

return 'other'
}

/**
* @public
*/
Expand Down Expand Up @@ -160,74 +169,97 @@ export const ImageRef = Node.create<ImageOptions>({
},
addProseMirrorPlugins () {
const opt = this.options
return [
new Plugin({
key: new PluginKey('handle-image-paste'),
props: {
handleDrop (view, event, slice) {
const uris = (event.dataTransfer?.getData('text/uri-list') ?? '')
.split('\r\n')
.filter((it) => !it.startsWith('#'))
let result = false
const pos = view.posAtCoords({ left: event.x, top: event.y })
for (const uri of uris) {
if (uri !== '') {
const url = new URL(uri)
if (url.hostname !== location.hostname) {
return
}
function handleDrop (
view: EditorView,
pos: { pos: number, inside: number } | null,
dataTransfer: DataTransfer
): any {
const uris = (dataTransfer.getData('text/uri-list') ?? '').split('\r\n').filter((it) => !it.startsWith('#'))
let result = false
for (const uri of uris) {
if (uri !== '') {
const url = new URL(uri)
const uploadUrl = getMetadata(presentation.metadata.UploadURL)
if (uploadUrl === undefined || !url.href.includes(uploadUrl)) {
continue
}

const _file = (url.searchParams.get('file') ?? '').split('/').join('')

const _file = (url.searchParams.get('file') ?? '').split('/').join('')
if (_file.trim().length === 0) {
continue
}

if (_file.trim().length === 0) {
return
const ctype = dataTransfer.getData('application/contentType')
const type = getType(ctype ?? 'other')

let content: ProseMirrorNode | Fragment | undefined
if (type === 'image') {
content = createNodeFromContent(
`<img data-type='image' width='75%' file-id='${_file}'></img>`,
view.state.schema,
{
parseOptions: {
preserveWhitespace: 'full'
}
const content = createNodeFromContent(
`<img data-type='image' width='75%' file-id='${_file}'></img>`,
view.state.schema,
{
parseOptions: {
preserveWhitespace: 'full'
}
}
)
event.preventDefault()
event.stopPropagation()
view.dispatch(view.state.tr.insert(pos?.pos ?? 0, content))
result = true
}
}
if (result) {
return result
}
)
}
if (content !== undefined) {
view.dispatch(view.state.tr.insert(pos?.pos ?? 0, content))
result = true
}
}
}
if (result) {
return result
}

const files = event.dataTransfer?.files
if (files !== undefined && opt.attachFile !== undefined) {
event.preventDefault()
event.stopPropagation()
for (let i = 0; i < files.length; i++) {
const file = files.item(i)
if (file != null) {
void opt.attachFile(file).then((id) => {
if (id !== undefined) {
if (id.type.includes('image')) {
const content = createNodeFromContent(
`<img data-type='image' width='75%' file-id='${id.file}'></img>`,
view.state.schema,
{
parseOptions: {
preserveWhitespace: 'full'
}
}
)
view.dispatch(view.state.tr.insert(pos?.pos ?? 0, content))
const files = dataTransfer?.files
if (files !== undefined && opt.attachFile !== undefined) {
for (let i = 0; i < files.length; i++) {
const file = files.item(i)
if (file != null) {
void opt.attachFile(file).then((id) => {
if (id !== undefined) {
if (id.type.includes('image')) {
const content = createNodeFromContent(
`<img data-type='image' width='75%' file-id='${id.file}'></img>`,
view.state.schema,
{
parseOptions: {
preserveWhitespace: 'full'
}
}
})
)
view.dispatch(view.state.tr.insert(pos?.pos ?? 0, content))
}
}
})
}
}
}
return true
}
return [
new Plugin({
key: new PluginKey('handle-image-paste'),
props: {
handlePaste (view, event, slice) {
event.preventDefault()
event.stopPropagation()
const dataTransfer = event.clipboardData
if (dataTransfer !== null) {
return handleDrop(view, { pos: view.state.selection.$from.pos, inside: 0 }, dataTransfer)
}
},
handleDrop (view, event, slice) {
event.preventDefault()
event.stopPropagation()
const dataTransfer = event.dataTransfer
if (dataTransfer !== null) {
return handleDrop(view, view.posAtCoords({ left: event.x, top: event.y }), dataTransfer)
}
return result
},
handleClick: (view, pos, event) => {
if (event.button !== 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@
${getFileUrl(value.file, getIconSize2x('large'))} 2x
);`
: ''
function dragStart (event: DragEvent): void {
event.dataTransfer?.setData('application/contentType', value.type)
}
</script>

<div class="flex-row-center attachment-container">
Expand All @@ -92,6 +95,7 @@
download={value.name}
on:click={clickHandler}
on:mousedown={middleClickHandler}
on:dragstart={dragStart}
>
{#if showPreview}
<div
Expand Down
25 changes: 25 additions & 0 deletions plugins/chunter-resources/src/components/CommentPresenter.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import { IdMap, Ref } from '@hcengineering/core'
import { MessageViewer } from '@hcengineering/presentation'
import { Icon, ShowMore, TimeSince } from '@hcengineering/ui'
import { LinkPresenter } from '@hcengineering/view-resources'

export let value: Comment
export let inline: boolean = false
Expand All @@ -42,6 +43,27 @@
return emp
}
}

$: links = getLinks(value.message)

function getLinks (content: string): HTMLLinkElement[] {
const parser = new DOMParser()
const parent = parser.parseFromString(content, 'text/html').firstChild?.childNodes[1] as HTMLElement
return parseLinks(parent.childNodes)
}

function parseLinks (nodes: NodeListOf<ChildNode>): HTMLLinkElement[] {
const res: HTMLLinkElement[] = []
nodes.forEach((p) => {
if (p.nodeType !== Node.TEXT_NODE) {
if (p.nodeName === 'A') {
res.push(p as HTMLLinkElement)
}
res.push(...parseLinks(p.childNodes))
}
})
return res
}
</script>

{#if inline}
Expand All @@ -68,6 +90,9 @@
<ShowMore fixed>
<MessageViewer message={value.message} />
<AttachmentDocList {value} />
{#each links as link}
<LinkPresenter {link} />
{/each}
</ShowMore>
</div>
{/await}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import { updateBacklinks } from '../../backlinks'
import chunter from '../../plugin'
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
import { LinkPresenter } from '@hcengineering/view-resources'

export let tx: TxCreateDoc<Comment>
export let value: Comment
Expand Down Expand Up @@ -57,6 +58,27 @@
}
let refInput: AttachmentRefInput
let loading = false

$: links = getLinks(value.message)

function getLinks (content: string): HTMLLinkElement[] {
const parser = new DOMParser()
const parent = parser.parseFromString(content, 'text/html').firstChild?.childNodes[1] as HTMLElement
return parseLinks(parent.childNodes)
}

function parseLinks (nodes: NodeListOf<ChildNode>): HTMLLinkElement[] {
const res: HTMLLinkElement[] = []
nodes.forEach((p) => {
if (p.nodeType !== Node.TEXT_NODE) {
if (p.nodeName === 'A') {
res.push(p as HTMLLinkElement)
}
res.push(...parseLinks(p.childNodes))
}
})
return res
}
</script>

<div class:editing class="content-color">
Expand All @@ -83,6 +105,9 @@
{:else}
<MessageViewer message={value.message} />
<AttachmentDocList {value} />
{#each links as link}
<LinkPresenter {link} />
{/each}
{/if}
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@
}

async function getData (href: string): Promise<Data> {
const params = href.replace(/(http.:\/\/)?(www.)?github.com\//, '')
let params = href.replace(/(http.:\/\/)?(www.)?github.com\//, '')
params = params.replace('/pull/', '/pulls/')
const res = await (await fetch(`https://api.github.com/repos/${params}`)).json()
return {
number: res.number,
body: format(res.body),
title: res.title,
assignees: res.assignees.map((p: any) => {
assignees: res.assignees?.map((p: any) => {
return {
login: p.login,
url: p.html_url
Expand Down Expand Up @@ -73,7 +74,7 @@
</div>
{/if}
<div class="flex-between">
{#if data.assignees.length}
{#if data.assignees?.length}
<div class="flex-col">
<div class="fs-title"><LabelComponent label={view.string.Assignees} /></div>
<div>
Expand All @@ -83,7 +84,7 @@
</div>
</div>
{/if}
{#if data.labels.length}
{#if data.labels?.length}
<div class="flex-col">
<div class="fs-title"><LabelComponent label={view.string.Labels} /></div>
<div>
Expand Down