Compact imported single-thread conversation copy

This commit is contained in:
kris
2026-04-07 12:49:53 +08:00
parent c5223c7c16
commit a43bb92f3c
4 changed files with 122 additions and 8 deletions

View File

@@ -59,7 +59,9 @@ public final class WechatSurfaceMapper {
JSONObject avatar = source.optJSONObject("avatar");
boolean isGroup = source.optBoolean("isGroup", groupAvatarMembers.size() > 1);
String conversationType = source.optString("conversationType", "");
String threadTitle = source.optString("threadTitle", source.optString("title", source.optString("projectTitle", "")));
String threadTitle = trimLocalWorkspacePrefix(
source.optString("threadTitle", source.optString("title", source.optString("projectTitle", "")))
);
if ("folder_archive".equals(conversationType)) {
threadTitle = source.optString(
"projectTitle",
@@ -70,7 +72,7 @@ public final class WechatSurfaceMapper {
return new ConversationRow(
threadTitle,
source.optString("folderLabel", ""),
source.optString("lastMessagePreview", source.optString("preview", "")),
compactImportedThreadPreview(source.optString("lastMessagePreview", source.optString("preview", ""))),
source.optString("timeLabel", source.optString("latestReplyLabel", "")),
source.optInt("unreadCount", 0),
pinnedLabel,
@@ -408,7 +410,7 @@ public final class WechatSurfaceMapper {
);
putIfNotEmpty(folder, "projectTitle", projectTitle);
putIfNotEmpty(folder, "threadTitle", projectTitle);
String recentThread = latest.optString("threadTitle", "").trim();
String recentThread = trimLocalWorkspacePrefix(latest.optString("threadTitle", "").trim());
putIfNotEmpty(
folder,
"folderLabel",
@@ -422,11 +424,16 @@ public final class WechatSurfaceMapper {
safePut(folder, "searchAliases", searchAliases);
safePut(folder, "searchTargetProjectIds", searchTargetProjectIds);
}
putIfNotEmpty(folder, "preview", firstNonBlank(latest.optString("preview", ""), latest.optString("lastMessagePreview", "")));
String compactPreview = compactImportedThreadPreview(
firstNonBlank(latest.optString("preview", ""), latest.optString("lastMessagePreview", ""))
);
putIfNotEmpty(folder, "preview", compactPreview);
putIfNotEmpty(
folder,
"lastMessagePreview",
firstNonBlank(latest.optString("lastMessagePreview", ""), latest.optString("preview", ""))
compactImportedThreadPreview(
firstNonBlank(latest.optString("lastMessagePreview", ""), latest.optString("preview", ""))
)
);
safePut(folder, "activityIconCount", Math.max(0, Math.min(4, sumInt(items, "activityIconCount"))));
if (hasPinnedLabel(items)) {
@@ -637,6 +644,26 @@ public final class WechatSurfaceMapper {
}
}
private static String compactImportedThreadPreview(String value) {
String preview = value == null ? "" : value.trim();
if (preview.matches("^已从设备.+导入线程《.+》[。.]?$")) {
return "已导入线程";
}
return preview;
}
private static String trimLocalWorkspacePrefix(String value) {
String label = value == null ? "" : value.trim();
if (label.isEmpty()) {
return "";
}
String normalized = label.replace('\\', '/');
return normalized
.replaceFirst("^/Users/[^/]+/code/", "")
.replaceFirst("^/home/[^/]+/code/", "")
.replaceFirst("^[A-Za-z]:/Users/[^/]+/code/", "");
}
private static String firstNonBlank(String... values) {
if (values == null) {
return "";

View File

@@ -1,6 +1,9 @@
package com.hyzq.boss;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -12,6 +15,8 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 34)
public class WechatSurfaceMapperTest {
@Test
public void toConversationRow_mapsWechatConversationFieldsFromThreadPayload() throws Exception {
@@ -79,6 +84,59 @@ public class WechatSurfaceMapperTest {
assertEquals("", row.avatarPrimary);
}
@Test
public void normalizeConversationHomeFeed_compactsImportedFolderPreviewAndTrimsWorkspacePrefix() throws Exception {
JSONArray source = new JSONArray()
.put(new JSONObject()
.put("conversationType", "single_device")
.put("projectId", "wenshen-thread-1")
.put("folderKey", "mac-studio:/users/kris/code/wenshenapp")
.put("projectTitle", "wenshenapp")
.put("threadTitle", "/Users/kris/code/wenshenapp/docs/superpowers/specs/2026-04-06-context-cleanup-design.md")
.put("folderLabel", "wenshenapp")
.put("preview", "已从设备 Mac Studio 导入线程《wenshenapp · 019d60》。")
.put("lastMessagePreview", "已从设备 Mac Studio 导入线程《wenshenapp · 019d60》。")
.put("latestReplyAt", "2026-04-06T17:35:00+08:00")
.put("latestReplyLabel", "17:35"))
.put(new JSONObject()
.put("conversationType", "single_device")
.put("projectId", "wenshen-thread-2")
.put("folderKey", "mac-studio:/users/kris/code/wenshenapp")
.put("projectTitle", "wenshenapp")
.put("threadTitle", "补齐 Android 会话可读性")
.put("folderLabel", "wenshenapp")
.put("preview", "最近消息:补齐 Android 会话可读性")
.put("lastMessagePreview", "最近消息:补齐 Android 会话可读性")
.put("latestReplyAt", "2026-04-06T16:30:00+08:00")
.put("latestReplyLabel", "16:30"));
JSONArray normalized = WechatSurfaceMapper.normalizeConversationHomeFeed(source);
JSONObject folder = normalized.optJSONObject(0);
assertNotNull(folder);
assertEquals("folder_archive", folder.optString("conversationType", ""));
assertEquals("2 个线程 · 最近wenshenapp/docs/superpowers/specs/2026-04-06-context-cleanup-design.md", folder.optString("folderLabel", ""));
assertEquals("已导入线程", folder.optString("preview", ""));
assertEquals("已导入线程", folder.optString("lastMessagePreview", ""));
}
@Test
public void toConversationRow_compactsImportedPreviewAndTrimsWorkspacePrefixForSingleThread() throws Exception {
JSONObject item = new JSONObject()
.put("conversationType", "single_device")
.put("projectTitle", "zhanglaoshi")
.put("threadTitle", "/Users/kris/code/zhanglaoshi/docs/superpowers/specs/2026-04-06-single-thread-cleanup-design.md")
.put("folderLabel", "zhanglaoshi")
.put("preview", "已从设备 Mac Studio 导入线程《zhanglaoshi · 019d61》。")
.put("lastMessagePreview", "已从设备 Mac Studio 导入线程《zhanglaoshi · 019d61》。")
.put("latestReplyLabel", "17:35");
WechatSurfaceMapper.ConversationRow row = WechatSurfaceMapper.toConversationRow(item);
assertEquals("zhanglaoshi/docs/superpowers/specs/2026-04-06-single-thread-cleanup-design.md", row.threadTitle);
assertEquals("已导入线程", row.lastMessagePreview);
}
@Test
public void toDeviceRow_mapsLegacyWechatThreeLineSummary() throws Exception {
JSONObject item = new StubJSONObject()