[fix] iOS safari clipboard / text position (#686)
* use idb for clipboard, too * Add warnings for firefox * remove logs * Update getTextSvgElement.ts
This commit is contained in:
parent
c3050db968
commit
60e936dfed
3 changed files with 53 additions and 31 deletions
17
packages/tldraw/src/state/IdbClipboard.ts
Normal file
17
packages/tldraw/src/state/IdbClipboard.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { get, set, del } from 'idb-keyval'
|
||||||
|
|
||||||
|
// Used for clipboard
|
||||||
|
|
||||||
|
const ID = 'tldraw_clipboard'
|
||||||
|
|
||||||
|
export async function getClipboard(): Promise<string | undefined> {
|
||||||
|
return get(ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setClipboard(item: string): Promise<void> {
|
||||||
|
return set(ID, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearClipboard(): Promise<void> {
|
||||||
|
return del(ID)
|
||||||
|
}
|
|
@ -80,6 +80,7 @@ import { ArrowTool } from './tools/ArrowTool'
|
||||||
import { StickyTool } from './tools/StickyTool'
|
import { StickyTool } from './tools/StickyTool'
|
||||||
import { StateManager } from './StateManager'
|
import { StateManager } from './StateManager'
|
||||||
import { clearPrevSize } from './shapes/shared/getTextSize'
|
import { clearPrevSize } from './shapes/shared/getTextSize'
|
||||||
|
import { getClipboard, setClipboard } from './IdbClipboard'
|
||||||
|
|
||||||
const uuid = Utils.uniqueId()
|
const uuid = Utils.uniqueId()
|
||||||
|
|
||||||
|
@ -1778,11 +1779,13 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
|
|
||||||
const tldrawString = `<tldraw>${jsonString}</tldraw>`
|
const tldrawString = `<tldraw>${jsonString}</tldraw>`
|
||||||
|
|
||||||
|
setClipboard(tldrawString)
|
||||||
|
|
||||||
if (e) {
|
if (e) {
|
||||||
e.clipboardData?.setData('text/html', tldrawString)
|
e.clipboardData?.setData('text/html', tldrawString)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (navigator.clipboard) {
|
if (navigator.clipboard && window.ClipboardItem) {
|
||||||
navigator.clipboard.write([
|
navigator.clipboard.write([
|
||||||
new ClipboardItem({
|
new ClipboardItem({
|
||||||
'text/html': new Blob([tldrawString], { type: 'text/html' }),
|
'text/html': new Blob([tldrawString], { type: 'text/html' }),
|
||||||
|
@ -1993,6 +1996,12 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getClipboard().then((clipboard) => {
|
||||||
|
if (clipboard) {
|
||||||
|
pasteAsHTML(clipboard)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if (navigator.clipboard) {
|
if (navigator.clipboard) {
|
||||||
const items = await navigator.clipboard.read()
|
const items = await navigator.clipboard.read()
|
||||||
|
|
||||||
|
@ -2000,16 +2009,27 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
// First, look for tldraw json in html.
|
// look for png data.
|
||||||
|
|
||||||
const htmlData = await item.getType('text/html')
|
const pngData = await item.getType('text/png')
|
||||||
|
|
||||||
if (htmlData) {
|
if (pngData) {
|
||||||
let html = await htmlData.text()
|
const file = new File([pngData], 'image.png')
|
||||||
pasteAsHTML(html)
|
this.addMediaFromFile(file)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, look for plain text data.
|
// look for svg data.
|
||||||
|
|
||||||
|
const svgData = await item.getType('image/svg+xml')
|
||||||
|
|
||||||
|
if (svgData) {
|
||||||
|
const file = new File([svgData], 'image.svg')
|
||||||
|
this.addMediaFromFile(file)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// look for plain text data.
|
||||||
|
|
||||||
const textData = await item.getType('text/plain')
|
const textData = await item.getType('text/plain')
|
||||||
|
|
||||||
|
@ -2026,26 +2046,6 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, look for png data.
|
|
||||||
|
|
||||||
const pngData = await item.getType('text/png')
|
|
||||||
|
|
||||||
if (pngData) {
|
|
||||||
const file = new File([pngData], 'image.png')
|
|
||||||
this.addMediaFromFile(file)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, look for svg data.
|
|
||||||
|
|
||||||
const svgData = await item.getType('image/svg+xml')
|
|
||||||
|
|
||||||
if (svgData) {
|
|
||||||
const file = new File([svgData], 'image.svg')
|
|
||||||
this.addMediaFromFile(file)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// noop
|
// noop
|
||||||
|
@ -2240,7 +2240,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
...this.clipboard,
|
...this.clipboard,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (navigator.clipboard) {
|
if (navigator.clipboard && window.ClipboardItem) {
|
||||||
navigator.clipboard.write([
|
navigator.clipboard.write([
|
||||||
new ClipboardItem({
|
new ClipboardItem({
|
||||||
'text/html': new Blob([tldrawString], { type: 'text/html' }),
|
'text/html': new Blob([tldrawString], { type: 'text/html' }),
|
||||||
|
@ -2356,7 +2356,10 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!navigator.clipboard) return
|
if (!(navigator.clipboard && window.ClipboardItem)) {
|
||||||
|
console.warn('Sorry, your browser does not support copying images.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const blob = await this.getImage(format, opts)
|
const blob = await this.getImage(format, opts)
|
||||||
|
|
||||||
|
|
|
@ -12,10 +12,12 @@ export function getTextSvgElement(text: string, style: ShapeStyles, bounds: TLBo
|
||||||
const textElm = document.createElementNS('http://www.w3.org/2000/svg', 'text')
|
const textElm = document.createElementNS('http://www.w3.org/2000/svg', 'text')
|
||||||
textElm.textContent = line
|
textElm.textContent = line
|
||||||
textElm.setAttribute('y', LINE_HEIGHT * fontSize * (0.5 + i) + '')
|
textElm.setAttribute('y', LINE_HEIGHT * fontSize * (0.5 + i) + '')
|
||||||
textElm.setAttribute('letter-spacing', '-0.03px')
|
textElm.setAttribute('letter-spacing', fontSize * -0.03 + '')
|
||||||
textElm.setAttribute('font-size', fontSize + 'px')
|
textElm.setAttribute('font-size', fontSize + 'px')
|
||||||
textElm.setAttribute('font-family', getFontFace(style.font).slice(1, -1))
|
textElm.setAttribute('font-family', getFontFace(style.font).slice(1, -1))
|
||||||
textElm.setAttribute('text-align', getTextAlign(style.textAlign))
|
textElm.setAttribute('text-align', getTextAlign(style.textAlign))
|
||||||
|
textElm.setAttribute('text-align', getTextAlign(style.textAlign))
|
||||||
|
textElm.setAttribute('alignment-baseline', 'central')
|
||||||
g.appendChild(textElm)
|
g.appendChild(textElm)
|
||||||
|
|
||||||
return textElm
|
return textElm
|
||||||
|
@ -37,8 +39,8 @@ export function getTextSvgElement(text: string, style: ShapeStyles, bounds: TLBo
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case AlignStyle.Start: {
|
case AlignStyle.Start: {
|
||||||
|
g.setAttribute('text-align', 'left')
|
||||||
g.setAttribute('text-anchor', 'start')
|
g.setAttribute('text-anchor', 'start')
|
||||||
g.setAttribute('alignment-baseline', 'central')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue