mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-24 22:09:08 +08:00
feat(core): web-clipper integration entrance (#11883)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a new "Web Clipper" integration, allowing users to import web pages into AFFiNE via a direct link to the Chrome Web Store. - **Improvements** - Integration cards can now display as clickable links or settings panels, depending on the integration. - **Localization** - Added English translations for the "Web Clipper" integration name and description. - **Style** - Improved card styling to ensure consistent text color, including for visited links. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
+6
@@ -13,6 +13,12 @@ export const card = style({
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
background: cssVarV2.layer.background.overlayPanel,
|
background: cssVarV2.layer.background.overlayPanel,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
color: 'unset',
|
||||||
|
selectors: {
|
||||||
|
'&:visited': {
|
||||||
|
color: 'unset',
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
export const cardHeader = style({
|
export const cardHeader = style({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|||||||
+15
-2
@@ -14,9 +14,22 @@ import {
|
|||||||
|
|
||||||
export const IntegrationCard = ({
|
export const IntegrationCard = ({
|
||||||
className,
|
className,
|
||||||
|
link,
|
||||||
...props
|
...props
|
||||||
}: HTMLAttributes<HTMLDivElement>) => {
|
}: HTMLAttributes<HTMLElement> & {
|
||||||
return <div className={clsx(className, card)} {...props} />;
|
link?: string;
|
||||||
|
}) => {
|
||||||
|
return link ? (
|
||||||
|
<a
|
||||||
|
className={clsx(className, card)}
|
||||||
|
{...props}
|
||||||
|
href={link}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className={clsx(className, card)} {...props} />
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IntegrationCardIcon = ({
|
export const IntegrationCardIcon = ({
|
||||||
|
|||||||
+17
-4
@@ -1,20 +1,26 @@
|
|||||||
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
import { IntegrationTypeIcon } from '@affine/core/modules/integration';
|
import { IntegrationTypeIcon } from '@affine/core/modules/integration';
|
||||||
import type { I18nString } from '@affine/i18n';
|
import type { I18nString } from '@affine/i18n';
|
||||||
import { TodayIcon } from '@blocksuite/icons/rc';
|
import { Logo1Icon, TodayIcon } from '@blocksuite/icons/rc';
|
||||||
import { LiveData } from '@toeverything/infra';
|
import { LiveData } from '@toeverything/infra';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
import { CalendarSettingPanel } from './calendar/setting-panel';
|
import { CalendarSettingPanel } from './calendar/setting-panel';
|
||||||
import { ReadwiseSettingPanel } from './readwise/setting-panel';
|
import { ReadwiseSettingPanel } from './readwise/setting-panel';
|
||||||
|
|
||||||
interface IntegrationCard {
|
type IntegrationCard = {
|
||||||
id: string;
|
id: string;
|
||||||
name: I18nString;
|
name: I18nString;
|
||||||
desc: I18nString;
|
desc: I18nString;
|
||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
setting: ReactNode;
|
} & (
|
||||||
}
|
| {
|
||||||
|
setting: ReactNode;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
link: string;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const INTEGRATION_LIST = [
|
const INTEGRATION_LIST = [
|
||||||
{
|
{
|
||||||
@@ -31,6 +37,13 @@ const INTEGRATION_LIST = [
|
|||||||
icon: <TodayIcon />,
|
icon: <TodayIcon />,
|
||||||
setting: <CalendarSettingPanel />,
|
setting: <CalendarSettingPanel />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'web-clipper' as const,
|
||||||
|
name: 'com.affine.integration.web-clipper.name',
|
||||||
|
desc: 'com.affine.integration.web-clipper.desc',
|
||||||
|
icon: <Logo1Icon />,
|
||||||
|
link: 'https://chromewebstore.google.com/detail/affine-web-clipper/mpbbkmbdpleomiogkbkkpfoljjpahmoi',
|
||||||
|
},
|
||||||
] satisfies (IntegrationCard | false)[];
|
] satisfies (IntegrationCard | false)[];
|
||||||
|
|
||||||
type IntegrationId = Exclude<
|
type IntegrationId = Exclude<
|
||||||
|
|||||||
+21
-8
@@ -2,7 +2,7 @@ import { SettingHeader } from '@affine/component/setting-components';
|
|||||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { type ReactNode, useMemo, useState } from 'react';
|
import { type ReactNode, useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { SubPageProvider, useSubPageIsland } from '../../sub-page';
|
import { SubPageProvider, useSubPageIsland } from '../../sub-page';
|
||||||
import {
|
import {
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
IntegrationCardHeader,
|
IntegrationCardHeader,
|
||||||
} from './card';
|
} from './card';
|
||||||
import { getAllowedIntegrationList$ } from './constants';
|
import { getAllowedIntegrationList$ } from './constants';
|
||||||
|
import { type IntegrationItem } from './constants';
|
||||||
import { list } from './index.css';
|
import { list } from './index.css';
|
||||||
|
|
||||||
export const IntegrationSetting = () => {
|
export const IntegrationSetting = () => {
|
||||||
@@ -25,6 +26,12 @@ export const IntegrationSetting = () => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleCardClick = useCallback((card: IntegrationItem) => {
|
||||||
|
if ('setting' in card && card.setting) {
|
||||||
|
setOpened(card.id);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SettingHeader
|
<SettingHeader
|
||||||
@@ -49,16 +56,22 @@ export const IntegrationSetting = () => {
|
|||||||
: t[item.desc.i18nKey]();
|
: t[item.desc.i18nKey]();
|
||||||
return (
|
return (
|
||||||
<li key={item.id}>
|
<li key={item.id}>
|
||||||
<IntegrationCard onClick={() => setOpened(item.id)}>
|
<IntegrationCard
|
||||||
|
onClick={() => handleCardClick(item)}
|
||||||
|
link={'link' in item ? item.link : undefined}
|
||||||
|
>
|
||||||
<IntegrationCardHeader icon={item.icon} title={title} />
|
<IntegrationCardHeader icon={item.icon} title={title} />
|
||||||
<IntegrationCardContent desc={desc} />
|
<IntegrationCardContent desc={desc} />
|
||||||
</IntegrationCard>
|
</IntegrationCard>
|
||||||
<IntegrationSettingPage
|
|
||||||
open={opened === item.id}
|
{'setting' in item && item.setting ? (
|
||||||
onClose={() => setOpened(null)}
|
<IntegrationSettingPage
|
||||||
>
|
open={opened === item.id}
|
||||||
{item.setting}
|
onClose={() => setOpened(null)}
|
||||||
</IntegrationSettingPage>
|
>
|
||||||
|
{item.setting}
|
||||||
|
</IntegrationSettingPage>
|
||||||
|
) : null}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -7415,6 +7415,14 @@ export function useAFFiNEI18N(): {
|
|||||||
* `Integrations`
|
* `Integrations`
|
||||||
*/
|
*/
|
||||||
["com.affine.integration.integrations"](): string;
|
["com.affine.integration.integrations"](): string;
|
||||||
|
/**
|
||||||
|
* `Web Clipper`
|
||||||
|
*/
|
||||||
|
["com.affine.integration.web-clipper.name"](): string;
|
||||||
|
/**
|
||||||
|
* `Import web pages to AFFiNE`
|
||||||
|
*/
|
||||||
|
["com.affine.integration.web-clipper.desc"](): string;
|
||||||
/**
|
/**
|
||||||
* `Elevate your AFFiNE experience with diverse add-ons and seamless integrations.`
|
* `Elevate your AFFiNE experience with diverse add-ons and seamless integrations.`
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1854,6 +1854,8 @@
|
|||||||
"com.affine.failed-to-send-request.description": "Unable to process your request to join <1/> <2>{{workspaceName}}</2> with <3>{{userEmail}}</3>, the workspace has reached its member limit. Please contact the workspace owner for available seats.",
|
"com.affine.failed-to-send-request.description": "Unable to process your request to join <1/> <2>{{workspaceName}}</2> with <3>{{userEmail}}</3>, the workspace has reached its member limit. Please contact the workspace owner for available seats.",
|
||||||
"com.affine.integration.name.readwise": "Readwise",
|
"com.affine.integration.name.readwise": "Readwise",
|
||||||
"com.affine.integration.integrations": "Integrations",
|
"com.affine.integration.integrations": "Integrations",
|
||||||
|
"com.affine.integration.web-clipper.name": "Web Clipper",
|
||||||
|
"com.affine.integration.web-clipper.desc": "Import web pages to AFFiNE",
|
||||||
"com.affine.integration.setting.description": "Elevate your AFFiNE experience with diverse add-ons and seamless integrations.",
|
"com.affine.integration.setting.description": "Elevate your AFFiNE experience with diverse add-ons and seamless integrations.",
|
||||||
"com.affine.integration.setting.learn": "Learn how to develop a integration for AFFiNE",
|
"com.affine.integration.setting.learn": "Learn how to develop a integration for AFFiNE",
|
||||||
"com.affine.integration.readwise.name": "Readwise",
|
"com.affine.integration.readwise.name": "Readwise",
|
||||||
|
|||||||
Reference in New Issue
Block a user