mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
feat(core): track for integration (#11128)
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
getTokenLink,
|
||||
inputErrorMsg,
|
||||
} from './index.css';
|
||||
import { readwiseTrack } from './track';
|
||||
|
||||
const ConnectDialog = ({
|
||||
onClose,
|
||||
@@ -48,6 +49,9 @@ const ConnectDialog = ({
|
||||
|
||||
const handleResult = useCallback(
|
||||
(success: boolean, token: string) => {
|
||||
readwiseTrack.connectIntegration({
|
||||
result: success ? 'success' : 'failed',
|
||||
});
|
||||
if (success) {
|
||||
onSuccess(token);
|
||||
} else {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useCallback, useState } from 'react';
|
||||
|
||||
import * as styles from './connected.css';
|
||||
import { actionButton } from './index.css';
|
||||
import { readwiseTrack } from './track';
|
||||
|
||||
export const DisconnectDialog = ({ onClose }: { onClose: () => void }) => {
|
||||
const t = useI18n();
|
||||
@@ -21,11 +22,13 @@ export const DisconnectDialog = ({ onClose }: { onClose: () => void }) => {
|
||||
const handleCancel = useCallback(() => onClose(), [onClose]);
|
||||
const handleKeep = useCallback(() => {
|
||||
readwise.disconnect();
|
||||
readwiseTrack.disconnectIntegration({ method: 'keep' });
|
||||
onClose();
|
||||
}, [onClose, readwise]);
|
||||
const handleDelete = useAsyncCallback(async () => {
|
||||
await readwise.deleteAll();
|
||||
readwise.disconnect();
|
||||
readwiseTrack.disconnectIntegration({ method: 'delete' });
|
||||
onClose();
|
||||
}, [onClose, readwise]);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { InformationFillDuotoneIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
type ChangeEvent,
|
||||
type Dispatch,
|
||||
forwardRef,
|
||||
type SetStateAction,
|
||||
@@ -26,6 +27,7 @@ import {
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
|
||||
import * as styles from './import-dialog.css';
|
||||
import { readwiseTrack } from './track';
|
||||
|
||||
export const ImportDialog = ({ onClose }: { onClose: () => void }) => {
|
||||
const t = useI18n();
|
||||
@@ -54,6 +56,13 @@ export const ImportDialog = ({ onClose }: { onClose: () => void }) => {
|
||||
);
|
||||
const handleConfirmImport = useCallback(
|
||||
(ids: string[]) => {
|
||||
readwiseTrack.confirmIntegrationImport({
|
||||
control: 'Readwise import list',
|
||||
method: readwise.setting$('lastImportedAt').value
|
||||
? 'withtimestamp'
|
||||
: 'new',
|
||||
});
|
||||
|
||||
if (ids.length === 0) {
|
||||
onClose();
|
||||
return;
|
||||
@@ -65,15 +74,26 @@ export const ImportDialog = ({ onClose }: { onClose: () => void }) => {
|
||||
abortControllerRef.current = abortController;
|
||||
const signal = abortController.signal;
|
||||
|
||||
const startTime = Date.now();
|
||||
readwise
|
||||
.highlightsToAffineDocs(selectedHighlights.reverse(), books, {
|
||||
signal,
|
||||
onProgress: setImportProgress,
|
||||
onComplete: () => {
|
||||
readwiseTrack.completeIntegrationImport({
|
||||
total: selectedHighlights.length,
|
||||
done: selectedHighlights.length,
|
||||
time: (Date.now() - startTime) / 1000,
|
||||
});
|
||||
readwise.updateSetting('lastImportedAt', timestamp);
|
||||
onClose();
|
||||
},
|
||||
onAbort: finished => {
|
||||
readwiseTrack.abortIntegrationImport({
|
||||
total: selectedHighlights.length,
|
||||
done: finished,
|
||||
time: (Date.now() - startTime) / 1000,
|
||||
});
|
||||
notify({
|
||||
icon: <InformationFillDuotoneIcon />,
|
||||
style: 'normal',
|
||||
@@ -184,6 +204,10 @@ const SelectStage = ({
|
||||
const [selected, setSelected] = useState<ReadwiseHighlight['id'][]>([]);
|
||||
|
||||
const handleResetLastImportedAt = useCallback(() => {
|
||||
readwiseTrack.startIntegrationImport({
|
||||
method: 'cleartimestamp',
|
||||
control: 'Readwise import list',
|
||||
});
|
||||
readwise.updateSetting('lastImportedAt', undefined);
|
||||
onResetLastImportedAt();
|
||||
}, [onResetLastImportedAt, readwise]);
|
||||
@@ -315,11 +339,17 @@ const HighlightTable = ({
|
||||
.catch(console.error);
|
||||
}, [readwise]);
|
||||
|
||||
const handleToggleSelectAll = useCallback(() => {
|
||||
setSelected(prev =>
|
||||
prev.length === highlights.length ? [] : highlights.map(h => h.id)
|
||||
);
|
||||
}, [highlights, setSelected]);
|
||||
const handleToggleSelectAll = useCallback(
|
||||
(_: ChangeEvent, checked: boolean) => {
|
||||
readwiseTrack.selectIntegrationImport({
|
||||
method: 'all',
|
||||
option: checked ? 'on' : 'off',
|
||||
control: 'Readwise import list',
|
||||
});
|
||||
setSelected(checked ? highlights.map(h => h.id) : []);
|
||||
},
|
||||
[highlights, setSelected]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.table}>
|
||||
@@ -358,14 +388,17 @@ const HighlightTable = ({
|
||||
<div className={styles.tableCellSelect}>
|
||||
<Checkbox
|
||||
checked={selected.includes(highlight.id)}
|
||||
onChange={() => {
|
||||
setSelected(prev => {
|
||||
if (prev.includes(highlight.id)) {
|
||||
return prev.filter(id => id !== highlight.id);
|
||||
} else {
|
||||
return [...prev, highlight.id];
|
||||
}
|
||||
onChange={(_: ChangeEvent, checked: boolean) => {
|
||||
readwiseTrack.selectIntegrationImport({
|
||||
method: 'single',
|
||||
option: checked ? 'on' : 'off',
|
||||
control: 'Readwise import list',
|
||||
});
|
||||
setSelected(
|
||||
checked
|
||||
? [...selected, highlight.id]
|
||||
: selected.filter(id => id !== highlight.id)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,7 @@ import { ConnectButton } from './connect';
|
||||
import { ConnectedActions } from './connected';
|
||||
import { ImportDialog } from './import-dialog';
|
||||
import { SettingDialog } from './setting-dialog';
|
||||
import { readwiseTrack } from './track';
|
||||
|
||||
export const ReadwiseIntegration = () => {
|
||||
const t = useI18n();
|
||||
@@ -42,6 +43,14 @@ export const ReadwiseIntegration = () => {
|
||||
setOpenImportDialog(true);
|
||||
}, []);
|
||||
|
||||
const onImportClick = useCallback(() => {
|
||||
readwiseTrack.startIntegrationImport({
|
||||
method: settings?.lastImportedAt ? 'withtimestamp' : 'new',
|
||||
control: 'Readwise Card',
|
||||
});
|
||||
handleImport();
|
||||
}, [handleImport, settings?.lastImportedAt]);
|
||||
|
||||
return (
|
||||
<IntegrationCard>
|
||||
<IntegrationCardHeader
|
||||
@@ -56,7 +65,7 @@ export const ReadwiseIntegration = () => {
|
||||
<IntegrationCardFooter>
|
||||
{token ? (
|
||||
<>
|
||||
<ConnectedActions onImport={handleImport} />
|
||||
<ConnectedActions onImport={onImportClick} />
|
||||
{openSetting && (
|
||||
<SettingDialog
|
||||
onClose={handleCloseSetting}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
IntegrationSettingToggle,
|
||||
} from '../setting';
|
||||
import * as styles from './setting-dialog.css';
|
||||
import { readwiseTrack } from './track';
|
||||
|
||||
export const SettingDialog = ({
|
||||
onClose,
|
||||
@@ -59,6 +60,18 @@ export const SettingDialog = ({
|
||||
);
|
||||
};
|
||||
|
||||
const trackModifySetting = (
|
||||
item: 'New' | 'Update' | 'Tag',
|
||||
option: 'on' | 'off',
|
||||
method?: 'append' | 'override'
|
||||
) => {
|
||||
readwiseTrack.modifyIntegrationSettings({
|
||||
item,
|
||||
option,
|
||||
method,
|
||||
});
|
||||
};
|
||||
|
||||
const Divider = () => {
|
||||
return <li className={styles.divider} />;
|
||||
};
|
||||
@@ -72,6 +85,7 @@ const NewHighlightSetting = () => {
|
||||
|
||||
const toggle = useCallback(
|
||||
(value: boolean) => {
|
||||
trackModifySetting('New', value ? 'on' : 'off');
|
||||
readwise.updateSetting('syncNewHighlights', value);
|
||||
},
|
||||
[readwise]
|
||||
@@ -98,6 +112,7 @@ const UpdateStrategySetting = () => {
|
||||
|
||||
const toggle = useCallback(
|
||||
(value: boolean) => {
|
||||
trackModifySetting('Update', value ? 'on' : 'off', 'append');
|
||||
if (!value) readwise.updateSetting('updateStrategy', undefined);
|
||||
else readwise.updateSetting('updateStrategy', 'append');
|
||||
},
|
||||
@@ -106,6 +121,7 @@ const UpdateStrategySetting = () => {
|
||||
|
||||
const handleUpdate = useCallback(
|
||||
(value: ReadwiseConfig['updateStrategy']) => {
|
||||
trackModifySetting('Update', 'on', value);
|
||||
readwise.updateSetting('updateStrategy', value);
|
||||
},
|
||||
[readwise]
|
||||
@@ -167,12 +183,23 @@ const UpdateStrategySetting = () => {
|
||||
|
||||
const StartImport = ({ onImport }: { onImport: () => void }) => {
|
||||
const t = useI18n();
|
||||
const readwise = useService(IntegrationService).readwise;
|
||||
|
||||
const handleImport = useCallback(() => {
|
||||
const lastImportedAt = readwise.setting$('lastImportedAt').value;
|
||||
readwiseTrack.startIntegrationImport({
|
||||
method: lastImportedAt ? 'withtimestamp' : 'new',
|
||||
control: 'Readwise settings',
|
||||
});
|
||||
onImport();
|
||||
}, [onImport, readwise]);
|
||||
|
||||
return (
|
||||
<IntegrationSettingItem
|
||||
name={t['com.affine.integration.readwise.setting.start-import-name']()}
|
||||
desc={t['com.affine.integration.readwise.setting.start-import-desc']()}
|
||||
>
|
||||
<Button onClick={onImport}>
|
||||
<Button onClick={handleImport}>
|
||||
{t['com.affine.integration.readwise.setting.start-import-button']()}
|
||||
</Button>
|
||||
</IntegrationSettingItem>
|
||||
@@ -226,18 +253,23 @@ const TagsSetting = () => {
|
||||
);
|
||||
const onSelectTag = useCallback(
|
||||
(tagId: string) => {
|
||||
trackModifySetting('Tag', 'on');
|
||||
updateReadwiseTags([...(tagIds ?? []), tagId]);
|
||||
},
|
||||
[tagIds, updateReadwiseTags]
|
||||
);
|
||||
const onDeselectTag = useCallback(
|
||||
(tagId: string) => {
|
||||
trackModifySetting('Tag', 'off');
|
||||
updateReadwiseTags(tagIds?.filter(id => id !== tagId) ?? []);
|
||||
},
|
||||
[tagIds, updateReadwiseTags]
|
||||
);
|
||||
const onDeleteTag = useCallback(
|
||||
(tagId: string) => {
|
||||
if (tagIds?.includes(tagId)) {
|
||||
trackModifySetting('Tag', 'off');
|
||||
}
|
||||
tagService.tagList.deleteTag(tagId);
|
||||
updateReadwiseTags(tagIds ?? []);
|
||||
},
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import track from '@affine/track';
|
||||
|
||||
/**
|
||||
* Wrap the track function to add default properties to the first argument
|
||||
*/
|
||||
export const readwiseTrack = new Proxy(track.$.settingsPanel.integrationList, {
|
||||
get(target, key, receiver) {
|
||||
const original = Reflect.get(target, key, receiver);
|
||||
|
||||
if (typeof original !== 'function') {
|
||||
return original;
|
||||
}
|
||||
|
||||
return function (this: unknown, ...args: unknown[]) {
|
||||
if (args.length > 0 && typeof args[0] === 'object' && args[0] !== null) {
|
||||
args[0] = {
|
||||
type: 'readwise',
|
||||
control: 'Readwise Card',
|
||||
...args[0],
|
||||
};
|
||||
}
|
||||
return original.apply(this, args);
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user