Replace SCSS variable usages with explicit pixel/hex values for consistent styling across all components Fix broken template syntax including missing class spaces and incorrect closing tags Migrate constant and API imports to centralized @/constants and @/api modules Add new utility classes: IdUtils, CallbackUtils, and TimerUtils Add new chat conversation API endpoints for recent conversations and message lists Add new Discovery page components (FindTabs, QuickQuestions, CardSwiper) and their styles Update app store config to use environment variables for base API and WebSocket URLs Add new selected tab icon assets
174 lines
5.1 KiB
TypeScript
174 lines
5.1 KiB
TypeScript
export const LONG_TEXT_KEYS = {
|
|
containerType: "container_type",
|
|
tag: "tag",
|
|
title: "title",
|
|
content: "content",
|
|
contentSummary: "content_summary",
|
|
guideConclusion: "guide_conclusion",
|
|
keyFacts: "key_facts",
|
|
sceneImage: "scene_image",
|
|
checklistOrSteps: "checklist_or_steps",
|
|
bestTimeOrPeople: "best_time_or_people",
|
|
avoidPitfalls: "avoid_pitfalls",
|
|
nextSuggestion: "next_suggestion",
|
|
poiDefinition: "poi_definition",
|
|
keyHighlights: "key_highlights",
|
|
heroImage: "hero_image",
|
|
backgroundStory: "background_story",
|
|
bestTime: "best_time",
|
|
visitSuggestion: "visit_suggestion",
|
|
routeSummary: "route_summary",
|
|
routeMeta: "route_meta",
|
|
routeSteps: "route_steps",
|
|
onsiteClues: "onsite_clues",
|
|
realtimeNotice: "realtime_notice",
|
|
routeWarning: "route_warning",
|
|
arrivalNextStep: "arrival_next_step",
|
|
photoConclusion: "photo_conclusion",
|
|
photoSpots: "photo_spots",
|
|
compositionTips: "composition_tips",
|
|
phoneSettings: "phone_settings",
|
|
lightReminder: "light_reminder",
|
|
sampleImage: "sample_image",
|
|
components: "components",
|
|
checklist: "checklist",
|
|
suggest: "suggest",
|
|
commodity: "commodity",
|
|
actionZone: "action_zone",
|
|
};
|
|
|
|
export const LONG_TEXT_FIELD_CONFIG = [
|
|
{ key: LONG_TEXT_KEYS.containerType },
|
|
{ key: LONG_TEXT_KEYS.tag },
|
|
{ key: LONG_TEXT_KEYS.title },
|
|
{ key: LONG_TEXT_KEYS.content },
|
|
{ key: LONG_TEXT_KEYS.contentSummary },
|
|
{ key: LONG_TEXT_KEYS.guideConclusion },
|
|
{ key: LONG_TEXT_KEYS.keyFacts },
|
|
{ key: LONG_TEXT_KEYS.sceneImage },
|
|
{ key: LONG_TEXT_KEYS.checklistOrSteps },
|
|
{ key: LONG_TEXT_KEYS.bestTimeOrPeople },
|
|
{ key: LONG_TEXT_KEYS.avoidPitfalls },
|
|
{ key: LONG_TEXT_KEYS.nextSuggestion },
|
|
{ key: LONG_TEXT_KEYS.poiDefinition },
|
|
{ key: LONG_TEXT_KEYS.keyHighlights },
|
|
{ key: LONG_TEXT_KEYS.heroImage },
|
|
{ key: LONG_TEXT_KEYS.backgroundStory },
|
|
{ key: LONG_TEXT_KEYS.bestTime },
|
|
{ key: LONG_TEXT_KEYS.visitSuggestion },
|
|
{ key: LONG_TEXT_KEYS.routeSummary },
|
|
{ key: LONG_TEXT_KEYS.routeMeta },
|
|
{ key: LONG_TEXT_KEYS.routeSteps },
|
|
{ key: LONG_TEXT_KEYS.onsiteClues },
|
|
{ key: LONG_TEXT_KEYS.realtimeNotice },
|
|
{ key: LONG_TEXT_KEYS.routeWarning },
|
|
{ key: LONG_TEXT_KEYS.arrivalNextStep },
|
|
{ key: LONG_TEXT_KEYS.photoConclusion },
|
|
{ key: LONG_TEXT_KEYS.photoSpots },
|
|
{ key: LONG_TEXT_KEYS.compositionTips },
|
|
{ key: LONG_TEXT_KEYS.phoneSettings },
|
|
{ key: LONG_TEXT_KEYS.lightReminder },
|
|
{ key: LONG_TEXT_KEYS.sampleImage },
|
|
{ key: LONG_TEXT_KEYS.components },
|
|
{ key: LONG_TEXT_KEYS.checklist },
|
|
{ key: LONG_TEXT_KEYS.suggest },
|
|
{ key: LONG_TEXT_KEYS.commodity },
|
|
{ key: LONG_TEXT_KEYS.actionZone },
|
|
];
|
|
|
|
export const LONG_TEXT_PREVIEW_KEYS = [
|
|
LONG_TEXT_KEYS.contentSummary,
|
|
LONG_TEXT_KEYS.title,
|
|
LONG_TEXT_KEYS.tag,
|
|
];
|
|
|
|
const CONFIGURED_KEYS = LONG_TEXT_FIELD_CONFIG.map((item) => item.key);
|
|
|
|
export const createLongTextData = () => ({
|
|
values: {},
|
|
parsedValues: {},
|
|
});
|
|
|
|
const toText = (value) => {
|
|
if (value === undefined || value === null) return "";
|
|
return typeof value === "string" ? value : String(value);
|
|
};
|
|
|
|
const shouldParseJSON = (raw) => {
|
|
if (!raw || typeof raw !== "string") return false;
|
|
return /^[\s]*[\[{]/.test(raw);
|
|
};
|
|
|
|
const tryParseJSON = (raw) => {
|
|
if (!shouldParseJSON(raw)) {
|
|
return { ok: false, value: null };
|
|
}
|
|
try {
|
|
return { ok: true, value: JSON.parse(raw) };
|
|
} catch (e) {
|
|
return { ok: false, value: null };
|
|
}
|
|
};
|
|
|
|
export const appendLongTextChunk = (target, chunk = {}) => {
|
|
if (!target || !chunk.contentKey) return target;
|
|
|
|
const key = String(chunk.contentKey);
|
|
const value = toText(chunk.contentValue);
|
|
|
|
if (!target.values) target.values = {};
|
|
if (!target.parsedValues) target.parsedValues = {};
|
|
|
|
target.values[key] = (target.values[key] || "") + value;
|
|
|
|
const parsed = tryParseJSON(target.values[key]);
|
|
if (parsed.ok) {
|
|
target.parsedValues[key] = parsed.value;
|
|
} else {
|
|
delete target.parsedValues[key];
|
|
}
|
|
|
|
return target;
|
|
};
|
|
|
|
export const getLongTextValue = (data, key) => {
|
|
if (!data || !data.values || !key) return "";
|
|
return data.values[key] || "";
|
|
};
|
|
|
|
export const getLongTextParsedValue = (data, key, fallback = undefined) => {
|
|
if (!data || !data.parsedValues || !key) return fallback;
|
|
return Object.prototype.hasOwnProperty.call(data.parsedValues, key)
|
|
? data.parsedValues[key]
|
|
: fallback;
|
|
};
|
|
|
|
export const getLongTextPreviewText = (data, keys = LONG_TEXT_PREVIEW_KEYS) => {
|
|
for (const key of keys) {
|
|
const value = getLongTextValue(data, key);
|
|
if (value) return value;
|
|
}
|
|
return "";
|
|
};
|
|
|
|
export const hasLongTextExtraSections = (data, previewKeys = LONG_TEXT_PREVIEW_KEYS) => {
|
|
if (!data || !data.values) return false;
|
|
return Object.keys(data.values).some((key) => !previewKeys.includes(key));
|
|
};
|
|
|
|
export const getLongTextSections = (data) => {
|
|
if (!data || !data.values) return [];
|
|
|
|
const extraKeys = Object.keys(data.values).filter(
|
|
(key) => !CONFIGURED_KEYS.includes(key),
|
|
);
|
|
|
|
return [...CONFIGURED_KEYS, ...extraKeys]
|
|
.filter((key) => Object.prototype.hasOwnProperty.call(data.values, key))
|
|
.map((key) => ({
|
|
contentKey: key,
|
|
contentValue: getLongTextValue(data, key),
|
|
parsedValue: getLongTextParsedValue(data, key, null),
|
|
}));
|
|
};
|