diff --git a/client/src/features/settings/KoreaderSettings.vue b/client/src/features/settings/KoreaderSettings.vue index a72aa7eac..09baf33de 100644 --- a/client/src/features/settings/KoreaderSettings.vue +++ b/client/src/features/settings/KoreaderSettings.vue @@ -120,7 +120,7 @@ const creating = ref(false) const showPassword = ref(false) const deleteConfirmOpen = ref(false) const helpOpen = ref(false) -const copiedUrlTarget = ref<'plugin' | 'stock' | null>(null) +const urlCopied = ref(false) const selectedUnmatched = ref(null) const selectedManualLink = ref(null) const linkSearchQuery = ref('') @@ -152,10 +152,7 @@ onUnmounted(() => { if (copiedUrlTimer) clearTimeout(copiedUrlTimer) }) -const syncUrl = computed(() => getSyncUrl()) -const stockSyncUrl = computed(() => new URL('/api/v1/koreader', syncUrl.value).toString()) -const urlCopied = computed(() => copiedUrlTarget.value === 'plugin') -const stockUrlCopied = computed(() => copiedUrlTarget.value === 'stock') +const syncUrl = computed(() => new URL('/api/v1/koreader', getSyncUrl()).toString()) const hasCredentials = computed(() => !!credentials.value) const deviceCount = computed(() => syncStatus.value?.devices.length ?? 0) const totalSyncedBooks = computed(() => syncStatus.value?.totalSyncedBooks ?? 0) @@ -383,30 +380,22 @@ async function handleDelete() { } } -async function copyUrlWithFeedback(url: string, target: 'plugin' | 'stock') { - const copied = await copyToClipboard(url) +async function handleCopySyncUrl() { + const copied = await copyToClipboard(syncUrl.value) if (!copied) { toast.error(t('settings.reader.koreader.syncUrlCopyFailed')) return } - copiedUrlTarget.value = target + urlCopied.value = true toast.success(t('settings.reader.koreader.syncUrlCopied')) if (copiedUrlTimer) clearTimeout(copiedUrlTimer) copiedUrlTimer = setTimeout(() => { - copiedUrlTarget.value = null + urlCopied.value = false copiedUrlTimer = null }, 2000) } -async function handleCopyUrl() { - await copyUrlWithFeedback(syncUrl.value, 'plugin') -} - -async function handleCopyStockUrl() { - await copyUrlWithFeedback(stockSyncUrl.value, 'stock') -} - async function handleRefresh() { try { await fetchSyncStatus(true) @@ -793,13 +782,24 @@ async function handleDownloadPlugin() {

-
- - +
+
+ +
+
+ +

+ {{ t('settings.reader.koreader.syncUrlHint') }} +

+
-
- - -
- +
  • {{ t('settings.reader.koreader.stockStep2') }}
  • {{ t('settings.reader.koreader.stockStep3') }}
  • diff --git a/client/src/features/settings/__tests__/KoreaderSettings.spec.ts b/client/src/features/settings/__tests__/KoreaderSettings.spec.ts index ac110eba1..2a06f7c6c 100644 --- a/client/src/features/settings/__tests__/KoreaderSettings.spec.ts +++ b/client/src/features/settings/__tests__/KoreaderSettings.spec.ts @@ -323,8 +323,9 @@ describe('KoreaderSettings', () => { expect(wrapper.text()).toContain('reader-user') expect(wrapper.text()).toContain('14 books') expect(wrapper.text()).toContain('1 device') - const syncUrlInput = wrapper.find('input[readonly]').element as HTMLInputElement - expect(syncUrlInput.value).toBe('https://bookorbit.example') + expect(wrapper.text()).toContain('KOReader sync URL') + expect(wrapper.text()).toContain("Works with the BookOrbit plugin and KOReader's built-in progress sync.") + expect((wrapper.find('#koreader-sync-url').element as HTMLInputElement).value).toBe('https://bookorbit.example/api/v1/koreader') expect(wrapper.text()).toContain('Kobo Libra 2') expect(wrapper.text()).toContain('Project Hail Mary') expect(wrapper.text()).toContain('Latest plugin: v1.6.0') @@ -351,11 +352,10 @@ describe('KoreaderSettings', () => { await buttonByText(wrapper, 'KOReader setup steps')!.trigger('click') expect(wrapper.text()).toContain('Download the preconfigured plugin above.') - const stockUrlInput = wrapper.find('#koreader-stock-server-url') - expect(wrapper.find('label[for="koreader-stock-server-url"]').text()).toBe('Set the custom sync server to the following URL:') - expect((stockUrlInput.element as HTMLInputElement).value).toBe('https://bookorbit.example/api/v1/koreader') + expect(wrapper.text()).toContain('Set the custom sync server to the URL shown above.') + expect(wrapper.findAll('#koreader-sync-url')).toHaveLength(1) - const copyButton = wrapper.find('#koreader-stock-server-url-copy') + const copyButton = wrapper.find('#koreader-sync-url-copy') await copyButton.trigger('click') expect(vi.mocked(copyToClipboard)).toHaveBeenLastCalledWith('https://bookorbit.example/api/v1/koreader') @@ -469,7 +469,7 @@ describe('KoreaderSettings', () => { expect(koreaderMock.fetchUnmatchedBooks).toHaveBeenCalledTimes(2) expect(koreaderMock.fetchManualHashLinks).toHaveBeenCalledTimes(2) expect(koreaderMock.updateCredentials).toHaveBeenCalledWith({ syncEnabled: false }) - expect(vi.mocked(copyToClipboard)).toHaveBeenCalledWith('https://bookorbit.example') + expect(vi.mocked(copyToClipboard)).toHaveBeenCalledWith('https://bookorbit.example/api/v1/koreader') expect(koreaderMock.downloadPluginPackage).toHaveBeenCalledTimes(1) expect(wrapper.text()).toContain('Delete KOReader credentials?') diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 8d3861003..27d4dd834 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -2105,6 +2105,8 @@ "credentialsCreatedLabel": "Credentials created", "setup": "Setup", "pluginServerUrl": "Plugin server URL", + "syncUrl": "KOReader sync URL", + "syncUrlHint": "Works with the BookOrbit plugin and KOReader's built-in progress sync.", "copied": "Copied", "copyUrl": "Copy URL", "preconfiguredPlugin": "Preconfigured BookOrbit plugin", diff --git a/koreader-plugin/spec/bookorbit_api_test.lua b/koreader-plugin/spec/bookorbit_api_test.lua index 85441c4f3..c8336cdbf 100644 --- a/koreader-plugin/spec/bookorbit_api_test.lua +++ b/koreader-plugin/spec/bookorbit_api_test.lua @@ -108,6 +108,11 @@ local function assertEqual(actual, expected, label) end end +assertEqual(BookOrbitApi.normalizeServerUrl("https://bookorbit.example.com"), + "https://bookorbit.example.com/api/v1", "origin-only server URL is normalized") +assertEqual(BookOrbitApi.normalizeServerUrl("https://bookorbit.example.com/api/v1/koreader"), + "https://bookorbit.example.com/api/v1", "stock sync URL is normalized") + local decoded, err = BookOrbitApi.decodeResponse({ "" }) assertEqual(type(decoded), "table", "empty body decodes to table") assertEqual(err, nil, "empty body has no error")