Compare commits

...

24 Commits

Author SHA1 Message Date
Tim
6497cb92af chore: remove ffmpeg video compression 2025-09-12 10:41:48 +08:00
Tim
37bef0b2d7 fix: remove 依赖 2025-09-12 10:15:17 +08:00
Tim
3519a41a2e Merge pull request #975 from nagisa77/feature/ffmpeg_load
Feature/ffmpeg load
2025-09-11 19:12:16 +08:00
tim
ab04a8b6b1 fix: ffmpeg 压缩适配 2025-09-11 19:10:14 +08:00
tim
ea079e8b8a fix: 简化ffmpeg配置 2025-09-11 18:36:47 +08:00
Tim
519656359f Merge pull request #974 from 4twocc/feat/message-box-shortcut
feat(MessageEditor): 添加发送消息的快捷键支持
2025-09-11 17:56:22 +08:00
jiahaosheng
dc64785279 feat: rename is.js to device.js 2025-09-11 17:53:08 +08:00
jiahaosheng
9421d004d4 feat(MessageEditor): 添加发送消息的快捷键支持 2025-09-11 17:27:54 +08:00
tim
90bd41e740 Revert "feat: switch video compression to webcodecs"
This reverts commit 3f35add587.
2025-09-11 17:20:08 +08:00
Tim
7d5c864f64 Merge pull request #973 from nagisa77/codex/switch-video-upload-to-webcodec-and-mp4box.js-bkkx49
feat: replace ffmpeg with WebCodecs and MP4Box.js
2025-09-11 17:02:08 +08:00
Tim
3f35add587 feat: switch video compression to webcodecs 2025-09-11 17:01:54 +08:00
Tim
1e284e15df Merge pull request #970 from sivdead/feat/video_upload_and_compress
feat(frontend/vditor): 实现基于 FFmpeg.wasm 的视频压缩功能
2025-09-11 12:54:12 +08:00
sivdead
9d76926b8a feat(frontend/vditor): 实现基于 FFmpeg.wasm 的视频压缩功能
- 添加视频压缩相关配置和工具函数
- 实现 FFmpeg.wasm 初始化和视频压缩功能
- 优化文件上传流程,支持视频文件压缩
2025-09-11 10:05:50 +08:00
tim
d2ce203236 Merge branch 'main' of github.com:nagisa77/OpenIsle 2025-09-10 18:13:16 +08:00
tim
b2228296af fix: hover 新增动画 2025-09-10 18:13:04 +08:00
Tim
7020ae19d0 Merge pull request #968 from smallclover/main
追加快捷键
2025-09-10 18:09:18 +08:00
tim
227fb6f6cc fix: 首页padding修改 2025-09-10 18:07:22 +08:00
wangshun
0e46a67ea6 评论追加快捷键
1.手机时不显示icon,且快捷键不起用
2.电脑端适配win和mac
2025-09-10 18:01:07 +08:00
wangshun
b20b705e46 添加快捷键
+ 不是手机的情况下不启用快捷键
2025-09-10 17:44:53 +08:00
夢夢の幻想郷
4b3ffbab99 Merge branch 'nagisa77:main' into main 2025-09-10 17:43:40 +08:00
tim
74039c89f9 Merge branch 'main' of github.com:nagisa77/OpenIsle 2025-09-10 17:42:32 +08:00
tim
10dca73d2f fix: 新增本地GitHub调试 2025-09-10 17:42:19 +08:00
wangshun
e37ed1b70b 评论追加快捷键
ps:schedule包名拼写错误修正
2025-09-10 15:40:49 +08:00
Tim
8500a7a914 Merge pull request #965 from nagisa77/feature/homepage_ui
fix: 首页banner和帖子之间间距可以大一些 #849
2025-09-10 14:00:19 +08:00
13 changed files with 291 additions and 2452 deletions

View File

@@ -1,4 +1,4 @@
package com.openisle.schdule;
package com.openisle.scheduler;
import com.openisle.config.CachingConfig;
import com.openisle.model.User;

View File

@@ -4,7 +4,9 @@ NUXT_PUBLIC_WEBSOCKET_URL=https://127.0.0.1:8082
NUXT_PUBLIC_WEBSITE_BASE_URL=http://localhost:3000
NUXT_PUBLIC_GOOGLE_CLIENT_ID=777830451304-nt8afkkap18gui4f9entcha99unal744.apps.googleusercontent.com
NUXT_PUBLIC_GITHUB_CLIENT_ID=Ov23liVkO1NPAX5JyWxJ
# NUXT_PUBLIC_GITHUB_CLIENT_ID=Ov23liVkO1NPAX5JyWxJ
; 本地
NUXT_PUBLIC_GITHUB_CLIENT_ID=Ov23liOlrZnPKRF7s7NN
NUXT_PUBLIC_DISCORD_CLIENT_ID=1394985417044000779
NUXT_PUBLIC_TWITTER_CLIENT_ID=ZTRTU05KSk9KTTJrTTdrVC1tc1E6MTpjaQ
NUXT_PUBLIC_TELEGRAM_BOT_ID=8450237135

View File

@@ -6,8 +6,15 @@
</div>
<div class="comment-bottom-container">
<div class="comment-submit" :class="{ disabled: isDisabled }" @click="submit">
<template v-if="!loading"> 发布评论 </template>
<template v-else> <loading-four /> 发布中... </template>
<template v-if="!loading">
发布评论
<span class="shortcut-icon" v-if="!isMobile">
{{ isMac ? '' : 'Ctrl' }}
</span>
</template>
<template v-else>
<loading-four /> 发布中...
</template>
</div>
</div>
</div>
@@ -24,6 +31,7 @@ import {
} from '~/utils/vditor'
import '~/assets/global.css'
import LoginOverlay from '~/components/LoginOverlay.vue'
import { useIsMobile } from '~/utils/screen'
export default {
name: 'CommentEditor',
@@ -52,12 +60,22 @@ export default {
},
components: { LoginOverlay },
setup(props, { emit }) {
const isMobile = useIsMobile()
const vditorInstance = ref(null)
const text = ref('')
const editorId = ref(props.editorId)
if (!editorId.value) {
editorId.value = 'editor-' + useId()
}
const isMac = ref(false)
if (navigator.userAgentData) {
isMac.value = navigator.userAgentData.platform === 'macOS'
} else {
isMac.value = /Mac|iPhone|iPad|iPod/.test(navigator.userAgent)
}
const getEditorTheme = getEditorThemeUtil
const getPreviewTheme = getPreviewThemeUtil
const applyTheme = () => {
@@ -96,7 +114,27 @@ export default {
applyTheme()
},
})
// applyTheme()
// 不是手机的情况下不添加快捷键
if(!isMobile.value){
// 添加快捷键监听 (Ctrl+Enter 或 Cmd+Enter)
const handleKeydown = (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault()
submit()
}
}
const el = document.getElementById(editorId.value)
if (el) {
el.addEventListener('keydown', handleKeydown)
}
onUnmounted(() => {
if (el) {
el.removeEventListener('keydown', handleKeydown)
}
})
}
})
onUnmounted(() => {
@@ -134,7 +172,7 @@ export default {
},
)
return { submit, isDisabled, editorId }
return { submit, isDisabled, editorId, isMac, isMobile}
},
}
</script>
@@ -174,10 +212,16 @@ export default {
.comment-submit:hover {
background-color: var(--primary-color-hover);
}
@media (max-width: 768px) {
.comment-editor-container {
margin-bottom: 10px;
}
/** 评论按钮快捷键样式 */
.shortcut-icon {
padding: 2px 6px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
line-height: 1.2;
background-color: rgba(0, 0, 0, 0.25);
}
.comment-submit.disabled .shortcut-icon {
background-color: rgba(0, 0, 0, 0);
}
</style>

View File

@@ -314,6 +314,7 @@ const gotoTag = (t) => {
border-radius: 10px;
display: flex;
align-items: center;
transition: background-color 0.5s ease;
}
.menu-item:hover {
@@ -408,6 +409,7 @@ const gotoTag = (t) => {
gap: 5px;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.5s ease;
}
.section-item:hover {

View File

@@ -5,7 +5,10 @@
</div>
<div class="message-bottom-container">
<div class="message-submit" :class="{ disabled: isDisabled }" @click="submit">
<template v-if="!loading"> 发送 </template>
<template v-if="!loading">
发送
<span class="shortcut-icon" v-if="!isMobile"> {{ isMac ? '' : 'Ctrl' }} </span>
</template>
<template v-else> <loading-four /> 发送中... </template>
</div>
</div>
@@ -21,6 +24,8 @@ import {
getEditorTheme as getEditorThemeUtil,
getPreviewTheme as getPreviewThemeUtil,
} from '~/utils/vditor'
import { useIsMobile } from '~/utils/screen'
import { isMac } from '~/utils/device'
import '~/assets/global.css'
export default {
@@ -44,6 +49,7 @@ export default {
const vditorInstance = ref(null)
const text = ref('')
const editorId = ref(props.editorId)
const isMobile = useIsMobile()
if (!editorId.value) {
editorId.value = 'editor-' + useId()
}
@@ -84,6 +90,28 @@ export default {
applyTheme()
},
})
// 不是手机的情况下不添加快捷键
if (!isMobile.value) {
// 添加快捷键监听 (Ctrl+Enter 或 Cmd+Enter)
const handleKeydown = (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault()
submit()
}
}
const el = document.getElementById(editorId.value)
if (el) {
el.addEventListener('keydown', handleKeydown)
}
onUnmounted(() => {
if (el) {
el.removeEventListener('keydown', handleKeydown)
}
})
}
})
onUnmounted(() => {
@@ -121,7 +149,7 @@ export default {
},
)
return { submit, isDisabled, editorId }
return { submit, isDisabled, editorId, isMac, isMobile }
},
}
</script>
@@ -168,4 +196,17 @@ export default {
.message-submit:not(.disabled):hover {
background-color: var(--primary-color-hover);
}
/** 评论按钮快捷键样式 */
.shortcut-icon {
padding: 2px 6px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
line-height: 1.2;
background-color: rgba(0, 0, 0, 0.25);
}
.comment-submit.disabled .shortcut-icon {
background-color: rgba(0, 0, 0, 0);
}
</style>

View File

@@ -0,0 +1,90 @@
/**
* 文件上传配置
*/
export const UPLOAD_CONFIG = {
// 视频文件配置
VIDEO: {
// 文件大小限制 (字节)
MAX_SIZE: 20 * 1024 * 1024,
// 支持的输入格式
SUPPORTED_FORMATS: ['mp4', 'webm', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'm4v', 'ogv'],
},
// 图片文件配置
IMAGE: {
MAX_SIZE: 5 * 1024 * 1024, // 5MB
TARGET_SIZE: 5 * 1024 * 1024, // 5MB
SUPPORTED_FORMATS: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp'],
},
// 音频文件配置
AUDIO: {
MAX_SIZE: 5 * 1024 * 1024, // 5MB
TARGET_SIZE: 5 * 1024 * 1024, // 5MB
SUPPORTED_FORMATS: ['mp3', 'wav', 'ogg', 'aac', 'm4a'],
},
// 通用文件配置
GENERAL: {
MAX_SIZE: 100 * 1024 * 1024, // 100MB
CHUNK_SIZE: 5 * 1024 * 1024, // 5MB 分片大小
},
// 用户体验配置
UI: {
SUCCESS_DURATION: 2000,
ERROR_DURATION: 3000,
WARNING_DURATION: 3000,
},
}
/**
* 获取文件类型配置
*/
export function getFileTypeConfig(filename) {
const ext = filename.split('.').pop().toLowerCase()
if (UPLOAD_CONFIG.VIDEO.SUPPORTED_FORMATS.includes(ext)) {
return { type: 'video', config: UPLOAD_CONFIG.VIDEO }
}
if (UPLOAD_CONFIG.IMAGE.SUPPORTED_FORMATS.includes(ext)) {
return { type: 'image', config: UPLOAD_CONFIG.IMAGE }
}
if (UPLOAD_CONFIG.AUDIO.SUPPORTED_FORMATS.includes(ext)) {
return { type: 'audio', config: UPLOAD_CONFIG.AUDIO }
}
return { type: 'general', config: UPLOAD_CONFIG.GENERAL }
}
/**
* 格式化文件大小
*/
export function formatFileSize(bytes) {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
}
/**
* 计算压缩节省的费用 (示例函数)
*/
export function calculateSavings(originalSize, compressedSize, costPerMB = 0.01) {
const originalMB = originalSize / (1024 * 1024)
const compressedMB = compressedSize / (1024 * 1024)
const savedMB = originalMB - compressedMB
const savedCost = savedMB * costPerMB
return {
savedMB: savedMB.toFixed(2),
savedCost: savedCost.toFixed(4),
originalCost: (originalMB * costPerMB).toFixed(4),
compressedCost: (compressedMB * costPerMB).toFixed(4),
}
}

View File

@@ -3,7 +3,7 @@ import { defineNuxtConfig } from 'nuxt/config'
export default defineNuxtConfig({
devServer: {
host: '0.0.0.0',
port: 3000
port: 3000,
},
ssr: true,
modules: ['@nuxt/image'],
@@ -97,26 +97,7 @@ export default defineNuxtConfig({
},
},
vite: {
build: {
// increase warning limit and split large libraries into separate chunks
// chunkSizeWarningLimit: 1024,
// rollupOptions: {
// output: {
// manualChunks(id) {
// if (id.includes('node_modules')) {
// if (id.includes('vditor')) {
// return 'vditor'
// }
// if (id.includes('echarts')) {
// return 'echarts'
// }
// if (id.includes('highlight.js')) {
// return 'highlight'
// }
// }
// },
// },
// },
},
optimizeDeps: {},
build: {},
},
})

View File

File diff suppressed because it is too large Load Diff

View File

@@ -25,6 +25,7 @@
"ldrs": "^1.0.0",
"markdown-it": "^14.1.0",
"mermaid": "^10.9.4",
"nanoid": "^5.1.5",
"nprogress": "^0.2.0",
"nuxt": "latest",
"sanitize-html": "^2.17.0",

View File

@@ -424,7 +424,8 @@ const sanitizeDescription = (text) => stripMarkdown(text)
.topic-container {
position: sticky;
top: calc(var(--header-height) + 1px);
top: var(--header-height);
padding-top: 10px;
z-index: 10;
background-color: var(--background-color-blur);
display: flex;
@@ -486,6 +487,7 @@ const sanitizeDescription = (text) => stripMarkdown(text)
align-items: center;
width: 100%;
border-bottom: 1px solid var(--normal-border-color);
transition: background-color 0.5s ease;
}
.article-item:hover {

View File

@@ -0,0 +1,28 @@
export const isClient = typeof window !== 'undefined' && typeof document !== 'undefined'
export const isMac = getIsMac()
function getIsMac() {
if (!isClient) {
return false
}
try {
// 优先使用现代浏览器的 navigator.userAgentData API
if (navigator.userAgentData && navigator.userAgentData.platform) {
return navigator.userAgentData.platform === 'macOS'
}
// 降级到传统的 User-Agent 检测
if (navigator.userAgent) {
return /Mac|iPhone|iPad|iPod/i.test(navigator.userAgent)
}
// 默认返回false
return false
} catch (error) {
// 异常处理,记录错误并返回默认值
console.warn('检测Mac设备时发生错误:', error)
return false
}
}

View File

@@ -3,6 +3,7 @@ import { getToken, authState } from './auth'
import { searchUsers, fetchFollowings, fetchAdmins } from './user'
import { tiebaEmoji } from './tiebaEmoji'
import vditorPostCitation from './vditorPostCitation.js'
import { checkFileSize, formatFileSize } from './videoCompressor.js'
export function getEditorTheme() {
return document.documentElement.dataset.theme === 'dark' ? 'dark' : 'classic'
@@ -91,7 +92,26 @@ export function createVditor(editorId, options = {}) {
multiple: false,
handler: async (files) => {
const file = files[0]
vditor.tip('图片上传中', 0)
const ext = file.name.split('.').pop().toLowerCase()
const videoExts = ['mp4', 'webm', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'm4v', 'ogv']
// 检查文件大小
const sizeCheck = checkFileSize(file)
if (!sizeCheck.isValid) {
console.log(
'文件大小不能超过',
formatFileSize(sizeCheck.maxSize),
',当前文件',
formatFileSize(sizeCheck.actualSize),
)
vditor.tip(
`文件大小不能超过 ${formatFileSize(sizeCheck.maxSize)},当前文件 ${formatFileSize(sizeCheck.actualSize)}`,
3000,
)
return '文件过大'
}
vditor.tip('文件上传中', 0)
vditor.disabled()
const res = await fetch(
`${API_BASE_URL}/api/upload/presign?filename=${encodeURIComponent(file.name)}`,
@@ -110,7 +130,6 @@ export function createVditor(editorId, options = {}) {
return '上传失败'
}
const ext = file.name.split('.').pop().toLowerCase()
const imageExts = [
'apng',
'bmp',
@@ -132,6 +151,8 @@ export function createVditor(editorId, options = {}) {
md = `![${file.name}](${info.fileUrl})`
} else if (audioExts.includes(ext)) {
md = `<audio controls="controls" src="${info.fileUrl}"></audio>`
} else if (videoExts.includes(ext)) {
md = `<video width="600" controls>\n <source src="${info.fileUrl}" type="video/${ext}">\n 你的浏览器不支持 video 标签。\n</video>`
} else {
md = `[${file.name}](${info.fileUrl})`
}

View File

@@ -0,0 +1,30 @@
/**
* 视频上传工具
*/
import { UPLOAD_CONFIG } from '../config/uploadConfig.js'
// 导出配置供外部使用
export const VIDEO_CONFIG = UPLOAD_CONFIG.VIDEO
/**
* 检查文件大小是否超出限制
*/
export function checkFileSize(file) {
return {
isValid: file.size <= VIDEO_CONFIG.MAX_SIZE,
actualSize: file.size,
maxSize: VIDEO_CONFIG.MAX_SIZE,
}
}
/**
* 格式化文件大小显示
*/
export function formatFileSize(bytes) {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
}