三件欠账一次清:
1. 图片直链签名化
- 新增 `backend/app/services/signing.py`:HMAC-SHA256,密钥由 `JWT_SECRET` 域分离派生
(`attachment-raw-v1`),载荷 `workspace_id:attachment_id:exp`,默认 30 分钟
- `/raw` 支持 `?exp=&sig=`,不需要登录态;签名里绑了工作区,跨租户链接直接拒
- 附件对象多一个 `raw_url`,前端每次渲染现签,正文里只存不带凭据的 `/raw`
- 保留旧 `?token=` 通道,给未升级的客户端过渡
2. 安卓富文本编辑器
- `ui/RichTextEditorScreen.kt`:Markdown 工具栏(粗体/斜体/标题/列表/引用/代码/链接/图片),
选区包裹与行首前缀都按光标位置来;贴图走附件接口,插入签名直链
- 预览用 Markwon,图片用 Coil;正文落在一张白纸卡片里,不再贴屏幕边
- 从任务详情与知识点都能进,保存按 `body_md` 回写(PATCH 带 version)
3. 表格
- 只读渲染走 markdown-it 的 GFM 表,`display:block + overflow-x:auto`:
宽表横向滚动、窄表铺满,都不撑破抽屉
验证:pytest 74 passed;richtext 15 / smoke 7 / tickets 8 / knowledge 7 全绿;
tsc -b 0 错;vite build 通过(主包 299.42 kB / gzip 74.96 kB);
安卓 assembleDebug 成功并在 API 35 模拟器走通编辑与预览。
仍欠:保存后回列表而非回详情抽屉、图片压缩与缩略图、HTML 片段按文本显示。
This commit is contained in:
@@ -19,12 +19,15 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import com.dadao.task.data.Api
|
||||
import com.dadao.task.data.TokenStore
|
||||
import com.dadao.task.data.Workspace
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import com.dadao.task.ui.BoardScreen
|
||||
import com.dadao.task.ui.DadaoTheme
|
||||
import com.dadao.task.ui.EditRequest
|
||||
import com.dadao.task.ui.HomeScreen
|
||||
import com.dadao.task.ui.KnowledgeScreen
|
||||
import com.dadao.task.ui.LoginScreen
|
||||
import com.dadao.task.ui.MembersScreen
|
||||
import com.dadao.task.ui.RichTextEditorScreen
|
||||
import com.dadao.task.ui.TaskListScreen
|
||||
import com.dadao.task.ui.TicketsScreen
|
||||
import com.dadao.task.ui.WorkspaceScaffold
|
||||
@@ -68,6 +71,8 @@ private fun AppRoot(joinUri: String?) {
|
||||
var current by remember { mutableStateOf<Workspace?>(null) }
|
||||
var tab by remember { mutableStateOf(WorkspaceTab.Tasks) }
|
||||
var unread by remember { mutableStateOf(0) }
|
||||
// 打开富文本编辑器时非空;关掉就恢复原来的界面
|
||||
var editing by remember { mutableStateOf<EditRequest?>(null) }
|
||||
|
||||
// 有本地令牌就先静默校验一次:令牌过期/被吊销要退回登录页,而不是进了主页再崩。
|
||||
LaunchedEffect(screen) {
|
||||
@@ -122,18 +127,62 @@ private fun AppRoot(joinUri: String?) {
|
||||
if (ws == null) {
|
||||
screen = Screen.Home
|
||||
} else {
|
||||
WorkspaceScaffold(
|
||||
workspace = ws,
|
||||
tab = tab,
|
||||
unread = unread,
|
||||
onTab = { tab = it },
|
||||
onBackHome = { screen = Screen.Home },
|
||||
) {
|
||||
when (tab) {
|
||||
WorkspaceTab.Tasks -> TaskListScreen(api = api, workspace = ws)
|
||||
WorkspaceTab.Tickets -> TicketsScreen(api = api, workspace = ws)
|
||||
WorkspaceTab.Knowledge -> KnowledgeScreen(api = api, workspace = ws)
|
||||
WorkspaceTab.Members -> MembersScreen(api = api, workspace = ws)
|
||||
Box {
|
||||
WorkspaceScaffold(
|
||||
workspace = ws,
|
||||
tab = tab,
|
||||
unread = unread,
|
||||
onTab = { tab = it },
|
||||
onBackHome = { screen = Screen.Home },
|
||||
) {
|
||||
when (tab) {
|
||||
WorkspaceTab.Tasks -> TaskListScreen(
|
||||
api = api,
|
||||
workspace = ws,
|
||||
onEdit = { editing = it },
|
||||
)
|
||||
|
||||
WorkspaceTab.Tickets -> TicketsScreen(api = api, workspace = ws)
|
||||
WorkspaceTab.Knowledge -> KnowledgeScreen(
|
||||
api = api,
|
||||
workspace = ws,
|
||||
onEdit = { editing = it },
|
||||
)
|
||||
|
||||
WorkspaceTab.Members -> MembersScreen(api = api, workspace = ws)
|
||||
}
|
||||
}
|
||||
|
||||
val request = editing
|
||||
if (request != null) {
|
||||
RichTextEditorScreen(
|
||||
api = api,
|
||||
workspaceId = ws.id,
|
||||
itemId = request.itemId,
|
||||
initialTitle = request.title,
|
||||
initialBody = request.body,
|
||||
titleEnabled = request.titleEnabled,
|
||||
save = { title, body ->
|
||||
if (request.knowledge) {
|
||||
api.updateKnowledge(
|
||||
workspaceId = ws.id,
|
||||
itemId = request.itemId,
|
||||
version = request.version,
|
||||
title = title,
|
||||
body = body,
|
||||
)
|
||||
} else {
|
||||
api.updateItem(
|
||||
workspaceId = ws.id,
|
||||
itemId = request.itemId,
|
||||
version = request.version,
|
||||
title = title,
|
||||
body = body,
|
||||
)
|
||||
}
|
||||
},
|
||||
onClose = { editing = null },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user