mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
feat: add BookOrbit attribution requirements
Define AGPLv3 additional terms and expose attribution, source, and license details through unobtrusive About surfaces.
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
# BookOrbit Additional Terms to the GNU AGPLv3
|
||||
|
||||
Effective 10 September 2026.
|
||||
|
||||
Copyright (C) 2025-2026 neon. Additional copyright is held by the respective BookOrbit contributors.
|
||||
|
||||
These non-permissive additional terms supplement the GNU Affero General Public License, version 3 only, as permitted by sections 7(b), 7(c), 7(d), and 7(e).
|
||||
|
||||
These terms apply to BookOrbit material owned by neon and distributed with this notice, and to contributions whose copyright holders expressly accept these terms. Other contributions remain subject to the license terms under which they were originally submitted.
|
||||
|
||||
## 1. Attribution
|
||||
|
||||
Every interactive user interface of a covered work containing BookOrbit material must provide a prominent and readily accessible **About** or **Legal Notices** feature displaying:
|
||||
|
||||
> **Powered by BookOrbit**
|
||||
>
|
||||
> Copyright (C) 2025-2026 neon.
|
||||
>
|
||||
> Additional copyright is held by the respective BookOrbit contributors.
|
||||
>
|
||||
> Original project: <https://github.com/bookorbit/bookorbit>
|
||||
>
|
||||
> Licensed under the GNU Affero General Public License, version 3 only, with additional terms. This software is provided without warranty. You may redistribute and modify it under those terms.
|
||||
|
||||
"Powered by BookOrbit" must link directly to <https://github.com/bookorbit/bookorbit>.
|
||||
|
||||
The feature must be available to all users without administrative permission. It and the required notice must not be removed, disabled, hidden, obscured, or made materially less prominent than comparable About, help, or legal-notice features.
|
||||
|
||||
## 2. Source Code
|
||||
|
||||
When AGPLv3 section 13 applies, the same feature must offer users the complete Corresponding Source for the exact version being operated, at no charge. A link only to the original BookOrbit repository is not sufficient for a modified version.
|
||||
|
||||
## 3. Modified Versions
|
||||
|
||||
A modified version must prominently state:
|
||||
|
||||
> This is a modified version of BookOrbit and is not an official BookOrbit release.
|
||||
|
||||
It must provide a relevant modification date and must not claim or imply that it is an official, approved, sponsored, or endorsed BookOrbit release.
|
||||
|
||||
## 4. Trademarks
|
||||
|
||||
Except for the factual attribution required above, no trademark rights are granted. Without written permission, a modified version may not use "BookOrbit", a confusingly similar name, or a BookOrbit logo as its primary name or branding.
|
||||
|
||||
## 5. Preservation and Enforcement
|
||||
|
||||
Source distributions containing covered BookOrbit material must preserve this file, the GNU AGPLv3 license, applicable copyright notices, and notices identifying modifications and their dates.
|
||||
|
||||
Compliance with these terms is an express condition of permission to modify or convey covered BookOrbit material or make a modified version available for remote network interaction. Noncompliance is subject to section 8 of the AGPLv3.
|
||||
|
||||
These terms apply only to the extent permitted by section 7 of the AGPLv3. If any provision is unenforceable, the remaining provisions continue in effect.
|
||||
+1
-1
@@ -72,7 +72,7 @@ ENV PORT=3000
|
||||
COPY --from=server-builder --chown=node:node /deploy ./
|
||||
COPY --from=client-builder --chown=node:node /app/client/dist ./public
|
||||
COPY --from=server-builder --chown=node:node /app/server/entrypoint.sh ./entrypoint.sh
|
||||
COPY --chown=node:node LICENSE NOTICE ./
|
||||
COPY --chown=node:node LICENSE NOTICE ADDITIONAL_TERMS.md ./
|
||||
COPY --chown=node:node server/bin/kepubify/ ./bin/kepubify/
|
||||
COPY --chown=node:node koreader-plugin/bookorbit.koplugin/ ./koreader-plugin/bookorbit.koplugin/
|
||||
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
BookOrbit Attribution Notice
|
||||
BookOrbit Attribution and Legal Notice
|
||||
|
||||
Copyright (C) 2025-2026 neon and BookOrbit contributors.
|
||||
Copyright (C) 2025-2026 neon.
|
||||
Additional copyright is held by the respective BookOrbit contributors.
|
||||
|
||||
BookOrbit is free software licensed under the GNU Affero General Public
|
||||
License, version 3. See LICENSE for the complete license terms.
|
||||
License, version 3 only. See LICENSE for the complete license text.
|
||||
|
||||
Additional attribution term under GNU AGPL v3 section 7(b):
|
||||
BookOrbit material owned by neon and contributions whose copyright holders
|
||||
expressly accept the terms are also subject to the non-permissive additional
|
||||
terms in ADDITIONAL_TERMS.md, as authorized by sections 7(b), 7(c), 7(d), and
|
||||
7(e) of the GNU AGPL v3.
|
||||
|
||||
When a covered work containing BookOrbit material is conveyed, its source
|
||||
distribution must preserve the following reasonable author attribution in a
|
||||
top-level NOTICE, COPYRIGHT, README, or comparable legal notices file:
|
||||
Other contributions remain subject to the license terms under which they were
|
||||
originally submitted.
|
||||
|
||||
BookOrbit
|
||||
Copyright (C) 2025-2026 neon and BookOrbit contributors.
|
||||
The required attribution is:
|
||||
|
||||
Powered by BookOrbit
|
||||
Copyright (C) 2025-2026 neon.
|
||||
Additional copyright is held by the respective BookOrbit contributors.
|
||||
Original project: https://github.com/bookorbit/bookorbit
|
||||
|
||||
This attribution does not grant permission to use the BookOrbit name or logo
|
||||
to imply endorsement of a modified version. Modified versions that are
|
||||
conveyed must identify themselves as modified, give relevant modification
|
||||
dates, and otherwise comply with the GNU AGPL v3.
|
||||
See ADDITIONAL_TERMS.md for the conditions governing display, source access,
|
||||
modified versions, preservation, and trademarks.
|
||||
|
||||
@@ -150,6 +150,6 @@ When adding user-facing text in code, add the Vue I18n key only to `client/src/l
|
||||
|
||||
## License and Attribution
|
||||
|
||||
BookOrbit is licensed under the **[GNU Affero General Public License v3.0](LICENSE)**.
|
||||
BookOrbit is licensed under the **[GNU Affero General Public License v3.0 only](LICENSE)**.
|
||||
|
||||
Forks and modified distributions must preserve BookOrbit's reasonable author attribution as described in the **[BookOrbit Attribution Notice](NOTICE)**, in accordance with section 7(b) of the GNU AGPL v3.
|
||||
BookOrbit material whose copyright holders have authorized them is also subject to the **[BookOrbit Additional Terms](ADDITIONAL_TERMS.md)** under sections 7(b), 7(c), 7(d), and 7(e) of the GNU AGPL v3. See the **[Attribution and Legal Notice](NOTICE)** for the required attribution.
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
"description": "BookOrbit client - self-hosted library management and reading platform",
|
||||
"author": "neonsolstice",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"license": "AGPL-3.0-only",
|
||||
"homepage": "https://bookorbit.app",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useAuth } from '@/features/auth/composables/useAuth'
|
||||
import { Toaster } from '@/components/ui/sonner'
|
||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||
import { resolveRouteViewKey } from '@/router/view-key'
|
||||
import LegalNotices from '@/components/legal/LegalNotices.vue'
|
||||
|
||||
const { isOpen } = useChangePasswordDialog()
|
||||
const themeStore = useThemeStore()
|
||||
@@ -53,6 +54,7 @@ provide(
|
||||
</router-view>
|
||||
<ChangePasswordDialog v-if="isOpen" />
|
||||
<WhatsNewDialog v-if="popupOpen" />
|
||||
<LegalNotices />
|
||||
<Toaster rich-colors position="bottom-right" :visible-toasts="5" :gap="8" />
|
||||
</TooltipProvider>
|
||||
</template>
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
ExternalLink,
|
||||
Sparkles,
|
||||
Languages,
|
||||
Info,
|
||||
} from '@lucide/vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@@ -57,6 +58,7 @@ import { DEFAULT_FORMAT_PRIORITY, LOCALE_LABELS, Permission, type Locale } from
|
||||
import { useThemeStore } from '@/stores/theme'
|
||||
import { useLocaleStore } from '@/stores/locale'
|
||||
import { getFormatColor } from '@/features/book/lib/format-colors'
|
||||
import { useLegalNotices } from '@/components/legal/useLegalNotices'
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
@@ -69,6 +71,7 @@ const { subscribe: subscribeNotifications } = useNotifications()
|
||||
const { hasUnseen: hasUnseenWhatsNew } = useWhatsNew()
|
||||
const themeStore = useThemeStore()
|
||||
const localeStore = useLocaleStore()
|
||||
const { openLegalNotices } = useLegalNotices()
|
||||
const currentLanguageLabel = computed(() => LOCALE_LABELS[localeStore.locale])
|
||||
const documentationUrl = 'https://bookorbit.app/what-is-bookorbit'
|
||||
|
||||
@@ -669,6 +672,10 @@ function formatBadgeStyle(fmt: string) {
|
||||
<ExternalLink :size="12" class="ml-auto text-muted-foreground" />
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="openLegalNotices">
|
||||
<Info :size="15" class="mr-2 text-muted-foreground" />
|
||||
{{ t('components.legalNotices.about') }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -742,6 +749,10 @@ function formatBadgeStyle(fmt: string) {
|
||||
{{ t('components.appHeader.whatsNew') }}
|
||||
<span v-if="hasUnseenWhatsNew" class="ml-auto h-1.5 w-1.5 rounded-full bg-primary" :aria-label="t('components.appHeader.new')" />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="openLegalNotices">
|
||||
<Info :size="14" class="mr-2 text-muted-foreground" />
|
||||
{{ t('components.legalNotices.about') }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<TooltipContent>{{ t('components.appHeader.help') }}</TooltipContent>
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
import LegalNotices from './LegalNotices.vue'
|
||||
import PublicLegalNotices from './PublicLegalNotices.vue'
|
||||
import { useLegalNotices } from './useLegalNotices'
|
||||
|
||||
vi.mock('reka-ui', () => ({
|
||||
DialogRoot: { props: ['open'], template: '<div v-if="open"><slot /></div>' },
|
||||
DialogPortal: { template: '<div><slot /></div>' },
|
||||
DialogOverlay: { template: '<div />' },
|
||||
DialogContent: { template: '<section class="legal-dialog"><slot /></section>' },
|
||||
DialogTitle: { template: '<h2><slot /></h2>' },
|
||||
DialogDescription: { template: '<p><slot /></p>' },
|
||||
}))
|
||||
|
||||
describe('LegalNotices', () => {
|
||||
beforeEach(() => {
|
||||
useLegalNotices().closeLegalNotices()
|
||||
})
|
||||
|
||||
it('keeps the legal notice available without authentication or elevated permission', async () => {
|
||||
const wrapper = mount(LegalNotices)
|
||||
|
||||
useLegalNotices().openLegalNotices()
|
||||
await nextTick()
|
||||
|
||||
expect(wrapper.get('.legal-dialog').text()).toContain('Powered by BookOrbit')
|
||||
expect(wrapper.get('.legal-dialog').text()).toContain('Copyright (C) 2025-2026 neon.')
|
||||
expect(wrapper.get('.legal-dialog').text()).toContain('Additional copyright is held by the respective BookOrbit contributors.')
|
||||
expect(wrapper.get('.legal-dialog').text()).toContain('provided without warranty')
|
||||
})
|
||||
|
||||
it('links attribution, source, license, and additional terms to the project', async () => {
|
||||
const wrapper = mount(LegalNotices)
|
||||
useLegalNotices().openLegalNotices()
|
||||
await nextTick()
|
||||
|
||||
const links = wrapper.findAll('a')
|
||||
expect(links.map((link) => link.attributes('href'))).toEqual([
|
||||
'https://github.com/bookorbit/bookorbit',
|
||||
'https://github.com/bookorbit/bookorbit',
|
||||
'https://github.com/bookorbit/bookorbit/blob/main/LICENSE',
|
||||
'https://github.com/bookorbit/bookorbit/blob/main/ADDITIONAL_TERMS.md',
|
||||
])
|
||||
})
|
||||
|
||||
it('uses an inline linked attribution on public pages', async () => {
|
||||
const wrapper = mount(PublicLegalNotices)
|
||||
|
||||
const attribution = wrapper.get('a')
|
||||
expect(attribution.text()).toBe('Powered by BookOrbit')
|
||||
expect(attribution.attributes('href')).toBe('https://github.com/bookorbit/bookorbit')
|
||||
expect(wrapper.classes()).not.toContain('fixed')
|
||||
|
||||
await wrapper.get('button').trigger('click')
|
||||
expect(useLegalNotices().open.value).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,94 @@
|
||||
<script setup lang="ts">
|
||||
import { ExternalLink, X } from '@lucide/vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { DialogContent, DialogDescription, DialogOverlay, DialogPortal, DialogRoot, DialogTitle } from 'reka-ui'
|
||||
import { useLegalNotices } from './useLegalNotices'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { open, closeLegalNotices, handleOpenChange } = useLegalNotices()
|
||||
|
||||
const PROJECT_URL = 'https://github.com/bookorbit/bookorbit'
|
||||
const LICENSE_URL = `${PROJECT_URL}/blob/main/LICENSE`
|
||||
const ADDITIONAL_TERMS_URL = `${PROJECT_URL}/blob/main/ADDITIONAL_TERMS.md`
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogRoot :open="open" @update:open="handleOpenChange">
|
||||
<DialogPortal>
|
||||
<DialogOverlay class="fixed inset-0 z-50 bg-foreground/50" />
|
||||
<DialogContent
|
||||
aria-modal="true"
|
||||
class="fixed left-1/2 top-1/2 z-50 max-h-[calc(100svh-2rem)] w-[calc(100%-2rem)] max-w-xl -translate-x-1/2 -translate-y-1/2 overflow-y-auto rounded-lg border border-border bg-card p-6 shadow-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<DialogTitle class="text-lg font-semibold text-foreground">
|
||||
{{ t('components.legalNotices.title') }}
|
||||
</DialogTitle>
|
||||
<DialogDescription class="mt-1 text-sm text-muted-foreground">
|
||||
{{ t('components.legalNotices.description') }}
|
||||
</DialogDescription>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex size-8 shrink-0 items-center justify-center rounded-md text-foreground outline-hidden hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring"
|
||||
:aria-label="t('common.close')"
|
||||
@click="closeLegalNotices"
|
||||
>
|
||||
<X :size="16" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 space-y-4 text-sm">
|
||||
<a
|
||||
:href="PROJECT_URL"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex items-center gap-1.5 font-semibold text-primary underline-offset-4 hover:underline focus-visible:rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
{{ t('components.legalNotices.poweredBy') }}
|
||||
<ExternalLink :size="14" aria-hidden="true" />
|
||||
</a>
|
||||
|
||||
<div class="space-y-1 text-foreground">
|
||||
<p>{{ t('components.legalNotices.originalDevelopment') }}</p>
|
||||
<p>{{ t('components.legalNotices.copyright') }}</p>
|
||||
<p>{{ t('components.legalNotices.contributors') }}</p>
|
||||
</div>
|
||||
|
||||
<p class="text-foreground">{{ t('components.legalNotices.licenseSummary') }}</p>
|
||||
|
||||
<div class="grid gap-2 sm:grid-cols-3">
|
||||
<a
|
||||
:href="PROJECT_URL"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-md border border-border px-3 py-2 font-medium text-foreground outline-hidden hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
{{ t('components.legalNotices.sourceCode') }}
|
||||
<ExternalLink :size="14" aria-hidden="true" />
|
||||
</a>
|
||||
<a
|
||||
:href="LICENSE_URL"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-md border border-border px-3 py-2 font-medium text-foreground outline-hidden hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
{{ t('components.legalNotices.license') }}
|
||||
<ExternalLink :size="14" aria-hidden="true" />
|
||||
</a>
|
||||
<a
|
||||
:href="ADDITIONAL_TERMS_URL"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-md border border-border px-3 py-2 font-medium text-foreground outline-hidden hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
{{ t('components.legalNotices.additionalTerms') }}
|
||||
<ExternalLink :size="14" aria-hidden="true" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</DialogPortal>
|
||||
</DialogRoot>
|
||||
</template>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useLegalNotices } from './useLegalNotices'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { openLegalNotices } = useLegalNotices()
|
||||
|
||||
const PROJECT_URL = 'https://github.com/bookorbit/bookorbit'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative z-10 flex items-center justify-center gap-1.5 text-[11px] text-foreground">
|
||||
<a
|
||||
:href="PROJECT_URL"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="rounded-sm outline-hidden underline-offset-4 hover:underline focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
{{ t('components.legalNotices.poweredBy') }}
|
||||
</a>
|
||||
<span aria-hidden="true">·</span>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-sm outline-hidden underline-offset-4 hover:underline focus-visible:ring-2 focus-visible:ring-ring"
|
||||
@click="openLegalNotices"
|
||||
>
|
||||
{{ t('components.legalNotices.trigger') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
const open = ref(false)
|
||||
|
||||
export function useLegalNotices() {
|
||||
function openLegalNotices() {
|
||||
open.value = true
|
||||
}
|
||||
|
||||
function closeLegalNotices() {
|
||||
open.value = false
|
||||
}
|
||||
|
||||
function handleOpenChange(value: boolean) {
|
||||
open.value = value
|
||||
}
|
||||
|
||||
return { open, openLegalNotices, closeLegalNotices, handleOpenChange }
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { Moon, Sun, Wallpaper } from '@lucide/vue'
|
||||
import { ACCENT_OPTIONS, ACCENT_ROWS, RADIUS_OPTIONS, BACKGROUND_OPTIONS, useThemeStore } from '@/stores/theme'
|
||||
import { api } from '@/lib/api'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import PublicLegalNotices from '@/components/legal/PublicLegalNotices.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const themeStore = useThemeStore()
|
||||
@@ -75,7 +76,7 @@ async function handleSubmit() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-bg min-h-screen flex items-center justify-center px-4 overflow-hidden">
|
||||
<div class="login-bg min-h-screen flex flex-col items-center justify-center gap-3 px-4 overflow-hidden">
|
||||
<!-- Compact theme picker -->
|
||||
<div class="fixed bottom-5 right-5 z-20 flex items-center gap-1.5">
|
||||
<!-- Dark / light toggle -->
|
||||
@@ -238,6 +239,7 @@ async function handleSubmit() {
|
||||
<RouterLink to="/login" class="text-primary hover:underline">{{ t('auth.backToSignIn') }}</RouterLink>
|
||||
</p>
|
||||
</div>
|
||||
<PublicLegalNotices />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useOidc } from './composables/useOidc'
|
||||
import { useSetupStatus } from './composables/useSetupStatus'
|
||||
import { useLoginOptions } from './composables/useLoginOptions'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import PublicLegalNotices from '@/components/legal/PublicLegalNotices.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const themeStore = useThemeStore()
|
||||
@@ -125,7 +126,7 @@ async function handleOidcLogin(provider: OidcProviderPublic) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-bg min-h-screen flex items-center justify-center px-4 overflow-hidden">
|
||||
<div class="login-bg min-h-screen flex flex-col items-center justify-center gap-3 px-4 overflow-hidden">
|
||||
<!-- Compact theme picker -->
|
||||
<div class="fixed top-5 right-5 z-20 flex items-center gap-1.5">
|
||||
<!-- Dark / light toggle -->
|
||||
@@ -334,6 +335,7 @@ async function handleOidcLogin(provider: OidcProviderPublic) {
|
||||
<RouterLink to="/register" class="text-primary hover:underline">{{ t('auth.login.signUp') }}</RouterLink>
|
||||
</p>
|
||||
</div>
|
||||
<PublicLegalNotices />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { Loader2, AlertCircle } from '@lucide/vue'
|
||||
import { useAuth } from '@/features/auth/composables/useAuth'
|
||||
import PublicLegalNotices from '@/components/legal/PublicLegalNotices.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
@@ -41,7 +42,7 @@ function goToLogin() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen flex items-center justify-center bg-background px-4">
|
||||
<div class="min-h-screen flex flex-col items-center justify-center gap-3 bg-background px-4">
|
||||
<div class="w-full max-w-sm text-center">
|
||||
<div v-if="status === 'loading'" class="space-y-4">
|
||||
<Loader2 :size="32" class="mx-auto text-primary animate-spin" />
|
||||
@@ -64,5 +65,6 @@ function goToLogin() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<PublicLegalNotices />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useRouter } from 'vue-router'
|
||||
import type { OidcCallbackResponse } from '@bookorbit/types'
|
||||
import { OidcErrorCode } from '@bookorbit/types'
|
||||
import { setAccessToken } from '@/lib/api'
|
||||
import PublicLegalNotices from '@/components/legal/PublicLegalNotices.vue'
|
||||
import { useAuth } from './composables/useAuth'
|
||||
import { useOidc, OidcLoginError } from './composables/useOidc'
|
||||
|
||||
@@ -82,7 +83,7 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen flex items-center justify-center bg-background px-4">
|
||||
<div class="min-h-screen flex flex-col items-center justify-center gap-3 bg-background px-4">
|
||||
<div class="w-full max-w-sm text-center">
|
||||
<h1 class="text-2xl font-serif font-semibold text-foreground mb-6">Book<span class="text-primary"> Orbit</span></h1>
|
||||
|
||||
@@ -98,5 +99,6 @@ onMounted(async () => {
|
||||
<RouterLink to="/login" class="text-sm text-primary hover:underline">{{ t('auth.oidc.tryAgain') }}</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
<PublicLegalNotices />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { Moon, Sun, Wallpaper } from '@lucide/vue'
|
||||
import { ACCENT_OPTIONS, ACCENT_ROWS, RADIUS_OPTIONS, BACKGROUND_OPTIONS, useThemeStore } from '@/stores/theme'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import PublicLegalNotices from '@/components/legal/PublicLegalNotices.vue'
|
||||
import { RegisterError, useAuth } from './composables/useAuth'
|
||||
import { checkIdentityText } from './lib/identity-text'
|
||||
|
||||
@@ -110,7 +111,7 @@ async function handleSubmit() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-bg min-h-screen flex items-center justify-center px-4 overflow-hidden">
|
||||
<div class="login-bg min-h-screen flex flex-col items-center justify-center gap-3 px-4 overflow-hidden">
|
||||
<!-- Compact theme picker -->
|
||||
<div class="fixed bottom-5 right-5 z-20 flex items-center gap-1.5">
|
||||
<!-- Dark / light toggle -->
|
||||
@@ -327,6 +328,7 @@ async function handleSubmit() {
|
||||
</RouterLink>
|
||||
</p>
|
||||
</div>
|
||||
<PublicLegalNotices />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
import { Moon, Sun, Wallpaper } from '@lucide/vue'
|
||||
import { ACCENT_OPTIONS, ACCENT_ROWS, RADIUS_OPTIONS, BACKGROUND_OPTIONS, useThemeStore } from '@/stores/theme'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import PublicLegalNotices from '@/components/legal/PublicLegalNotices.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const themeStore = useThemeStore()
|
||||
@@ -95,7 +96,7 @@ async function handleSubmit() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-bg min-h-screen flex items-center justify-center px-4 overflow-hidden">
|
||||
<div class="login-bg min-h-screen flex flex-col items-center justify-center gap-3 px-4 overflow-hidden">
|
||||
<!-- Compact theme picker -->
|
||||
<div class="fixed bottom-5 right-5 z-20 flex items-center gap-1.5">
|
||||
<!-- Dark / light toggle -->
|
||||
@@ -267,6 +268,7 @@ async function handleSubmit() {
|
||||
<RouterLink to="/login" class="text-primary hover:underline">{{ t('auth.backToSignIn') }}</RouterLink>
|
||||
</p>
|
||||
</div>
|
||||
<PublicLegalNotices />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { Moon, Sun, Wallpaper } from '@lucide/vue'
|
||||
import { ACCENT_OPTIONS, ACCENT_ROWS, RADIUS_OPTIONS, BACKGROUND_OPTIONS, useThemeStore } from '@/stores/theme'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import PublicLegalNotices from '@/components/legal/PublicLegalNotices.vue'
|
||||
import { useAuth } from './composables/useAuth'
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -85,7 +86,7 @@ async function handleSubmit() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-bg min-h-screen flex items-center justify-center px-4 overflow-hidden">
|
||||
<div class="login-bg min-h-screen flex flex-col items-center justify-center gap-3 px-4 overflow-hidden">
|
||||
<!-- Compact theme picker -->
|
||||
<div class="fixed bottom-5 right-5 z-20 flex items-center gap-1.5">
|
||||
<!-- Dark / light toggle -->
|
||||
@@ -305,6 +306,7 @@ async function handleSubmit() {
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<PublicLegalNotices />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Keyboard, X } from '@lucide/vue'
|
||||
import { Info, Keyboard, X } from '@lucide/vue'
|
||||
import { useLegalNotices } from '@/components/legal/useLegalNotices'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { openLegalNotices } = useLegalNotices()
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: []
|
||||
@@ -51,6 +53,11 @@ function handleBackdropClick(e: MouseEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
function openAboutBookOrbit() {
|
||||
emit('close')
|
||||
openLegalNotices()
|
||||
}
|
||||
|
||||
onMounted(() => document.addEventListener('keydown', onKeyDown, true))
|
||||
onUnmounted(() => document.removeEventListener('keydown', onKeyDown, true))
|
||||
</script>
|
||||
@@ -87,6 +94,17 @@ onUnmounted(() => document.removeEventListener('keydown', onKeyDown, true))
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end border-t border-border/80 px-4 py-2.5">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium text-foreground outline-hidden hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring"
|
||||
@click="openAboutBookOrbit"
|
||||
>
|
||||
<Info :size="13" aria-hidden="true" />
|
||||
{{ t('components.legalNotices.about') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -7266,6 +7266,20 @@
|
||||
"librariesEmptyHint": "Create a library to start adding books.",
|
||||
"smartScopesEmptyHint": "Smart scopes save a set of filters you use often.",
|
||||
"collectionsEmptyHint": "Collections group books you pick by hand."
|
||||
},
|
||||
"legalNotices": {
|
||||
"about": "About BookOrbit",
|
||||
"trigger": "Legal notices",
|
||||
"title": "About BookOrbit",
|
||||
"description": "BookOrbit attribution, licensing, and source code.",
|
||||
"poweredBy": "Powered by BookOrbit",
|
||||
"originalDevelopment": "This software contains material originally developed as BookOrbit.",
|
||||
"copyright": "Copyright (C) 2025-2026 neon.",
|
||||
"contributors": "Additional copyright is held by the respective BookOrbit contributors.",
|
||||
"licenseSummary": "Licensed under the GNU Affero General Public License, version 3 only, with additional terms. This software is provided without warranty. You may redistribute and modify it under those terms.",
|
||||
"sourceCode": "Source code",
|
||||
"license": "AGPLv3 license",
|
||||
"additionalTerms": "Additional terms"
|
||||
}
|
||||
},
|
||||
"customIcons": {
|
||||
|
||||
@@ -281,4 +281,4 @@ See [AI_POLICY.md](AI_POLICY.md). The short version: disclose all AI usage in yo
|
||||
|
||||
## License
|
||||
|
||||
By contributing, you agree that your contributions are licensed under the same license as the project (AGPL-3.0), including the attribution term in the [BookOrbit Attribution Notice](../NOTICE).
|
||||
By submitting a contribution, you license it under the [GNU Affero General Public License, version 3 only](../LICENSE), together with the [BookOrbit Additional Terms](../ADDITIONAL_TERMS.md). You represent that you have the authority to grant this license. Copyright in your contribution remains yours.
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "bookorbit",
|
||||
"description": "Self-hosted library management and reading platform for ebooks, PDFs, audiobooks, and comics",
|
||||
"author": "neonsolstice",
|
||||
"license": "AGPL-3.0",
|
||||
"license": "AGPL-3.0-only",
|
||||
"homepage": "https://bookorbit.app",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@bookorbit/plugin-api",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@bookorbit/types",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"devDependencies": {
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
"description": "BookOrbit server - self-hosted library management and reading platform",
|
||||
"author": "neonsolstice",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"license": "AGPL-3.0-only",
|
||||
"homepage": "https://bookorbit.app",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Reference in New Issue
Block a user