Compare commits
10 Commits
c23fdbf310
...
edf35b7dd0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edf35b7dd0 | ||
|
|
de886dcfcc | ||
|
|
67f019713a | ||
|
|
2af396a2b8 | ||
|
|
20f12c97c1 | ||
|
|
15c4bcd48e | ||
|
|
e429bbbcdb | ||
|
|
020fd63c97 | ||
|
|
2771503b6f | ||
|
|
65458cf491 |
@@ -8,6 +8,7 @@ import ReactDOM from 'react-dom/client'
|
||||
|
||||
import './ui/custom-elements/chat-image.ts'
|
||||
import './ui/custom-elements/chat-video.ts'
|
||||
import './ui/custom-elements/chat-file.ts'
|
||||
|
||||
const urlParams = new URL(location.href).searchParams
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ const markedInstance = new marked.Marked({
|
||||
if (/uploaded_files\/[A-Za-z0-9]+$/.test(href)) {
|
||||
return ({
|
||||
Image: `<chat-image src="${href}" alt="${text}"></chat-image>`,
|
||||
Video: `<chat-video src="${href}" alt="${text}"></chat-video>`,
|
||||
File: `<chat-file src="${href}" alt="${text}"></chat-file>`,
|
||||
Video: `<chat-video src="${href}"></chat-video>`,
|
||||
File: `<chat-file href="${href}" name="${/^Video|File=(.*)/.exec(text)?.[1] || 'Unnamed file'}"></chat-file>`,
|
||||
})?.[type] || ``
|
||||
}
|
||||
return ``
|
||||
@@ -144,7 +144,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
||||
target,
|
||||
data: cachedFiles.current[fileName],
|
||||
}, 5000)
|
||||
if (checkApiSuccessOrSncakbar(re, `文件[${fileName}] 上傳失敗`)) return
|
||||
if (checkApiSuccessOrSncakbar(re, `文件[${fileName}] 上傳失敗`)) return setIsMessageSending(false)
|
||||
text = text.replaceAll('(' + fileName + ')', '(' + re.data!.file_path as string + ')')
|
||||
}
|
||||
}
|
||||
@@ -154,7 +154,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
||||
target,
|
||||
text,
|
||||
}, 5000)
|
||||
if (checkApiSuccessOrSncakbar(re, "發送失敗")) return
|
||||
if (checkApiSuccessOrSncakbar(re, "發送失敗")) return setIsMessageSending(false)
|
||||
inputRef.current!.value = ''
|
||||
cachedFiles.current = {}
|
||||
} catch (e) {
|
||||
@@ -280,6 +280,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
||||
'src',
|
||||
'alt',
|
||||
'href',
|
||||
'name',
|
||||
],
|
||||
}).replaceAll('\n', '<br>')
|
||||
const lastDate = date
|
||||
@@ -299,11 +300,14 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
||||
{
|
||||
(date.getMinutes() != lastDate.getMinutes() || date.getDate() != lastDate.getDate() || date.getMonth() != lastDate.getMonth() || date.getFullYear() != lastDate.getFullYear())
|
||||
&& <mdui-tooltip content={`${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日 ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`}>
|
||||
<div>
|
||||
<div style={{
|
||||
fontSize: '87%',
|
||||
marginTop: '10px',
|
||||
}}>
|
||||
{
|
||||
(date.getFullYear() != lastDate.getFullYear() ? `${date.getFullYear()}年` : '')
|
||||
+ (date.getMonth() != lastDate.getMonth() ? `${date.getMonth() + 1}月` : '')
|
||||
+ (date.getDate() != lastDate.getDate() ? `${date.getDate()}日` : '')
|
||||
+ `${date.getMonth() + 1}月`
|
||||
+ `${date.getDate()}日`
|
||||
+ ` ${date.getHours()}:${date.getMinutes()}`
|
||||
}
|
||||
</div>
|
||||
@@ -349,7 +353,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
|
||||
// 即便是 no-cors 還是殘廢, 因此暫時沒有什麽想法
|
||||
const re = await fetch(url)
|
||||
const type = re.headers.get("Content-Type")
|
||||
if (type?.startsWith("image/"))
|
||||
if (type && re.ok)
|
||||
addFile(type as string, getFileNameOrRandom(url), re)
|
||||
} catch (e) {
|
||||
snackbar({
|
||||
|
||||
@@ -33,6 +33,9 @@ export default function Message({ userId, rawData, renderHTML, message, ...props
|
||||
|
||||
const dropDownRef = React.useRef<Dropdown>(null)
|
||||
const messageJsonDialogRef = React.useRef<Dialog>(null)
|
||||
useEventListener(messageJsonDialogRef, 'click', (e) => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
|
||||
const [isDropDownOpen, setDropDownOpen] = React.useState(false)
|
||||
|
||||
@@ -116,7 +119,10 @@ export default function Message({ userId, rawData, renderHTML, message, ...props
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: renderHTML
|
||||
}} />
|
||||
<mdui-menu onClick={(e) => e.stopPropagation()}>
|
||||
<mdui-menu onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setDropDownOpen(false)
|
||||
}}>
|
||||
<mdui-menu-item icon="content_copy" onClick={() => copyToClipboard($(dropDownRef.current as HTMLElement).find('#msg').text())}>複製文字</mdui-menu-item>
|
||||
<mdui-menu-item icon="content_copy" onClick={() => copyToClipboard(rawData)}>複製原文</mdui-menu-item>
|
||||
<mdui-menu-item icon="info" onClick={() => messageJsonDialogRef.current.open = true}>查看詳情</mdui-menu-item>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export default function copyToClipboard(text: string) {
|
||||
if (navigator.clipboard)
|
||||
return navigator.clipboard.writeText(text)
|
||||
return new Promise((res rej) => {
|
||||
return new Promise((res, rej) => {
|
||||
if (document.hasFocus()) {
|
||||
const a = document.createElement("textarea")
|
||||
document.body.appendChild(a)
|
||||
@@ -12,7 +12,7 @@ export default function copyToClipboard(text: string) {
|
||||
a.select()
|
||||
document.execCommand("copy", true)
|
||||
document.body.removeChild(a)
|
||||
res()
|
||||
res(null)
|
||||
} else {
|
||||
rej()
|
||||
}
|
||||
|
||||
30
client/ui/custom-elements/chat-file.ts
Normal file
30
client/ui/custom-elements/chat-file.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { $ } from 'mdui/jq'
|
||||
|
||||
customElements.define('chat-file', class extends HTMLElement {
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
connectedCallback() {
|
||||
this.style.display = 'block'
|
||||
const e = new DOMParser().parseFromString(`
|
||||
<a style="width: 100%;height: 100%;">
|
||||
<mdui-card variant="outlined" clickable style="display: flex;align-items: center;">
|
||||
<mdui-icon name="insert_drive_file" style="margin: 13px;font-size: 34px;"></mdui-icon>
|
||||
<span style="margin-right: 13px;"></span>
|
||||
</mdui-card>
|
||||
</a>`, 'text/html').body.firstChild as HTMLElement
|
||||
$(e).find('span').text($(this).attr("name"))
|
||||
const href = $(this).attr('href')
|
||||
$(e).attr('href', href)
|
||||
$(e).attr('target', '_blank')
|
||||
$(e).attr('download', href)
|
||||
e.style.textDecoration = 'none'
|
||||
e.style.color = 'inherit'
|
||||
// deno-lint-ignore no-window
|
||||
e.onclick = (e) => {
|
||||
e.stopPropagation()
|
||||
window.open(href, '_blank')
|
||||
}
|
||||
this.appendChild(e)
|
||||
}
|
||||
})
|
||||
@@ -6,12 +6,12 @@ customElements.define('chat-video', class extends HTMLElement {
|
||||
}
|
||||
connectedCallback() {
|
||||
this.style.display = 'block'
|
||||
const e = new DOMParser().parseFromString(`<video controls>視頻無法播放</video>`, 'text/html').body.firstChild as Node
|
||||
const e = new DOMParser().parseFromString(`<video controls>視頻無法播放</video>`, 'text/html').body.firstChild as HTMLVideoElement
|
||||
e.style.width = "100%"
|
||||
e.style.height = "100%"
|
||||
e.style.borderRadius = "var(--mdui-shape-corner-medium)"
|
||||
e.alt = $(this).attr('alt') || ""
|
||||
e.src = $(this).attr('src') as string
|
||||
e.onclick = (e) => e.stopPropagation()
|
||||
this.appendChild(e)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -45,6 +45,8 @@ app.get('/uploaded_files/:hash', (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
const fileName = encodeURIComponent(file!.getName()?.replaceAll('"', ''))
|
||||
res.setHeader('Content-Disposition', `inline; filename="${fileName}"`)
|
||||
res.setHeader('Content-Type', file!.getMime())
|
||||
res.sendFile(path.resolve(file!.getFilePath()))
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user