fix: proxy unstable native image media
This commit is contained in:
Vendored
+205
@@ -0,0 +1,205 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require __DIR__ . '/im_web_bootstrap.php';
|
||||||
|
|
||||||
|
const WEB_MEDIA_PROXY_MAX_BYTES = 15 * 1024 * 1024;
|
||||||
|
const WEB_MEDIA_PROXY_MAX_SECONDS = 25;
|
||||||
|
|
||||||
|
function failMediaProxy(int $status, string $message): never
|
||||||
|
{
|
||||||
|
http_response_code($status);
|
||||||
|
header('Content-Type: text/plain; charset=utf-8');
|
||||||
|
header('Cache-Control: no-store');
|
||||||
|
header('X-Content-Type-Options: nosniff');
|
||||||
|
echo $message;
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mediaProxyAllowedHosts(): array
|
||||||
|
{
|
||||||
|
$configured = webImEnv('JC_WEB_IM_MEDIA_PROXY_HOSTS');
|
||||||
|
$hosts = array_map(
|
||||||
|
static fn(string $host): string => strtolower(rtrim(trim($host), '.')),
|
||||||
|
preg_split('/[,\s]+/', $configured) ?: []
|
||||||
|
);
|
||||||
|
|
||||||
|
return array_values(array_unique(array_filter($hosts)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function validatedMediaProxyUrl(string $url): array
|
||||||
|
{
|
||||||
|
if ($url === '' || strlen($url) > 2048 || filter_var($url, FILTER_VALIDATE_URL) === false) {
|
||||||
|
failMediaProxy(400, '媒体地址不合法');
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = parse_url($url);
|
||||||
|
$scheme = strtolower((string)($parts['scheme'] ?? ''));
|
||||||
|
$host = strtolower(rtrim((string)($parts['host'] ?? ''), '.'));
|
||||||
|
$port = isset($parts['port']) ? (int)$parts['port'] : 443;
|
||||||
|
if (
|
||||||
|
$scheme !== 'https'
|
||||||
|
|| $host === ''
|
||||||
|
|| $port !== 443
|
||||||
|
|| isset($parts['user'])
|
||||||
|
|| isset($parts['pass'])
|
||||||
|
|| !in_array($host, mediaProxyAllowedHosts(), true)
|
||||||
|
) {
|
||||||
|
failMediaProxy(403, '媒体来源不允许代理');
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$url, $host];
|
||||||
|
}
|
||||||
|
|
||||||
|
function mediaProxyCacheRoot(): string
|
||||||
|
{
|
||||||
|
$root = rtrim(webImEnv('JC_WEB_IM_MEDIA_CACHE_ROOT', '/var/cache/jc-web-im-media'), '/');
|
||||||
|
if ($root === '' || !is_dir($root) || !is_writable($root)) {
|
||||||
|
failMediaProxy(503, '媒体缓存目录不可用');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $root;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mediaProxyMime(string $path): string
|
||||||
|
{
|
||||||
|
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||||
|
$mime = $finfo !== false ? (string)(finfo_file($finfo, $path) ?: '') : '';
|
||||||
|
if ($finfo !== false) {
|
||||||
|
finfo_close($finfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $mime;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchMediaProxyImage(string $url, string $host, string $cacheFile): void
|
||||||
|
{
|
||||||
|
$deadline = microtime(true) + WEB_MEDIA_PROXY_MAX_SECONDS;
|
||||||
|
$lastError = '媒体源无响应';
|
||||||
|
|
||||||
|
for ($attempt = 1; $attempt <= 6 && microtime(true) < $deadline; $attempt++) {
|
||||||
|
$temporaryFile = $cacheFile . '.tmp.' . getmypid() . '.' . $attempt;
|
||||||
|
$stream = @fopen($temporaryFile, 'x+b');
|
||||||
|
if ($stream === false) {
|
||||||
|
$lastError = '无法创建媒体缓存文件';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bytes = 0;
|
||||||
|
$tooLarge = false;
|
||||||
|
$curl = curl_init($url);
|
||||||
|
if ($curl === false) {
|
||||||
|
fclose($stream);
|
||||||
|
@unlink($temporaryFile);
|
||||||
|
$lastError = '无法初始化媒体请求';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$remainingMs = max(1000, (int)floor(($deadline - microtime(true)) * 1000));
|
||||||
|
curl_setopt_array($curl, [
|
||||||
|
CURLOPT_RETURNTRANSFER => false,
|
||||||
|
CURLOPT_FOLLOWLOCATION => false,
|
||||||
|
CURLOPT_CONNECTTIMEOUT_MS => min(2500, $remainingMs),
|
||||||
|
CURLOPT_TIMEOUT_MS => min(12000, $remainingMs),
|
||||||
|
CURLOPT_NOSIGNAL => true,
|
||||||
|
CURLOPT_DNS_CACHE_TIMEOUT => 0,
|
||||||
|
CURLOPT_FRESH_CONNECT => true,
|
||||||
|
CURLOPT_FORBID_REUSE => true,
|
||||||
|
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
|
||||||
|
CURLOPT_SSL_VERIFYPEER => true,
|
||||||
|
CURLOPT_SSL_VERIFYHOST => 2,
|
||||||
|
CURLOPT_USERAGENT => 'JC-Web-IM-Media-Proxy/1.0',
|
||||||
|
CURLOPT_HTTPHEADER => ['Accept: image/avif,image/webp,image/*,*/*;q=0.8'],
|
||||||
|
CURLOPT_WRITEFUNCTION => static function ($handle, string $chunk) use ($stream, &$bytes, &$tooLarge): int {
|
||||||
|
$length = strlen($chunk);
|
||||||
|
if ($bytes + $length > WEB_MEDIA_PROXY_MAX_BYTES) {
|
||||||
|
$tooLarge = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
$written = fwrite($stream, $chunk);
|
||||||
|
if ($written === false) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
$bytes += $written;
|
||||||
|
return $written;
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
$result = curl_exec($curl);
|
||||||
|
$status = (int)curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
|
||||||
|
$curlError = curl_error($curl);
|
||||||
|
curl_close($curl);
|
||||||
|
fflush($stream);
|
||||||
|
fclose($stream);
|
||||||
|
|
||||||
|
$mime = $bytes > 0 && is_file($temporaryFile) ? mediaProxyMime($temporaryFile) : '';
|
||||||
|
if ($result !== false && $status >= 200 && $status < 300 && !$tooLarge && str_starts_with($mime, 'image/')) {
|
||||||
|
if (!@rename($temporaryFile, $cacheFile)) {
|
||||||
|
@unlink($temporaryFile);
|
||||||
|
$lastError = '媒体缓存保存失败';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
@chmod($cacheFile, 0644);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
@unlink($temporaryFile);
|
||||||
|
$lastError = $tooLarge
|
||||||
|
? '图片超过代理大小限制'
|
||||||
|
: ($curlError !== '' ? $curlError : ($status > 0 ? '媒体源返回 HTTP ' . $status : '媒体源无响应'));
|
||||||
|
if ($attempt < 6 && microtime(true) < $deadline) {
|
||||||
|
usleep(150000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log(sprintf('media_proxy fetch failed host=%s error=%s', $host, $lastError));
|
||||||
|
failMediaProxy(502, '图片源暂时不可用,请稍后重试');
|
||||||
|
}
|
||||||
|
|
||||||
|
function serveMediaProxyImage(string $cacheFile, string $etag): never
|
||||||
|
{
|
||||||
|
if (!is_file($cacheFile)) {
|
||||||
|
failMediaProxy(404, '媒体缓存不存在');
|
||||||
|
}
|
||||||
|
$mime = mediaProxyMime($cacheFile);
|
||||||
|
if (!str_starts_with($mime, 'image/')) {
|
||||||
|
failMediaProxy(415, '媒体内容不是图片');
|
||||||
|
}
|
||||||
|
|
||||||
|
header('ETag: "' . $etag . '"');
|
||||||
|
header('Cache-Control: public, max-age=86400, immutable');
|
||||||
|
header('Content-Type: ' . $mime);
|
||||||
|
header('Content-Length: ' . (string)filesize($cacheFile));
|
||||||
|
header('Content-Disposition: inline');
|
||||||
|
header('X-Content-Type-Options: nosniff');
|
||||||
|
if (trim((string)($_SERVER['HTTP_IF_NONE_MATCH'] ?? ''), ' W/') === '"' . $etag . '"') {
|
||||||
|
http_response_code(304);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
readfile($cacheFile);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$url, $host] = validatedMediaProxyUrl(trim((string)($_GET['url'] ?? '')));
|
||||||
|
$cacheRoot = mediaProxyCacheRoot();
|
||||||
|
$cacheKey = hash('sha256', $url);
|
||||||
|
$cacheFile = $cacheRoot . '/' . $cacheKey . '.image';
|
||||||
|
$lockFile = $cacheRoot . '/' . $cacheKey . '.lock';
|
||||||
|
|
||||||
|
if (!is_file($cacheFile)) {
|
||||||
|
$lock = @fopen($lockFile, 'c');
|
||||||
|
if ($lock === false || !flock($lock, LOCK_EX)) {
|
||||||
|
if (is_resource($lock)) {
|
||||||
|
fclose($lock);
|
||||||
|
}
|
||||||
|
failMediaProxy(503, '媒体缓存暂时忙碌');
|
||||||
|
}
|
||||||
|
if (!is_file($cacheFile)) {
|
||||||
|
fetchMediaProxyImage($url, $host, $cacheFile);
|
||||||
|
}
|
||||||
|
flock($lock, LOCK_UN);
|
||||||
|
fclose($lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
serveMediaProxyImage($cacheFile, $cacheKey);
|
||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Web IM</title><link rel="icon" href="img/5IuOieu9PF8MJtp43kD2kCkdLVjVwDp7.ico"><script src="config/runtime-config.js"></script><script defer="defer" src="js/chunk-vendors.86756863.js"></script><script defer="defer" src="js/app.84630d8b.js"></script><link href="css/app.2c52dabb.css" rel="stylesheet"></head><body><noscript>请启用 JavaScript 后使用 Web IM。</noscript><div id="app"></div></body></html>
|
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Web IM</title><link rel="icon" href="img/5IuOieu9PF8MJtp43kD2kCkdLVjVwDp7.ico"><script src="config/runtime-config.js"></script><script defer="defer" src="js/chunk-vendors.86756863.js"></script><script defer="defer" src="js/app.a89973f7.js"></script><link href="css/app.2c52dabb.css" rel="stylesheet"></head><body><noscript>请启用 JavaScript 后使用 Web IM。</noscript><div id="app"></div></body></html>
|
||||||
Vendored
-1
File diff suppressed because one or more lines are too long
+101
-36
@@ -2879,9 +2879,10 @@ const _hoisted_14 = ["disabled"];
|
|||||||
/* harmony import */ var core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_4__);
|
/* harmony import */ var core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_4__);
|
||||||
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(5130);
|
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(5130);
|
||||||
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(6768);
|
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(6768);
|
||||||
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(5187);
|
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(144);
|
||||||
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(144);
|
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(5187);
|
||||||
/* harmony import */ var _stores_contacts__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(9045);
|
/* harmony import */ var _stores_contacts__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(9045);
|
||||||
|
/* harmony import */ var _services_mediaProxy__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(446);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -2981,6 +2982,7 @@ const _hoisted_36 = ["disabled"];
|
|||||||
const _hoisted_37 = ["disabled"];
|
const _hoisted_37 = ["disabled"];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const IMAGE_MIN_SCALE = 0.5;
|
const IMAGE_MIN_SCALE = 0.5;
|
||||||
const IMAGE_MAX_SCALE = 6;
|
const IMAGE_MAX_SCALE = 6;
|
||||||
/* harmony default export */ __webpack_exports__.A = ({
|
/* harmony default export */ __webpack_exports__.A = ({
|
||||||
@@ -3018,9 +3020,9 @@ const IMAGE_MAX_SCALE = 6;
|
|||||||
const props = __props;
|
const props = __props;
|
||||||
const emit = __emit;
|
const emit = __emit;
|
||||||
const contacts = (0,_stores_contacts__WEBPACK_IMPORTED_MODULE_9__/* .useContactStore */ .p)();
|
const contacts = (0,_stores_contacts__WEBPACK_IMPORTED_MODULE_9__/* .useContactStore */ .p)();
|
||||||
const listEl = (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .ref */ .KR)(null);
|
const listEl = (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .ref */ .KR)(null);
|
||||||
const viewerStage = (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .ref */ .KR)(null);
|
const viewerStage = (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .ref */ .KR)(null);
|
||||||
const contextMenu = (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .reactive */ .Kh)({
|
const contextMenu = (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .reactive */ .Kh)({
|
||||||
visible: false,
|
visible: false,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
@@ -3032,7 +3034,7 @@ const IMAGE_MAX_SCALE = 6;
|
|||||||
hour12: false,
|
hour12: false,
|
||||||
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||||
});
|
});
|
||||||
const imageViewer = (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .reactive */ .Kh)({
|
const imageViewer = (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .reactive */ .Kh)({
|
||||||
visible: false,
|
visible: false,
|
||||||
url: '',
|
url: '',
|
||||||
name: '',
|
name: '',
|
||||||
@@ -3046,7 +3048,7 @@ const IMAGE_MAX_SCALE = 6;
|
|||||||
dragOffsetX: 0,
|
dragOffsetX: 0,
|
||||||
dragOffsetY: 0
|
dragOffsetY: 0
|
||||||
});
|
});
|
||||||
const senderProfile = (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .reactive */ .Kh)({
|
const senderProfile = (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .reactive */ .Kh)({
|
||||||
visible: false,
|
visible: false,
|
||||||
uid: '',
|
uid: '',
|
||||||
displayName: '',
|
displayName: '',
|
||||||
@@ -3565,7 +3567,7 @@ const IMAGE_MAX_SCALE = 6;
|
|||||||
}, [((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(true), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)(vue__WEBPACK_IMPORTED_MODULE_6__/* .Fragment */ .FK, null, (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .renderList */ .pI)(__props.messages, message => {
|
}, [((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(true), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)(vue__WEBPACK_IMPORTED_MODULE_6__/* .Fragment */ .FK, null, (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .renderList */ .pI)(__props.messages, message => {
|
||||||
return (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("article", {
|
return (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("article", {
|
||||||
key: message.id,
|
key: message.id,
|
||||||
class: (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .normalizeClass */ .C4)(["message-row", [message.direction, {
|
class: (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .normalizeClass */ .C4)(["message-row", [message.direction, {
|
||||||
selected: isSelected(message),
|
selected: isSelected(message),
|
||||||
attention: isAttention(message),
|
attention: isAttention(message),
|
||||||
recalled: message.recalled || message.status === 'recalled'
|
recalled: message.recalled || message.status === 'recalled'
|
||||||
@@ -3587,13 +3589,13 @@ const IMAGE_MAX_SCALE = 6;
|
|||||||
key: 0,
|
key: 0,
|
||||||
src: message.senderPortrait,
|
src: message.senderPortrait,
|
||||||
alt: ""
|
alt: ""
|
||||||
}, null, 8, _hoisted_5)) : ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", _hoisted_6, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(groupSenderAvatarText(message)), 1))], 8, _hoisted_4)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("div", _hoisted_7, [showGroupSender(message) ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("button", {
|
}, null, 8, _hoisted_5)) : ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", _hoisted_6, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(groupSenderAvatarText(message)), 1))], 8, _hoisted_4)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("div", _hoisted_7, [showGroupSender(message) ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("button", {
|
||||||
key: 0,
|
key: 0,
|
||||||
type: "button",
|
type: "button",
|
||||||
class: "message-sender-name",
|
class: "message-sender-name",
|
||||||
onClick: (0,vue__WEBPACK_IMPORTED_MODULE_5__/* .withModifiers */ .D$)($event => openSenderProfile(message), ["stop"])
|
onClick: (0,vue__WEBPACK_IMPORTED_MODULE_5__/* .withModifiers */ .D$)($event => openSenderProfile(message), ["stop"])
|
||||||
}, [(0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createTextVNode */ .eW)((0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(groupSenderName(message)) + " ", 1), senderRoleLabel(message) ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", _hoisted_9, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(senderRoleLabel(message)), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true)], 8, _hoisted_8)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("div", {
|
}, [(0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createTextVNode */ .eW)((0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(groupSenderName(message)) + " ", 1), senderRoleLabel(message) ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", _hoisted_9, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(senderRoleLabel(message)), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true)], 8, _hoisted_8)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("div", {
|
||||||
class: (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .normalizeClass */ .C4)(["message-bubble", roleBubbleClass(message)]),
|
class: (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .normalizeClass */ .C4)(["message-bubble", roleBubbleClass(message)]),
|
||||||
onContextmenu: (0,vue__WEBPACK_IMPORTED_MODULE_5__/* .withModifiers */ .D$)($event => openMenu($event, message), ["prevent"])
|
onContextmenu: (0,vue__WEBPACK_IMPORTED_MODULE_5__/* .withModifiers */ .D$)($event => openMenu($event, message), ["prevent"])
|
||||||
}, [isImageMessage(message) ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("button", {
|
}, [isImageMessage(message) ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("button", {
|
||||||
key: 0,
|
key: 0,
|
||||||
@@ -3601,7 +3603,7 @@ const IMAGE_MAX_SCALE = 6;
|
|||||||
class: "message-image",
|
class: "message-image",
|
||||||
onClick: (0,vue__WEBPACK_IMPORTED_MODULE_5__/* .withModifiers */ .D$)($event => handleImageClick(message), ["stop"])
|
onClick: (0,vue__WEBPACK_IMPORTED_MODULE_5__/* .withModifiers */ .D$)($event => handleImageClick(message), ["stop"])
|
||||||
}, [(0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("img", {
|
}, [(0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("img", {
|
||||||
src: message.attachment.url,
|
src: (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .unref */ .R1)(_services_mediaProxy__WEBPACK_IMPORTED_MODULE_10__/* .imageDisplayUrl */ .B)(message.attachment.url),
|
||||||
alt: message.attachment.name || '图片',
|
alt: message.attachment.name || '图片',
|
||||||
loading: "lazy",
|
loading: "lazy",
|
||||||
decoding: "async",
|
decoding: "async",
|
||||||
@@ -3615,18 +3617,18 @@ const IMAGE_MAX_SCALE = 6;
|
|||||||
download: ""
|
download: ""
|
||||||
}, [_cache[7] || (_cache[7] = (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("span", {
|
}, [_cache[7] || (_cache[7] = (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("span", {
|
||||||
class: "message-file-icon"
|
class: "message-file-icon"
|
||||||
}, "▤", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("span", _hoisted_14, [(0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("strong", null, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(message.attachment.name || '文件'), 1), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("small", null, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(formatFileSize(message.attachment.size)), 1)])], 8, _hoisted_13)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), displayMessageText(message) ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("p", _hoisted_15, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(displayMessageText(message)), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("time", null, [isRecallNotice(message) ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", _hoisted_16, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(message.recallStatus || '已撤回'), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("span", null, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(formatTime(message.displayTimestamp || message.timestamp)), 1), message.editedAt ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", _hoisted_17, "【edit " + (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(formatTime(message.editedAt)) + "】", 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), isFavorite(message) ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", _hoisted_18, "★")) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), deliveryState(message) ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", {
|
}, "▤", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("span", _hoisted_14, [(0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("strong", null, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(message.attachment.name || '文件'), 1), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("small", null, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(formatFileSize(message.attachment.size)), 1)])], 8, _hoisted_13)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), displayMessageText(message) ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("p", _hoisted_15, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(displayMessageText(message)), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("time", null, [isRecallNotice(message) ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", _hoisted_16, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(message.recallStatus || '已撤回'), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("span", null, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(formatTime(message.displayTimestamp || message.timestamp)), 1), message.editedAt ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", _hoisted_17, "【edit " + (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(formatTime(message.editedAt)) + "】", 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), isFavorite(message) ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", _hoisted_18, "★")) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), deliveryState(message) ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", {
|
||||||
key: 3,
|
key: 3,
|
||||||
class: (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .normalizeClass */ .C4)(["delivery-mark", `delivery-mark-${deliveryState(message)}`]),
|
class: (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .normalizeClass */ .C4)(["delivery-mark", `delivery-mark-${deliveryState(message)}`]),
|
||||||
title: deliveryTitle(message),
|
title: deliveryTitle(message),
|
||||||
"aria-label": deliveryTitle(message)
|
"aria-label": deliveryTitle(message)
|
||||||
}, [_cache[8] || (_cache[8] = (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("span", {
|
}, [_cache[8] || (_cache[8] = (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("span", {
|
||||||
class: "delivery-check"
|
class: "delivery-check"
|
||||||
}, "✓", -1)), deliveryState(message) !== 'sent' ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", _hoisted_20, "✓")) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true)], 10, _hoisted_19)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), message.status === 'failed' ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", _hoisted_21, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(failureText(message)), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true)])], 42, _hoisted_10)])], 10, _hoisted_1);
|
}, "✓", -1)), deliveryState(message) !== 'sent' ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", _hoisted_20, "✓")) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true)], 10, _hoisted_19)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), message.status === 'failed' ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", _hoisted_21, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(failureText(message)), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true)])], 42, _hoisted_10)])], 10, _hoisted_1);
|
||||||
}), 128)), contextMenu.visible ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("div", {
|
}), 128)), contextMenu.visible ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("div", {
|
||||||
key: 0,
|
key: 0,
|
||||||
class: "message-context-menu",
|
class: "message-context-menu",
|
||||||
style: (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .normalizeStyle */ .Tr)({
|
style: (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .normalizeStyle */ .Tr)({
|
||||||
left: `${contextMenu.x}px`,
|
left: `${contextMenu.x}px`,
|
||||||
top: `${contextMenu.y}px`
|
top: `${contextMenu.y}px`
|
||||||
}),
|
}),
|
||||||
@@ -3636,7 +3638,7 @@ const IMAGE_MAX_SCALE = 6;
|
|||||||
key: item.action,
|
key: item.action,
|
||||||
type: "button",
|
type: "button",
|
||||||
onClick: $event => chooseMenuAction(item.action)
|
onClick: $event => chooseMenuAction(item.action)
|
||||||
}, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(item.label), 9, _hoisted_22);
|
}, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(item.label), 9, _hoisted_22);
|
||||||
}), 128))], 4)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createBlock */ .Wv)(vue__WEBPACK_IMPORTED_MODULE_6__/* .Teleport */ .Im, {
|
}), 128))], 4)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createBlock */ .Wv)(vue__WEBPACK_IMPORTED_MODULE_6__/* .Teleport */ .Im, {
|
||||||
to: "body"
|
to: "body"
|
||||||
}, [imageViewer.visible ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("div", {
|
}, [imageViewer.visible ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("div", {
|
||||||
@@ -3646,7 +3648,7 @@ const IMAGE_MAX_SCALE = 6;
|
|||||||
}, [(0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("div", {
|
}, [(0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("div", {
|
||||||
class: "image-viewer-dialog",
|
class: "image-viewer-dialog",
|
||||||
onClick: _cache[4] || (_cache[4] = (0,vue__WEBPACK_IMPORTED_MODULE_5__/* .withModifiers */ .D$)(() => {}, ["stop"]))
|
onClick: _cache[4] || (_cache[4] = (0,vue__WEBPACK_IMPORTED_MODULE_5__/* .withModifiers */ .D$)(() => {}, ["stop"]))
|
||||||
}, [(0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("div", _hoisted_23, [(0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("span", _hoisted_24, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(imageViewer.name || '图片'), 1), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("button", {
|
}, [(0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("div", _hoisted_23, [(0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("span", _hoisted_24, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(imageViewer.name || '图片'), 1), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
title: "缩小",
|
title: "缩小",
|
||||||
"aria-label": "缩小",
|
"aria-label": "缩小",
|
||||||
@@ -3679,7 +3681,7 @@ const IMAGE_MAX_SCALE = 6;
|
|||||||
}, "×")]), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("div", {
|
}, "×")]), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("div", {
|
||||||
ref_key: "viewerStage",
|
ref_key: "viewerStage",
|
||||||
ref: viewerStage,
|
ref: viewerStage,
|
||||||
class: (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .normalizeClass */ .C4)(["image-viewer-stage", {
|
class: (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .normalizeClass */ .C4)(["image-viewer-stage", {
|
||||||
dragging: imageViewer.dragging,
|
dragging: imageViewer.dragging,
|
||||||
zoomed: imageViewer.scale > 1
|
zoomed: imageViewer.scale > 1
|
||||||
}]),
|
}]),
|
||||||
@@ -3687,9 +3689,9 @@ const IMAGE_MAX_SCALE = 6;
|
|||||||
onPointerdown: (0,vue__WEBPACK_IMPORTED_MODULE_5__/* .withModifiers */ .D$)(startImageDrag, ["prevent"])
|
onPointerdown: (0,vue__WEBPACK_IMPORTED_MODULE_5__/* .withModifiers */ .D$)(startImageDrag, ["prevent"])
|
||||||
}, [(0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("img", {
|
}, [(0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("img", {
|
||||||
class: "image-viewer-img",
|
class: "image-viewer-img",
|
||||||
src: imageViewer.url,
|
src: (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .unref */ .R1)(_services_mediaProxy__WEBPACK_IMPORTED_MODULE_10__/* .imageDisplayUrl */ .B)(imageViewer.url),
|
||||||
alt: imageViewer.name || '图片',
|
alt: imageViewer.name || '图片',
|
||||||
style: (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .normalizeStyle */ .Tr)(imageViewerStyle.value),
|
style: (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .normalizeStyle */ .Tr)(imageViewerStyle.value),
|
||||||
decoding: "async",
|
decoding: "async",
|
||||||
onError: retryImageLoad,
|
onError: retryImageLoad,
|
||||||
draggable: "false"
|
draggable: "false"
|
||||||
@@ -3712,7 +3714,7 @@ const IMAGE_MAX_SCALE = 6;
|
|||||||
class: "profile-avatar image-avatar",
|
class: "profile-avatar image-avatar",
|
||||||
src: senderProfile.portrait,
|
src: senderProfile.portrait,
|
||||||
alt: ""
|
alt: ""
|
||||||
}, null, 8, _hoisted_27)) : ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("div", _hoisted_28, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(senderProfile.avatarText), 1)), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("strong", null, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(senderProfile.displayName || senderProfile.uid), 1), senderProfile.roleLabel ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", _hoisted_29, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(senderProfile.roleLabel), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true)]), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("dl", _hoisted_30, [(0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("div", null, [_cache[10] || (_cache[10] = (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("dt", null, "用户ID", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("dd", null, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(senderProfile.uid || '-'), 1)]), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("div", null, [_cache[11] || (_cache[11] = (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("dt", null, "昵称", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("dd", null, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(senderProfile.rawName || senderProfile.displayName || '-'), 1)]), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("div", null, [_cache[12] || (_cache[12] = (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("dt", null, "手机号", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("dd", null, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(senderProfile.mobile || '未知'), 1)]), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("div", null, [_cache[13] || (_cache[13] = (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("dt", null, "地区", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("dd", null, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(senderProfile.address || '未知'), 1)])]), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("label", _hoisted_31, [_cache[14] || (_cache[14] = (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("span", null, "备注名称", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .withDirectives */ .bo)((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("input", {
|
}, null, 8, _hoisted_27)) : ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("div", _hoisted_28, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(senderProfile.avatarText), 1)), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("strong", null, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(senderProfile.displayName || senderProfile.uid), 1), senderProfile.roleLabel ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", _hoisted_29, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(senderProfile.roleLabel), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true)]), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("dl", _hoisted_30, [(0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("div", null, [_cache[10] || (_cache[10] = (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("dt", null, "用户ID", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("dd", null, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(senderProfile.uid || '-'), 1)]), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("div", null, [_cache[11] || (_cache[11] = (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("dt", null, "昵称", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("dd", null, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(senderProfile.rawName || senderProfile.displayName || '-'), 1)]), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("div", null, [_cache[12] || (_cache[12] = (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("dt", null, "手机号", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("dd", null, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(senderProfile.mobile || '未知'), 1)]), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("div", null, [_cache[13] || (_cache[13] = (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("dt", null, "地区", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("dd", null, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(senderProfile.address || '未知'), 1)])]), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("label", _hoisted_31, [_cache[14] || (_cache[14] = (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("span", null, "备注名称", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .withDirectives */ .bo)((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("input", {
|
||||||
"onUpdate:modelValue": _cache[5] || (_cache[5] = $event => senderProfile.aliasDraft = $event),
|
"onUpdate:modelValue": _cache[5] || (_cache[5] = $event => senderProfile.aliasDraft = $event),
|
||||||
maxlength: "32",
|
maxlength: "32",
|
||||||
autocomplete: "off",
|
autocomplete: "off",
|
||||||
@@ -3720,18 +3722,18 @@ const IMAGE_MAX_SCALE = 6;
|
|||||||
disabled: senderProfile.saving || senderProfile.adding
|
disabled: senderProfile.saving || senderProfile.adding
|
||||||
}, null, 8, _hoisted_32), [[vue__WEBPACK_IMPORTED_MODULE_5__/* .vModelText */ .Jo, senderProfile.aliasDraft, void 0, {
|
}, null, 8, _hoisted_32), [[vue__WEBPACK_IMPORTED_MODULE_5__/* .vModelText */ .Jo, senderProfile.aliasDraft, void 0, {
|
||||||
trim: true
|
trim: true
|
||||||
}]])]), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("footer", _hoisted_33, [senderProfile.notice ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", _hoisted_34, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(senderProfile.notice), 1)) : senderProfile.error ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", _hoisted_35, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(senderProfile.error), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), !senderProfile.isContact ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("button", {
|
}]])]), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("footer", _hoisted_33, [senderProfile.notice ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", _hoisted_34, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(senderProfile.notice), 1)) : senderProfile.error ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("span", _hoisted_35, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(senderProfile.error), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), !senderProfile.isContact ? ((0,vue__WEBPACK_IMPORTED_MODULE_6__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementBlock */ .CE)("button", {
|
||||||
key: 2,
|
key: 2,
|
||||||
type: "button",
|
type: "button",
|
||||||
class: "ghost-inline-action",
|
class: "ghost-inline-action",
|
||||||
disabled: senderProfile.adding || senderProfile.saving || !senderProfile.uid,
|
disabled: senderProfile.adding || senderProfile.saving || !senderProfile.uid,
|
||||||
onClick: addSenderAsContact
|
onClick: addSenderAsContact
|
||||||
}, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(senderProfile.adding ? '添加中...' : '添加为好友'), 9, _hoisted_36)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("button", {
|
}, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(senderProfile.adding ? '添加中...' : '添加为好友'), 9, _hoisted_36)) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true), (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createElementVNode */ .Lk)("button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
class: "primary-inline-action",
|
class: "primary-inline-action",
|
||||||
disabled: senderProfile.saving || senderProfile.adding || !senderProfile.uid,
|
disabled: senderProfile.saving || senderProfile.adding || !senderProfile.uid,
|
||||||
onClick: saveSenderAlias
|
onClick: saveSenderAlias
|
||||||
}, (0,vue__WEBPACK_IMPORTED_MODULE_7__/* .toDisplayString */ .v_)(senderProfile.saving ? '保存中...' : '保存备注'), 9, _hoisted_37)])])])) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true)]))], 512);
|
}, (0,vue__WEBPACK_IMPORTED_MODULE_8__/* .toDisplayString */ .v_)(senderProfile.saving ? '保存中...' : '保存备注'), 9, _hoisted_37)])])])) : (0,vue__WEBPACK_IMPORTED_MODULE_6__/* .createCommentVNode */ .Q3)("", true)]))], 512);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -3982,10 +3984,11 @@ const _hoisted_6 = {
|
|||||||
/* harmony import */ var core_js_modules_es_iterator_find_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(116);
|
/* harmony import */ var core_js_modules_es_iterator_find_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(116);
|
||||||
/* harmony import */ var core_js_modules_es_iterator_find_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_find_js__WEBPACK_IMPORTED_MODULE_1__);
|
/* harmony import */ var core_js_modules_es_iterator_find_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_find_js__WEBPACK_IMPORTED_MODULE_1__);
|
||||||
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6768);
|
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6768);
|
||||||
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5187);
|
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(144);
|
||||||
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(144);
|
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(5187);
|
||||||
/* harmony import */ var _stores_conversations__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(7566);
|
/* harmony import */ var _stores_conversations__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(7566);
|
||||||
/* harmony import */ var _stores_navigation__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(1414);
|
/* harmony import */ var _stores_navigation__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(1414);
|
||||||
|
/* harmony import */ var _services_mediaProxy__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(446);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -4032,14 +4035,15 @@ const _hoisted_14 = {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* harmony default export */ __webpack_exports__.A = ({
|
/* harmony default export */ __webpack_exports__.A = ({
|
||||||
__name: 'FavoritePanel',
|
__name: 'FavoritePanel',
|
||||||
setup(__props) {
|
setup(__props) {
|
||||||
const conversations = (0,_stores_conversations__WEBPACK_IMPORTED_MODULE_5__/* .useConversationStore */ .w)();
|
const conversations = (0,_stores_conversations__WEBPACK_IMPORTED_MODULE_5__/* .useConversationStore */ .w)();
|
||||||
const navigation = (0,_stores_navigation__WEBPACK_IMPORTED_MODULE_6__/* .useNavigationStore */ .l)();
|
const navigation = (0,_stores_navigation__WEBPACK_IMPORTED_MODULE_6__/* .useNavigationStore */ .l)();
|
||||||
const favorite = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .computed */ .EW)(() => conversations.activeFavorite);
|
const favorite = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .computed */ .EW)(() => conversations.activeFavorite);
|
||||||
const notice = (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .ref */ .KR)('');
|
const notice = (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .ref */ .KR)('');
|
||||||
const error = (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .ref */ .KR)('');
|
const error = (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .ref */ .KR)('');
|
||||||
function clearFeedback() {
|
function clearFeedback() {
|
||||||
notice.value = '';
|
notice.value = '';
|
||||||
error.value = '';
|
error.value = '';
|
||||||
@@ -4165,20 +4169,20 @@ const _hoisted_14 = {
|
|||||||
return (_ctx, _cache) => {
|
return (_ctx, _cache) => {
|
||||||
return (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("section", _hoisted_1, [favorite.value ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)(vue__WEBPACK_IMPORTED_MODULE_2__/* .Fragment */ .FK, {
|
return (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("section", _hoisted_1, [favorite.value ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)(vue__WEBPACK_IMPORTED_MODULE_2__/* .Fragment */ .FK, {
|
||||||
key: 0
|
key: 0
|
||||||
}, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("header", _hoisted_2, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("div", null, [_cache[0] || (_cache[0] = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("h2", null, "收藏详情", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("p", null, (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .toDisplayString */ .v_)(favoriteTitle(favorite.value)), 1)])]), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("div", _hoisted_3, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("div", {
|
}, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("header", _hoisted_2, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("div", null, [_cache[0] || (_cache[0] = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("h2", null, "收藏详情", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("p", null, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)(favoriteTitle(favorite.value)), 1)])]), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("div", _hoisted_3, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("div", {
|
||||||
class: (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .normalizeClass */ .C4)(["favorite-detail-message", {
|
class: (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .normalizeClass */ .C4)(["favorite-detail-message", {
|
||||||
recalled: favorite.value.recalled
|
recalled: favorite.value.recalled
|
||||||
}])
|
}])
|
||||||
}, [_cache[2] || (_cache[2] = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("span", {
|
}, [_cache[2] || (_cache[2] = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("span", {
|
||||||
class: "favorite-detail-star"
|
class: "favorite-detail-star"
|
||||||
}, "★", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("div", _hoisted_4, [favorite.value.recalled ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("span", _hoisted_5, (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .toDisplayString */ .v_)(favorite.value.recallStatus || '已撤回'), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createCommentVNode */ .Q3)("", true), isImageFavorite(favorite.value) ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("a", {
|
}, "★", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("div", _hoisted_4, [favorite.value.recalled ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("span", _hoisted_5, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)(favorite.value.recallStatus || '已撤回'), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createCommentVNode */ .Q3)("", true), isImageFavorite(favorite.value) ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("a", {
|
||||||
key: 1,
|
key: 1,
|
||||||
class: "favorite-detail-image",
|
class: "favorite-detail-image",
|
||||||
href: favorite.value.attachment.url,
|
href: (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .unref */ .R1)(_services_mediaProxy__WEBPACK_IMPORTED_MODULE_7__/* .imageDisplayUrl */ .B)(favorite.value.attachment.url),
|
||||||
target: "_blank",
|
target: "_blank",
|
||||||
rel: "noreferrer"
|
rel: "noreferrer"
|
||||||
}, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("img", {
|
}, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("img", {
|
||||||
src: favorite.value.attachment.url,
|
src: (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .unref */ .R1)(_services_mediaProxy__WEBPACK_IMPORTED_MODULE_7__/* .imageDisplayUrl */ .B)(favorite.value.attachment.url),
|
||||||
alt: favorite.value.attachment.name || '图片'
|
alt: favorite.value.attachment.name || '图片'
|
||||||
}, null, 8, _hoisted_7)], 8, _hoisted_6)) : isFileFavorite(favorite.value) ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("a", {
|
}, null, 8, _hoisted_7)], 8, _hoisted_6)) : isFileFavorite(favorite.value) ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("a", {
|
||||||
key: 2,
|
key: 2,
|
||||||
@@ -4189,7 +4193,7 @@ const _hoisted_14 = {
|
|||||||
download: ""
|
download: ""
|
||||||
}, [_cache[1] || (_cache[1] = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("span", {
|
}, [_cache[1] || (_cache[1] = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("span", {
|
||||||
class: "message-file-icon"
|
class: "message-file-icon"
|
||||||
}, "▤", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("span", _hoisted_9, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("strong", null, (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .toDisplayString */ .v_)(favorite.value.attachment.name || '文件'), 1), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("small", null, (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .toDisplayString */ .v_)(formatFileSize(favorite.value.attachment.size)), 1)])], 8, _hoisted_8)) : (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createCommentVNode */ .Q3)("", true), favoriteText(favorite.value) ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("p", _hoisted_10, (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .toDisplayString */ .v_)(favoriteText(favorite.value)), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createCommentVNode */ .Q3)("", true)]), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("time", null, (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .toDisplayString */ .v_)(formatFavoriteTime(favorite.value.timestamp)), 1)], 2), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("div", _hoisted_11, [notice.value ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("span", _hoisted_12, (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .toDisplayString */ .v_)(notice.value), 1)) : error.value ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("span", _hoisted_13, (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .toDisplayString */ .v_)(error.value), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createCommentVNode */ .Q3)("", true), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("button", {
|
}, "▤", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("span", _hoisted_9, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("strong", null, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)(favorite.value.attachment.name || '文件'), 1), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("small", null, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)(formatFileSize(favorite.value.attachment.size)), 1)])], 8, _hoisted_8)) : (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createCommentVNode */ .Q3)("", true), favoriteText(favorite.value) ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("p", _hoisted_10, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)(favoriteText(favorite.value)), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createCommentVNode */ .Q3)("", true)]), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("time", null, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)(formatFavoriteTime(favorite.value.timestamp)), 1)], 2), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("div", _hoisted_11, [notice.value ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("span", _hoisted_12, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)(notice.value), 1)) : error.value ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("span", _hoisted_13, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)(error.value), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createCommentVNode */ .Q3)("", true), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
class: "ghost-inline-action",
|
class: "ghost-inline-action",
|
||||||
onClick: copyFavorite
|
onClick: copyFavorite
|
||||||
@@ -7225,6 +7229,67 @@ class LongConnectionImClient extends _httpImClient__WEBPACK_IMPORTED_MODULE_5__/
|
|||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ 446:
|
||||||
|
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
||||||
|
/* harmony export */ B: function() { return /* binding */ imageDisplayUrl; }
|
||||||
|
/* harmony export */ });
|
||||||
|
/* harmony import */ var core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8111);
|
||||||
|
/* harmony import */ var core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_0__);
|
||||||
|
/* harmony import */ var core_js_modules_es_iterator_filter_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2489);
|
||||||
|
/* harmony import */ var core_js_modules_es_iterator_filter_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_filter_js__WEBPACK_IMPORTED_MODULE_1__);
|
||||||
|
/* harmony import */ var core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1701);
|
||||||
|
/* harmony import */ var core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_2__);
|
||||||
|
/* harmony import */ var core_js_modules_es_set_difference_v2_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7642);
|
||||||
|
/* harmony import */ var core_js_modules_es_set_difference_v2_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_difference_v2_js__WEBPACK_IMPORTED_MODULE_3__);
|
||||||
|
/* harmony import */ var core_js_modules_es_set_intersection_v2_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(8004);
|
||||||
|
/* harmony import */ var core_js_modules_es_set_intersection_v2_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_intersection_v2_js__WEBPACK_IMPORTED_MODULE_4__);
|
||||||
|
/* harmony import */ var core_js_modules_es_set_is_disjoint_from_v2_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(3853);
|
||||||
|
/* harmony import */ var core_js_modules_es_set_is_disjoint_from_v2_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_is_disjoint_from_v2_js__WEBPACK_IMPORTED_MODULE_5__);
|
||||||
|
/* harmony import */ var core_js_modules_es_set_is_subset_of_v2_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5876);
|
||||||
|
/* harmony import */ var core_js_modules_es_set_is_subset_of_v2_js__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_is_subset_of_v2_js__WEBPACK_IMPORTED_MODULE_6__);
|
||||||
|
/* harmony import */ var core_js_modules_es_set_is_superset_of_v2_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(2475);
|
||||||
|
/* harmony import */ var core_js_modules_es_set_is_superset_of_v2_js__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_is_superset_of_v2_js__WEBPACK_IMPORTED_MODULE_7__);
|
||||||
|
/* harmony import */ var core_js_modules_es_set_symmetric_difference_v2_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(5024);
|
||||||
|
/* harmony import */ var core_js_modules_es_set_symmetric_difference_v2_js__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_symmetric_difference_v2_js__WEBPACK_IMPORTED_MODULE_8__);
|
||||||
|
/* harmony import */ var core_js_modules_es_set_union_v2_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(1698);
|
||||||
|
/* harmony import */ var core_js_modules_es_set_union_v2_js__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_union_v2_js__WEBPACK_IMPORTED_MODULE_9__);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function configuredProxyHosts() {
|
||||||
|
const configured = window.JC_WEB_IM_CONFIG?.mediaProxyHosts;
|
||||||
|
const values = Array.isArray(configured) ? configured : String(configured || '').split(/[\s,]+/);
|
||||||
|
return new Set(values.map(value => String(value || '').trim().toLowerCase().replace(/\.$/, '')).filter(Boolean));
|
||||||
|
}
|
||||||
|
function imageDisplayUrl(value) {
|
||||||
|
const source = String(value || '').trim();
|
||||||
|
if (!source || source.startsWith('blob:') || source.startsWith('data:')) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const url = new URL(source, window.location.origin);
|
||||||
|
if (url.protocol !== 'https:' || !configuredProxyHosts().has(url.hostname.toLowerCase())) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
const proxyUrl = new URL('/api/media_proxy.php', window.location.origin);
|
||||||
|
proxyUrl.searchParams.set('url', url.toString());
|
||||||
|
return proxyUrl.toString();
|
||||||
|
} catch (error) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
/***/ 9687:
|
/***/ 9687:
|
||||||
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
@@ -9809,4 +9874,4 @@ const __exports__ = _NavigationRail_vue_vue_type_script_setup_true_lang_js__WEBP
|
|||||||
/******/
|
/******/
|
||||||
/******/ })()
|
/******/ })()
|
||||||
;
|
;
|
||||||
//# sourceMappingURL=app.84630d8b.js.map
|
//# sourceMappingURL=app.a89973f7.js.map
|
||||||
Vendored
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,205 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require __DIR__ . '/im_web_bootstrap.php';
|
||||||
|
|
||||||
|
const WEB_MEDIA_PROXY_MAX_BYTES = 15 * 1024 * 1024;
|
||||||
|
const WEB_MEDIA_PROXY_MAX_SECONDS = 25;
|
||||||
|
|
||||||
|
function failMediaProxy(int $status, string $message): never
|
||||||
|
{
|
||||||
|
http_response_code($status);
|
||||||
|
header('Content-Type: text/plain; charset=utf-8');
|
||||||
|
header('Cache-Control: no-store');
|
||||||
|
header('X-Content-Type-Options: nosniff');
|
||||||
|
echo $message;
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mediaProxyAllowedHosts(): array
|
||||||
|
{
|
||||||
|
$configured = webImEnv('JC_WEB_IM_MEDIA_PROXY_HOSTS');
|
||||||
|
$hosts = array_map(
|
||||||
|
static fn(string $host): string => strtolower(rtrim(trim($host), '.')),
|
||||||
|
preg_split('/[,\s]+/', $configured) ?: []
|
||||||
|
);
|
||||||
|
|
||||||
|
return array_values(array_unique(array_filter($hosts)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function validatedMediaProxyUrl(string $url): array
|
||||||
|
{
|
||||||
|
if ($url === '' || strlen($url) > 2048 || filter_var($url, FILTER_VALIDATE_URL) === false) {
|
||||||
|
failMediaProxy(400, '媒体地址不合法');
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = parse_url($url);
|
||||||
|
$scheme = strtolower((string)($parts['scheme'] ?? ''));
|
||||||
|
$host = strtolower(rtrim((string)($parts['host'] ?? ''), '.'));
|
||||||
|
$port = isset($parts['port']) ? (int)$parts['port'] : 443;
|
||||||
|
if (
|
||||||
|
$scheme !== 'https'
|
||||||
|
|| $host === ''
|
||||||
|
|| $port !== 443
|
||||||
|
|| isset($parts['user'])
|
||||||
|
|| isset($parts['pass'])
|
||||||
|
|| !in_array($host, mediaProxyAllowedHosts(), true)
|
||||||
|
) {
|
||||||
|
failMediaProxy(403, '媒体来源不允许代理');
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$url, $host];
|
||||||
|
}
|
||||||
|
|
||||||
|
function mediaProxyCacheRoot(): string
|
||||||
|
{
|
||||||
|
$root = rtrim(webImEnv('JC_WEB_IM_MEDIA_CACHE_ROOT', '/var/cache/jc-web-im-media'), '/');
|
||||||
|
if ($root === '' || !is_dir($root) || !is_writable($root)) {
|
||||||
|
failMediaProxy(503, '媒体缓存目录不可用');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $root;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mediaProxyMime(string $path): string
|
||||||
|
{
|
||||||
|
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||||
|
$mime = $finfo !== false ? (string)(finfo_file($finfo, $path) ?: '') : '';
|
||||||
|
if ($finfo !== false) {
|
||||||
|
finfo_close($finfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $mime;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchMediaProxyImage(string $url, string $host, string $cacheFile): void
|
||||||
|
{
|
||||||
|
$deadline = microtime(true) + WEB_MEDIA_PROXY_MAX_SECONDS;
|
||||||
|
$lastError = '媒体源无响应';
|
||||||
|
|
||||||
|
for ($attempt = 1; $attempt <= 6 && microtime(true) < $deadline; $attempt++) {
|
||||||
|
$temporaryFile = $cacheFile . '.tmp.' . getmypid() . '.' . $attempt;
|
||||||
|
$stream = @fopen($temporaryFile, 'x+b');
|
||||||
|
if ($stream === false) {
|
||||||
|
$lastError = '无法创建媒体缓存文件';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bytes = 0;
|
||||||
|
$tooLarge = false;
|
||||||
|
$curl = curl_init($url);
|
||||||
|
if ($curl === false) {
|
||||||
|
fclose($stream);
|
||||||
|
@unlink($temporaryFile);
|
||||||
|
$lastError = '无法初始化媒体请求';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$remainingMs = max(1000, (int)floor(($deadline - microtime(true)) * 1000));
|
||||||
|
curl_setopt_array($curl, [
|
||||||
|
CURLOPT_RETURNTRANSFER => false,
|
||||||
|
CURLOPT_FOLLOWLOCATION => false,
|
||||||
|
CURLOPT_CONNECTTIMEOUT_MS => min(2500, $remainingMs),
|
||||||
|
CURLOPT_TIMEOUT_MS => min(12000, $remainingMs),
|
||||||
|
CURLOPT_NOSIGNAL => true,
|
||||||
|
CURLOPT_DNS_CACHE_TIMEOUT => 0,
|
||||||
|
CURLOPT_FRESH_CONNECT => true,
|
||||||
|
CURLOPT_FORBID_REUSE => true,
|
||||||
|
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
|
||||||
|
CURLOPT_SSL_VERIFYPEER => true,
|
||||||
|
CURLOPT_SSL_VERIFYHOST => 2,
|
||||||
|
CURLOPT_USERAGENT => 'JC-Web-IM-Media-Proxy/1.0',
|
||||||
|
CURLOPT_HTTPHEADER => ['Accept: image/avif,image/webp,image/*,*/*;q=0.8'],
|
||||||
|
CURLOPT_WRITEFUNCTION => static function ($handle, string $chunk) use ($stream, &$bytes, &$tooLarge): int {
|
||||||
|
$length = strlen($chunk);
|
||||||
|
if ($bytes + $length > WEB_MEDIA_PROXY_MAX_BYTES) {
|
||||||
|
$tooLarge = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
$written = fwrite($stream, $chunk);
|
||||||
|
if ($written === false) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
$bytes += $written;
|
||||||
|
return $written;
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
$result = curl_exec($curl);
|
||||||
|
$status = (int)curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
|
||||||
|
$curlError = curl_error($curl);
|
||||||
|
curl_close($curl);
|
||||||
|
fflush($stream);
|
||||||
|
fclose($stream);
|
||||||
|
|
||||||
|
$mime = $bytes > 0 && is_file($temporaryFile) ? mediaProxyMime($temporaryFile) : '';
|
||||||
|
if ($result !== false && $status >= 200 && $status < 300 && !$tooLarge && str_starts_with($mime, 'image/')) {
|
||||||
|
if (!@rename($temporaryFile, $cacheFile)) {
|
||||||
|
@unlink($temporaryFile);
|
||||||
|
$lastError = '媒体缓存保存失败';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
@chmod($cacheFile, 0644);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
@unlink($temporaryFile);
|
||||||
|
$lastError = $tooLarge
|
||||||
|
? '图片超过代理大小限制'
|
||||||
|
: ($curlError !== '' ? $curlError : ($status > 0 ? '媒体源返回 HTTP ' . $status : '媒体源无响应'));
|
||||||
|
if ($attempt < 6 && microtime(true) < $deadline) {
|
||||||
|
usleep(150000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log(sprintf('media_proxy fetch failed host=%s error=%s', $host, $lastError));
|
||||||
|
failMediaProxy(502, '图片源暂时不可用,请稍后重试');
|
||||||
|
}
|
||||||
|
|
||||||
|
function serveMediaProxyImage(string $cacheFile, string $etag): never
|
||||||
|
{
|
||||||
|
if (!is_file($cacheFile)) {
|
||||||
|
failMediaProxy(404, '媒体缓存不存在');
|
||||||
|
}
|
||||||
|
$mime = mediaProxyMime($cacheFile);
|
||||||
|
if (!str_starts_with($mime, 'image/')) {
|
||||||
|
failMediaProxy(415, '媒体内容不是图片');
|
||||||
|
}
|
||||||
|
|
||||||
|
header('ETag: "' . $etag . '"');
|
||||||
|
header('Cache-Control: public, max-age=86400, immutable');
|
||||||
|
header('Content-Type: ' . $mime);
|
||||||
|
header('Content-Length: ' . (string)filesize($cacheFile));
|
||||||
|
header('Content-Disposition: inline');
|
||||||
|
header('X-Content-Type-Options: nosniff');
|
||||||
|
if (trim((string)($_SERVER['HTTP_IF_NONE_MATCH'] ?? ''), ' W/') === '"' . $etag . '"') {
|
||||||
|
http_response_code(304);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
readfile($cacheFile);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$url, $host] = validatedMediaProxyUrl(trim((string)($_GET['url'] ?? '')));
|
||||||
|
$cacheRoot = mediaProxyCacheRoot();
|
||||||
|
$cacheKey = hash('sha256', $url);
|
||||||
|
$cacheFile = $cacheRoot . '/' . $cacheKey . '.image';
|
||||||
|
$lockFile = $cacheRoot . '/' . $cacheKey . '.lock';
|
||||||
|
|
||||||
|
if (!is_file($cacheFile)) {
|
||||||
|
$lock = @fopen($lockFile, 'c');
|
||||||
|
if ($lock === false || !flock($lock, LOCK_EX)) {
|
||||||
|
if (is_resource($lock)) {
|
||||||
|
fclose($lock);
|
||||||
|
}
|
||||||
|
failMediaProxy(503, '媒体缓存暂时忙碌');
|
||||||
|
}
|
||||||
|
if (!is_file($cacheFile)) {
|
||||||
|
fetchMediaProxyImage($url, $host, $cacheFile);
|
||||||
|
}
|
||||||
|
flock($lock, LOCK_UN);
|
||||||
|
fclose($lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
serveMediaProxyImage($cacheFile, $cacheKey);
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
@click.stop="handleImageClick(message)"
|
@click.stop="handleImageClick(message)"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="message.attachment.url"
|
:src="imageDisplayUrl(message.attachment.url)"
|
||||||
:alt="message.attachment.name || '图片'"
|
:alt="message.attachment.name || '图片'"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
class="image-viewer-img"
|
class="image-viewer-img"
|
||||||
:src="imageViewer.url"
|
:src="imageDisplayUrl(imageViewer.url)"
|
||||||
:alt="imageViewer.name || '图片'"
|
:alt="imageViewer.name || '图片'"
|
||||||
:style="imageViewerStyle"
|
:style="imageViewerStyle"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
@@ -219,6 +219,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
|
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
|
||||||
import { useContactStore } from '@/stores/contacts';
|
import { useContactStore } from '@/stores/contacts';
|
||||||
|
import { imageDisplayUrl } from '@/services/mediaProxy';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
messages: {
|
messages: {
|
||||||
|
|||||||
@@ -15,11 +15,11 @@
|
|||||||
<a
|
<a
|
||||||
v-if="isImageFavorite(favorite)"
|
v-if="isImageFavorite(favorite)"
|
||||||
class="favorite-detail-image"
|
class="favorite-detail-image"
|
||||||
:href="favorite.attachment.url"
|
:href="imageDisplayUrl(favorite.attachment.url)"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
<img :src="favorite.attachment.url" :alt="favorite.attachment.name || '图片'">
|
<img :src="imageDisplayUrl(favorite.attachment.url)" :alt="favorite.attachment.name || '图片'">
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
v-else-if="isFileFavorite(favorite)"
|
v-else-if="isFileFavorite(favorite)"
|
||||||
@@ -59,6 +59,7 @@
|
|||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useConversationStore } from '@/stores/conversations';
|
import { useConversationStore } from '@/stores/conversations';
|
||||||
import { useNavigationStore } from '@/stores/navigation';
|
import { useNavigationStore } from '@/stores/navigation';
|
||||||
|
import { imageDisplayUrl } from '@/services/mediaProxy';
|
||||||
|
|
||||||
const conversations = useConversationStore();
|
const conversations = useConversationStore();
|
||||||
const navigation = useNavigationStore();
|
const navigation = useNavigationStore();
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
function configuredProxyHosts() {
|
||||||
|
const configured = window.JC_WEB_IM_CONFIG?.mediaProxyHosts;
|
||||||
|
const values = Array.isArray(configured)
|
||||||
|
? configured
|
||||||
|
: String(configured || '').split(/[\s,]+/);
|
||||||
|
return new Set(
|
||||||
|
values
|
||||||
|
.map(value => String(value || '').trim().toLowerCase().replace(/\.$/, ''))
|
||||||
|
.filter(Boolean)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function imageDisplayUrl(value) {
|
||||||
|
const source = String(value || '').trim();
|
||||||
|
if (!source || source.startsWith('blob:') || source.startsWith('data:')) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = new URL(source, window.location.origin);
|
||||||
|
if (url.protocol !== 'https:' || !configuredProxyHosts().has(url.hostname.toLowerCase())) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
const proxyUrl = new URL('/api/media_proxy.php', window.location.origin);
|
||||||
|
proxyUrl.searchParams.set('url', url.toString());
|
||||||
|
return proxyUrl.toString();
|
||||||
|
} catch (error) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user