diff --git a/src/app/content/ankiWeb/helpers/fillForm.tsx b/src/app/content/ankiWeb/helpers/fillForm.tsx new file mode 100644 index 0000000..d601985 --- /dev/null +++ b/src/app/content/ankiWeb/helpers/fillForm.tsx @@ -0,0 +1,53 @@ +import { INote } from '@entities/note' + +export function fillForm(note: INote) { + const ankiFaceElement = document.querySelector( + 'body > div > main > form > div:nth-child(1) > div > div', + ) + const ankiBackElement = document.querySelector( + 'body > div > main > form > div:nth-child(2) > div > div', + ) + + if (ankiFaceElement) { + ankiFaceElement.innerText = getFrontText(ankiFaceElement.innerText, note) + ankiFaceElement.dispatchEvent(new Event('input', { bubbles: true })) + } + + if (ankiBackElement) { + ankiBackElement.innerText = getBackText(ankiBackElement.innerText, note) + ankiBackElement.dispatchEvent(new Event('input', { bubbles: true })) + } +} + +function getFrontText(initialText: string, note: INote) { + return ( + initialText + + ' ' + + note.text + + (note.transcription ? '\n' + note.transcription : '') + + (note.context ? '\n' + note.context : '') + ) +} + +function getBackText(initialText: string, note: INote) { + const blankedHintText = getBlankedHintText(note.text) + return ( + initialText + + ' ' + + (note.translation ? note.translation : '') + + (blankedHintText ? '\n' + blankedHintText : '') + ) +} + +function getBlankedHintText(text: string) { + return text + .split(' ') + .map( + (word) => + word[0] + + Array(word.length - 1) + .fill(' _') + .join(''), + ) + .join(' ') +} diff --git a/src/app/content/ankiWeb/index.ts b/src/app/content/ankiWeb/index.ts index 7adb628..ba84c6e 100644 --- a/src/app/content/ankiWeb/index.ts +++ b/src/app/content/ankiWeb/index.ts @@ -1 +1,10 @@ +import { autoFillFormHandler } from '@features/note/FillFlashcardForm' + +import { browser } from '@shared/browser' + +import { fillForm } from './helpers/fillForm' + console.log('anki web content code') +browser.runtime.onConnect.addListener(function (port) { + autoFillFormHandler(port, fillForm) +}) diff --git a/src/entities/tab/index.ts b/src/entities/tab/index.ts new file mode 100644 index 0000000..ef30951 --- /dev/null +++ b/src/entities/tab/index.ts @@ -0,0 +1,18 @@ +import { atom, onMount, task } from 'nanostores' + +import { ITab, browser } from '@shared/browser' +import { addToast, getErrorToast } from '@shared/ui/Toast' + +export const $activeTab = atom(null) + +onMount($activeTab, () => { + task(async () => { + const getResult = await browser.tabs.getActiveTab() + + if (getResult.data) { + $activeTab.set(getResult.data) + } else { + addToast(getErrorToast(getResult.error)) + } + }) +}) diff --git a/src/features/note/FillFlashcardForm/api/index.ts b/src/features/note/FillFlashcardForm/api/index.ts new file mode 100644 index 0000000..9e6bec2 --- /dev/null +++ b/src/features/note/FillFlashcardForm/api/index.ts @@ -0,0 +1,44 @@ +import { INote } from '@entities/note' + +import { PortEmitter, PortReceiver } from '@shared/browser' + +interface Request { + message: 'FILL_FORM' + data: INote +} + +interface Response { + message: 'FILL_FORM_RESULT' + data: INote +} + +export const autoFillForm = (port: PortEmitter, note: INote) => { + port.postMessage({ message: 'FILL_FORM', data: note }) +} + +export const autoFillFormHandler = ( + port: PortReceiver, + callback: (note: INote) => void, +) => { + port.onMessage((request) => { + if (request.message === 'FILL_FORM') { + callback(request.data) + + port.postMessage({ + message: 'FILL_FORM_RESULT', + data: request.data, + }) + } + }) +} + +export const autoFillFormResult = ( + port: PortEmitter, + callback: (response: Response['data']) => void, +) => { + port.onMessage((response) => { + if (response.message === 'FILL_FORM_RESULT') { + callback(response.data) + } + }) +} diff --git a/src/features/note/FillFlashcardForm/index.ts b/src/features/note/FillFlashcardForm/index.ts new file mode 100644 index 0000000..fa050ac --- /dev/null +++ b/src/features/note/FillFlashcardForm/index.ts @@ -0,0 +1,3 @@ +export * from './api' +export * from './ui' +export * from './model' diff --git a/src/features/note/FillFlashcardForm/model/index.ts b/src/features/note/FillFlashcardForm/model/index.ts new file mode 100644 index 0000000..ab069fa --- /dev/null +++ b/src/features/note/FillFlashcardForm/model/index.ts @@ -0,0 +1 @@ +export { $ankiPort } from './store' diff --git a/src/features/note/FillFlashcardForm/model/store.ts b/src/features/note/FillFlashcardForm/model/store.ts new file mode 100644 index 0000000..896802c --- /dev/null +++ b/src/features/note/FillFlashcardForm/model/store.ts @@ -0,0 +1,25 @@ +import { computed } from 'nanostores' + +import { $activeTab } from '@entities/tab' + +import { PortEmitter } from '@shared/browser' + +export const $ankiPort = computed( + $activeTab, + (activeTab): PortEmitter | null => { + if (!activeTab?.id) return null + const isAnkiTab = activeTab + ? activeTab.url === 'https://ankiuser.net/add' + : false + + if (isAnkiTab) { + const port = new PortEmitter({ + tabId: activeTab.id, + connectInfo: { name: 'anki' }, + }) + + return port + } + return null + }, +) diff --git a/src/features/note/FillFlashcardForm/ui/FillFlashcardForm.tsx b/src/features/note/FillFlashcardForm/ui/FillFlashcardForm.tsx new file mode 100644 index 0000000..7f570fa --- /dev/null +++ b/src/features/note/FillFlashcardForm/ui/FillFlashcardForm.tsx @@ -0,0 +1,53 @@ +import { useStore } from '@nanostores/preact' +import { useEffect, useState } from 'preact/hooks' + +import { INote } from '@entities/note' + +import { browser } from '@shared/browser' +import { useResetAfterDelay } from '@shared/libs/useResetAfterDelay' +import { Button } from '@shared/ui/Button' +import { AddCardIcon } from '@shared/ui/icons/AddCardIcon' +import { DoneIcon } from '@shared/ui/icons/DoneIcon' + +import { autoFillForm, autoFillFormResult } from '../api' +import { $ankiPort } from '../model/store' + +interface Props { + note: INote +} + +export const FillFlashcardForm = ({ note }: Props) => { + const ankiPort = useStore($ankiPort) + const [isFormFilled, setIsFormFilled] = useState(false) + const resetAfterDelay = useResetAfterDelay({ + reset: () => setIsFormFilled(false), + }) + + useEffect(() => { + ankiPort && + autoFillFormResult(ankiPort, (data) => { + if (data.id === note.id) { + setIsFormFilled(true) + resetAfterDelay() + } + }) + }, [ankiPort]) + + const fillAnkiForm = () => { + ankiPort && autoFillForm(ankiPort, note) + } + + return ( +