mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 11:37:47 +01:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65a48efdde | ||
|
|
0613238876 | ||
|
|
501f6df473 | ||
|
|
67c8c4bdfa |
@@ -7,11 +7,20 @@ title: "Release Notes"
|
||||
|
||||
Information about release notes of Coco App is provided here.
|
||||
|
||||
## Latest (In development)
|
||||
### ❌ Breaking changes
|
||||
### 🚀 Features
|
||||
### 🐛 Bug fix
|
||||
### ✈️ Improvements
|
||||
## Latest (In development)
|
||||
|
||||
### ❌ Breaking changes
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
### 🐛 Bug fix
|
||||
|
||||
- fix: fix the abnormal input height issue #1006
|
||||
- fix: implement custom serialization for Extension.minimum_coco_version #1010
|
||||
|
||||
### ✈️ Improvements
|
||||
|
||||
- chore: show error msg (not err code) when installing exts via deeplink fails #1007
|
||||
|
||||
## 0.9.1 (2025-12-05)
|
||||
|
||||
|
||||
@@ -135,7 +135,9 @@ pub struct Extension {
|
||||
/// It is only for third-party extensions. Built-in extensions should always
|
||||
/// set this field to `None`.
|
||||
#[serde(deserialize_with = "deserialize_coco_semver")]
|
||||
#[serde(default)] // None if this field is missing
|
||||
#[serde(serialize_with = "serialize_coco_semver")]
|
||||
// None if this field is missing, required as we use custom deserilize method.
|
||||
#[serde(default)]
|
||||
minimum_coco_version: Option<SemVer>,
|
||||
|
||||
/*
|
||||
@@ -419,6 +421,37 @@ where
|
||||
Ok(Some(semver))
|
||||
}
|
||||
|
||||
/// Serialize Coco SemVer to a string.
|
||||
///
|
||||
/// For a `SemVer`, there are 2 possible input cases, guarded by `to_semver()`:
|
||||
///
|
||||
/// 1. "x.y.z" => "x.y.z"
|
||||
/// 2. "x.y.z-SNAPSHOT.2560" => "x.y.z-SNAPSHOT-2560"
|
||||
fn serialize_coco_semver<S>(version: &Option<SemVer>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match version {
|
||||
Some(v) => {
|
||||
assert!(v.build.is_empty());
|
||||
|
||||
let s = if v.pre.is_empty() {
|
||||
format!("{}.{}.{}", v.major, v.minor, v.patch)
|
||||
} else {
|
||||
format!(
|
||||
"{}.{}.{}-{}",
|
||||
v.major,
|
||||
v.minor,
|
||||
v.patch,
|
||||
v.pre.as_str().replace('.', "-")
|
||||
)
|
||||
};
|
||||
serializer.serialize_str(&s)
|
||||
}
|
||||
None => serializer.serialize_none(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||
pub(crate) struct CommandAction {
|
||||
pub(crate) exec: String,
|
||||
@@ -2150,4 +2183,31 @@ mod tests {
|
||||
let deserialized: FileSystemAccess = serde_json::from_str(&serialized).unwrap();
|
||||
assert_eq!(original, deserialized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize_coco_semver_none() {
|
||||
let version: Option<SemVer> = None;
|
||||
let mut serializer = serde_json::Serializer::new(Vec::new());
|
||||
serialize_coco_semver(&version, &mut serializer).unwrap();
|
||||
let serialized = String::from_utf8(serializer.into_inner()).unwrap();
|
||||
assert_eq!(serialized, "null");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize_coco_semver_simple() {
|
||||
let version: Option<SemVer> = Some(SemVer::parse("1.2.3").unwrap());
|
||||
let mut serializer = serde_json::Serializer::new(Vec::new());
|
||||
serialize_coco_semver(&version, &mut serializer).unwrap();
|
||||
let serialized = String::from_utf8(serializer.into_inner()).unwrap();
|
||||
assert_eq!(serialized, "\"1.2.3\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize_coco_semver_with_pre() {
|
||||
let version: Option<SemVer> = Some(SemVer::parse("1.2.3-SNAPSHOT.1234").unwrap());
|
||||
let mut serializer = serde_json::Serializer::new(Vec::new());
|
||||
serialize_coco_semver(&version, &mut serializer).unwrap();
|
||||
let serialized = String::from_utf8(serializer.into_inner()).unwrap();
|
||||
assert_eq!(serialized, "\"1.2.3-SNAPSHOT-1234\"");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ const ExtensionStore = ({ extensionId }: { extensionId?: string }) => {
|
||||
"info"
|
||||
);
|
||||
} catch (error) {
|
||||
installExtensionError(String(error));
|
||||
installExtensionError(error);
|
||||
} finally {
|
||||
const { installingExtensions } = useSearchStore.getState();
|
||||
|
||||
@@ -306,7 +306,7 @@ const ExtensionStore = ({ extensionId }: { extensionId?: string }) => {
|
||||
<div
|
||||
key={id}
|
||||
className={clsx(
|
||||
"flex justify-between gap-4 h-[40px] px-2 rounded-lg cursor-pointer text-[#333] dark:text-[#d8d8d8] transition",
|
||||
"flex justify-between gap-4 h-10 px-2 rounded-lg cursor-pointer text-[#333] dark:text-[#d8d8d8] transition",
|
||||
{
|
||||
"bg-black/10 dark:bg-white/15":
|
||||
selectedExtension?.id === id,
|
||||
|
||||
@@ -36,7 +36,7 @@ const MultilevelWrapper: FC<MultilevelWrapperProps> = (props) => {
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className={clsx(
|
||||
"flex items-center h-[40px] gap-1 px-2 border border-[#EDEDED] dark:border-[#202126] rounded-l-lg",
|
||||
"flex items-center h-10 gap-1 px-2 border border-[#EDEDED] dark:border-[#202126] rounded-l-lg",
|
||||
{
|
||||
"justify-center": visibleSearchBar(),
|
||||
"w-[calc(100vw-16px)] rounded-r-lg": !visibleSearchBar(),
|
||||
@@ -115,7 +115,7 @@ export default function SearchIcons({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center pl-2 h-[40px] bg-[#ededed] dark:bg-[#202126]">
|
||||
<div className="flex items-center justify-center pl-2 h-10 bg-[#ededed] dark:bg-[#202126]">
|
||||
<Search className="w-4 h-4 text-[#ccc] dark:text-[#d8d8d8]" />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ import platformAdapter from "@/utils/platformAdapter";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MAIN_WINDOW_LABEL, SETTINGS_WINDOW_LABEL } from "@/constants";
|
||||
import { useAsyncEffect, useEventListener } from "ahooks";
|
||||
import { installExtensionError } from "@/utils";
|
||||
|
||||
export interface DeepLinkHandler {
|
||||
pattern: string;
|
||||
@@ -78,7 +79,7 @@ export function useDeepLinkManager() {
|
||||
addError(t("deepLink.extensionInstallSuccessfully"), "info");
|
||||
console.log("Extension installed successfully:", extensionId);
|
||||
} catch (error) {
|
||||
addError(String(error));
|
||||
installExtensionError(error)
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -305,10 +305,10 @@ export const visibleSearchBar = () => {
|
||||
};
|
||||
|
||||
export const visibleFilterBar = () => {
|
||||
const { viewExtensionOpened, visibleExtensionDetail } =
|
||||
const { viewExtensionOpened, visibleExtensionDetail, goAskAi } =
|
||||
useSearchStore.getState();
|
||||
|
||||
if (visibleExtensionDetail) return false;
|
||||
if (visibleExtensionDetail || goAskAi) return false;
|
||||
|
||||
if (isNil(viewExtensionOpened)) return true;
|
||||
|
||||
@@ -329,7 +329,7 @@ export const visibleFooterBar = () => {
|
||||
|
||||
export const installExtensionError = (error: any) => {
|
||||
console.log(error);
|
||||
|
||||
|
||||
const { addError } = useAppStore.getState();
|
||||
|
||||
let message = "settings.extensions.hints.importFailed";
|
||||
@@ -338,8 +338,7 @@ export const installExtensionError = (error: any) => {
|
||||
message = "settings.extensions.hints.extensionAlreadyImported";
|
||||
}
|
||||
|
||||
if ( isObject(error) && "IncompatiblePlatform" in error
|
||||
) {
|
||||
if (isObject(error) && "IncompatiblePlatform" in error) {
|
||||
message = "settings.extensions.hints.platformIncompatibleExtension";
|
||||
}
|
||||
|
||||
@@ -367,27 +366,30 @@ export const installExtensionError = (error: any) => {
|
||||
} else if ("InvalidPluginJson" in source) {
|
||||
const innerSource = (source as any).InvalidPluginJson.source;
|
||||
const kind = innerSource.kind;
|
||||
|
||||
|
||||
if (isObject(kind)) {
|
||||
if ("DuplicateSubExtensionId" in kind) {
|
||||
message = "settings.extensions.hints.duplicateSubExtensionId";
|
||||
options = (kind as any).DuplicateSubExtensionId;
|
||||
} else if ("FieldsNotAllowed" in kind) {
|
||||
message = "settings.extensions.hints.fieldsNotAllowed";
|
||||
options = (kind as any).FieldsNotAllowed;
|
||||
} else if ("FieldsNotAllowedForSubExtension" in kind) {
|
||||
message = "settings.extensions.hints.fieldsNotAllowedForSubExtension";
|
||||
options = (kind as any).FieldsNotAllowedForSubExtension;
|
||||
} else if ("TypesNotAllowedForSubExtension" in kind) {
|
||||
message = "settings.extensions.hints.typesNotAllowedForSubExtension";
|
||||
options = (kind as any).TypesNotAllowedForSubExtension;
|
||||
} else if ("SubExtensionHasMoreSupportedPlatforms" in kind) {
|
||||
message = "settings.extensions.hints.subExtensionHasMoreSupportedPlatforms";
|
||||
options = (kind as any).SubExtensionHasMoreSupportedPlatforms;
|
||||
} else if ("FieldRequired" in kind) {
|
||||
message = "settings.extensions.hints.fieldRequired";
|
||||
options = (kind as any).FieldRequired;
|
||||
}
|
||||
if ("DuplicateSubExtensionId" in kind) {
|
||||
message = "settings.extensions.hints.duplicateSubExtensionId";
|
||||
options = (kind as any).DuplicateSubExtensionId;
|
||||
} else if ("FieldsNotAllowed" in kind) {
|
||||
message = "settings.extensions.hints.fieldsNotAllowed";
|
||||
options = (kind as any).FieldsNotAllowed;
|
||||
} else if ("FieldsNotAllowedForSubExtension" in kind) {
|
||||
message =
|
||||
"settings.extensions.hints.fieldsNotAllowedForSubExtension";
|
||||
options = (kind as any).FieldsNotAllowedForSubExtension;
|
||||
} else if ("TypesNotAllowedForSubExtension" in kind) {
|
||||
message =
|
||||
"settings.extensions.hints.typesNotAllowedForSubExtension";
|
||||
options = (kind as any).TypesNotAllowedForSubExtension;
|
||||
} else if ("SubExtensionHasMoreSupportedPlatforms" in kind) {
|
||||
message =
|
||||
"settings.extensions.hints.subExtensionHasMoreSupportedPlatforms";
|
||||
options = (kind as any).SubExtensionHasMoreSupportedPlatforms;
|
||||
} else if ("FieldRequired" in kind) {
|
||||
message = "settings.extensions.hints.fieldRequired";
|
||||
options = (kind as any).FieldRequired;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (source === "MissingPluginJson") {
|
||||
|
||||
Reference in New Issue
Block a user