mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 19:47:43 +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.
|
Information about release notes of Coco App is provided here.
|
||||||
|
|
||||||
## Latest (In development)
|
## Latest (In development)
|
||||||
### ❌ Breaking changes
|
|
||||||
### 🚀 Features
|
### ❌ Breaking changes
|
||||||
### 🐛 Bug fix
|
|
||||||
### ✈️ Improvements
|
### 🚀 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)
|
## 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
|
/// It is only for third-party extensions. Built-in extensions should always
|
||||||
/// set this field to `None`.
|
/// set this field to `None`.
|
||||||
#[serde(deserialize_with = "deserialize_coco_semver")]
|
#[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>,
|
minimum_coco_version: Option<SemVer>,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -419,6 +421,37 @@ where
|
|||||||
Ok(Some(semver))
|
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)]
|
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||||
pub(crate) struct CommandAction {
|
pub(crate) struct CommandAction {
|
||||||
pub(crate) exec: String,
|
pub(crate) exec: String,
|
||||||
@@ -2150,4 +2183,31 @@ mod tests {
|
|||||||
let deserialized: FileSystemAccess = serde_json::from_str(&serialized).unwrap();
|
let deserialized: FileSystemAccess = serde_json::from_str(&serialized).unwrap();
|
||||||
assert_eq!(original, deserialized);
|
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"
|
"info"
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
installExtensionError(String(error));
|
installExtensionError(error);
|
||||||
} finally {
|
} finally {
|
||||||
const { installingExtensions } = useSearchStore.getState();
|
const { installingExtensions } = useSearchStore.getState();
|
||||||
|
|
||||||
@@ -306,7 +306,7 @@ const ExtensionStore = ({ extensionId }: { extensionId?: string }) => {
|
|||||||
<div
|
<div
|
||||||
key={id}
|
key={id}
|
||||||
className={clsx(
|
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":
|
"bg-black/10 dark:bg-white/15":
|
||||||
selectedExtension?.id === id,
|
selectedExtension?.id === id,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const MultilevelWrapper: FC<MultilevelWrapperProps> = (props) => {
|
|||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
className={clsx(
|
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(),
|
"justify-center": visibleSearchBar(),
|
||||||
"w-[calc(100vw-16px)] rounded-r-lg": !visibleSearchBar(),
|
"w-[calc(100vw-16px)] rounded-r-lg": !visibleSearchBar(),
|
||||||
@@ -115,7 +115,7 @@ export default function SearchIcons({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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]" />
|
<Search className="w-4 h-4 text-[#ccc] dark:text-[#d8d8d8]" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import platformAdapter from "@/utils/platformAdapter";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { MAIN_WINDOW_LABEL, SETTINGS_WINDOW_LABEL } from "@/constants";
|
import { MAIN_WINDOW_LABEL, SETTINGS_WINDOW_LABEL } from "@/constants";
|
||||||
import { useAsyncEffect, useEventListener } from "ahooks";
|
import { useAsyncEffect, useEventListener } from "ahooks";
|
||||||
|
import { installExtensionError } from "@/utils";
|
||||||
|
|
||||||
export interface DeepLinkHandler {
|
export interface DeepLinkHandler {
|
||||||
pattern: string;
|
pattern: string;
|
||||||
@@ -78,7 +79,7 @@ export function useDeepLinkManager() {
|
|||||||
addError(t("deepLink.extensionInstallSuccessfully"), "info");
|
addError(t("deepLink.extensionInstallSuccessfully"), "info");
|
||||||
console.log("Extension installed successfully:", extensionId);
|
console.log("Extension installed successfully:", extensionId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addError(String(error));
|
installExtensionError(error)
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -305,10 +305,10 @@ export const visibleSearchBar = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const visibleFilterBar = () => {
|
export const visibleFilterBar = () => {
|
||||||
const { viewExtensionOpened, visibleExtensionDetail } =
|
const { viewExtensionOpened, visibleExtensionDetail, goAskAi } =
|
||||||
useSearchStore.getState();
|
useSearchStore.getState();
|
||||||
|
|
||||||
if (visibleExtensionDetail) return false;
|
if (visibleExtensionDetail || goAskAi) return false;
|
||||||
|
|
||||||
if (isNil(viewExtensionOpened)) return true;
|
if (isNil(viewExtensionOpened)) return true;
|
||||||
|
|
||||||
@@ -329,7 +329,7 @@ export const visibleFooterBar = () => {
|
|||||||
|
|
||||||
export const installExtensionError = (error: any) => {
|
export const installExtensionError = (error: any) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|
||||||
const { addError } = useAppStore.getState();
|
const { addError } = useAppStore.getState();
|
||||||
|
|
||||||
let message = "settings.extensions.hints.importFailed";
|
let message = "settings.extensions.hints.importFailed";
|
||||||
@@ -338,8 +338,7 @@ export const installExtensionError = (error: any) => {
|
|||||||
message = "settings.extensions.hints.extensionAlreadyImported";
|
message = "settings.extensions.hints.extensionAlreadyImported";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isObject(error) && "IncompatiblePlatform" in error
|
if (isObject(error) && "IncompatiblePlatform" in error) {
|
||||||
) {
|
|
||||||
message = "settings.extensions.hints.platformIncompatibleExtension";
|
message = "settings.extensions.hints.platformIncompatibleExtension";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,27 +366,30 @@ export const installExtensionError = (error: any) => {
|
|||||||
} else if ("InvalidPluginJson" in source) {
|
} else if ("InvalidPluginJson" in source) {
|
||||||
const innerSource = (source as any).InvalidPluginJson.source;
|
const innerSource = (source as any).InvalidPluginJson.source;
|
||||||
const kind = innerSource.kind;
|
const kind = innerSource.kind;
|
||||||
|
|
||||||
if (isObject(kind)) {
|
if (isObject(kind)) {
|
||||||
if ("DuplicateSubExtensionId" in kind) {
|
if ("DuplicateSubExtensionId" in kind) {
|
||||||
message = "settings.extensions.hints.duplicateSubExtensionId";
|
message = "settings.extensions.hints.duplicateSubExtensionId";
|
||||||
options = (kind as any).DuplicateSubExtensionId;
|
options = (kind as any).DuplicateSubExtensionId;
|
||||||
} else if ("FieldsNotAllowed" in kind) {
|
} else if ("FieldsNotAllowed" in kind) {
|
||||||
message = "settings.extensions.hints.fieldsNotAllowed";
|
message = "settings.extensions.hints.fieldsNotAllowed";
|
||||||
options = (kind as any).FieldsNotAllowed;
|
options = (kind as any).FieldsNotAllowed;
|
||||||
} else if ("FieldsNotAllowedForSubExtension" in kind) {
|
} else if ("FieldsNotAllowedForSubExtension" in kind) {
|
||||||
message = "settings.extensions.hints.fieldsNotAllowedForSubExtension";
|
message =
|
||||||
options = (kind as any).FieldsNotAllowedForSubExtension;
|
"settings.extensions.hints.fieldsNotAllowedForSubExtension";
|
||||||
} else if ("TypesNotAllowedForSubExtension" in kind) {
|
options = (kind as any).FieldsNotAllowedForSubExtension;
|
||||||
message = "settings.extensions.hints.typesNotAllowedForSubExtension";
|
} else if ("TypesNotAllowedForSubExtension" in kind) {
|
||||||
options = (kind as any).TypesNotAllowedForSubExtension;
|
message =
|
||||||
} else if ("SubExtensionHasMoreSupportedPlatforms" in kind) {
|
"settings.extensions.hints.typesNotAllowedForSubExtension";
|
||||||
message = "settings.extensions.hints.subExtensionHasMoreSupportedPlatforms";
|
options = (kind as any).TypesNotAllowedForSubExtension;
|
||||||
options = (kind as any).SubExtensionHasMoreSupportedPlatforms;
|
} else if ("SubExtensionHasMoreSupportedPlatforms" in kind) {
|
||||||
} else if ("FieldRequired" in kind) {
|
message =
|
||||||
message = "settings.extensions.hints.fieldRequired";
|
"settings.extensions.hints.subExtensionHasMoreSupportedPlatforms";
|
||||||
options = (kind as any).FieldRequired;
|
options = (kind as any).SubExtensionHasMoreSupportedPlatforms;
|
||||||
}
|
} else if ("FieldRequired" in kind) {
|
||||||
|
message = "settings.extensions.hints.fieldRequired";
|
||||||
|
options = (kind as any).FieldRequired;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (source === "MissingPluginJson") {
|
} else if (source === "MissingPluginJson") {
|
||||||
|
|||||||
Reference in New Issue
Block a user