mirror of
https://github.com/gotson/komga.git
synced 2025-12-06 08:32:25 +01:00
parent
a7252f8429
commit
3d69e19fd6
29 changed files with 5678 additions and 333 deletions
2554
komga-webui/package-lock.json
generated
2554
komga-webui/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -9,6 +9,7 @@
|
|||
"lint": "vue-cli-service lint --mode production"
|
||||
},
|
||||
"dependencies": {
|
||||
"@d-i-t-a/reader": "https://github.com/gotson/R2D2BC.git#fork",
|
||||
"@saekitominaga/isbn-verify": "^2.0.1",
|
||||
"axios": "^1.5.0",
|
||||
"chart.js": "^2.9.4",
|
||||
|
|
|
|||
56
komga-webui/src/components/TocList.vue
Normal file
56
komga-webui/src/components/TocList.vue
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<template>
|
||||
<v-list v-if="toc"
|
||||
expand
|
||||
>
|
||||
<template v-for="(t, i) in toc">
|
||||
<v-list-group v-if="t.children"
|
||||
:key="i"
|
||||
no-action
|
||||
>
|
||||
<template v-slot:activator>
|
||||
<toc-list-item :item="t" @goto="goto" class="ps-0"/>
|
||||
</template>
|
||||
|
||||
<template v-for="(child, i) in t.children">
|
||||
<toc-list-item :key="i"
|
||||
:item="child"
|
||||
@goto="goto"
|
||||
:class="`ms-${(child.level - 1) * 4}`"
|
||||
/>
|
||||
</template>
|
||||
</v-list-group>
|
||||
|
||||
<!-- Single item -->
|
||||
<template v-else>
|
||||
<toc-list-item :key="i" :item="t" @goto="goto"/>
|
||||
</template>
|
||||
</template>
|
||||
</v-list>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue, {PropType} from 'vue'
|
||||
import {TocEntry} from '@/types/epub'
|
||||
import TocListItem from '@/components/TocListItem.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'TocList',
|
||||
components: {TocListItem},
|
||||
data: () => ({}),
|
||||
props: {
|
||||
toc: {
|
||||
type: Array as PropType<TocEntry>[],
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
goto(element: TocEntry) {
|
||||
this.$emit('goto', element)
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
46
komga-webui/src/components/TocListItem.vue
Normal file
46
komga-webui/src/components/TocListItem.vue
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<v-list-item link>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title
|
||||
@click.stop="goto(item)"
|
||||
:title="item.title"
|
||||
style="width: 100px"
|
||||
:class="item.current ? 'primary--text' : ''"
|
||||
:style="item.href ? 'cursor: pointer' : undefined"
|
||||
>
|
||||
<v-badge
|
||||
:value="!!item.current"
|
||||
color="primary"
|
||||
dot
|
||||
left
|
||||
offset-y="0"
|
||||
offset-x="-5"
|
||||
/>
|
||||
{{ item.title }}
|
||||
</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue, {PropType} from 'vue'
|
||||
import {TocEntry} from '@/types/epub'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'TocListItem',
|
||||
props: {
|
||||
item: {
|
||||
type: Object as PropType<TocEntry>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
goto(element: TocEntry) {
|
||||
this.$emit('goto', element)
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<svg style="width:24px;height:24px" viewBox="0 0 24 24" class="custom-icon">
|
||||
<path
|
||||
d="M 4 3.5 L 4 8.1152344 L 1.5 8.1152344 L 5 11.615234 L 8.5 8.1152344 L 6 8.1152344 L 6 3.5 L 4 3.5 z M 10 5 L 10 7 L 22 7 L 22 5 L 10 5 z M 9.9980469 9.0410156 L 9.9980469 11.041016 L 21.998047 11.041016 L 21.998047 9.0410156 L 9.9980469 9.0410156 z M 5 12.390625 L 1.5 15.890625 L 4 15.890625 L 4 20.5 L 6 20.5 L 6 15.890625 L 8.5 15.890625 L 5 12.390625 z M 10 13 L 10 15 L 22 15 L 22 13 L 10 13 z M 10 17 L 10 19 L 22 19 L 22 17 L 10 17 z "/>
|
||||
<!-- account -->
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'IconFormatLineSpacingDown',
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.custom-icon {
|
||||
fill: currentColor
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,19 +1,29 @@
|
|||
import {BookFormat} from '@/types/komga-books'
|
||||
import {lowerCase} from 'lodash'
|
||||
|
||||
export function getBookFormatFromMediaType (mediaType: string): BookFormat {
|
||||
export function getBookFormatFromMediaType(mediaType: string): BookFormat {
|
||||
switch (mediaType) {
|
||||
case 'application/x-rar-compressed':
|
||||
case 'application/x-rar-compressed; version=4':
|
||||
return { type: 'CBR', color: '#03A9F4' }
|
||||
return {type: 'CBR', color: '#03A9F4'}
|
||||
case 'application/zip':
|
||||
return { type: 'CBZ', color: '#4CAF50' }
|
||||
return {type: 'CBZ', color: '#4CAF50'}
|
||||
case 'application/pdf':
|
||||
return { type: 'PDF', color: '#FF5722' }
|
||||
return {type: 'PDF', color: '#FF5722'}
|
||||
case 'application/epub+zip':
|
||||
return { type: 'EPUB', color: '#ff5ab1' }
|
||||
return {type: 'EPUB', color: '#ff5ab1'}
|
||||
case 'application/x-rar-compressed; version=5':
|
||||
return { type: 'RAR5', color: '#000000' }
|
||||
return {type: 'RAR5', color: '#000000'}
|
||||
default:
|
||||
return { type: mediaType, color: '#000000' }
|
||||
return {type: mediaType, color: '#000000'}
|
||||
}
|
||||
}
|
||||
|
||||
export function getBookReadRouteFromMediaProfile(mediaProfile: string): string {
|
||||
switch (lowerCase(mediaProfile)) {
|
||||
case 'epub':
|
||||
return 'read-epub'
|
||||
default:
|
||||
return 'read-book'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
55
komga-webui/src/functions/shortcuts/epubreader.ts
Normal file
55
komga-webui/src/functions/shortcuts/epubreader.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import {Shortcut} from '@/types/shortcuts'
|
||||
|
||||
export const shortcutsD2Reader = [
|
||||
new Shortcut('epubreader.shortcuts.previous',
|
||||
() => {
|
||||
}, 'ArrowLeft', '←'),
|
||||
new Shortcut('epubreader.shortcuts.next',
|
||||
() => {
|
||||
}, 'ArrowRight', '→'),
|
||||
new Shortcut('epubreader.shortcuts.previous',
|
||||
() => {
|
||||
}, 'CTRL+Space', 'CTRL + SPACE'),
|
||||
new Shortcut('epubreader.shortcuts.next',
|
||||
() => {
|
||||
}, 'Space', 'SPACE'),
|
||||
]
|
||||
|
||||
export const epubShortcutsSettings = [
|
||||
new Shortcut('epubreader.shortcuts.scroll',
|
||||
(ctx: any) => ctx.changeLayout(true)
|
||||
, 'v'),
|
||||
new Shortcut('epubreader.shortcuts.cycle_pagination',
|
||||
(ctx: any) => ctx.cyclePagination()
|
||||
, 'p'),
|
||||
new Shortcut('epubreader.shortcuts.cycle_viewing_theme',
|
||||
(ctx: any) => ctx.cycleViewingTheme()
|
||||
, 'a'),
|
||||
new Shortcut('epubreader.shortcuts.font_size_increase',
|
||||
(ctx: any) => ctx.changeFontSize(true)
|
||||
, '+'),
|
||||
new Shortcut('epubreader.shortcuts.font_size_decrease',
|
||||
(ctx: any) => ctx.changeFontSize(false)
|
||||
, '-'),
|
||||
new Shortcut('bookreader.shortcuts.fullscreen',
|
||||
(ctx: any) => ctx.switchFullscreen()
|
||||
, 'f'),
|
||||
]
|
||||
|
||||
export const epubShortcutsMenus = [
|
||||
new Shortcut('bookreader.shortcuts.show_hide_toolbars',
|
||||
(ctx: any) => ctx.toggleToolbars()
|
||||
, 'm'),
|
||||
new Shortcut('bookreader.shortcuts.show_hide_settings',
|
||||
(ctx: any) => ctx.toggleSettings()
|
||||
, 's'),
|
||||
new Shortcut('epubreader.shortcuts.show_hide_toc',
|
||||
(ctx: any) => ctx.toggleTableOfContents()
|
||||
, 't'),
|
||||
new Shortcut('bookreader.shortcuts.show_hide_help',
|
||||
(ctx: any) => ctx.toggleHelp()
|
||||
, 'h'),
|
||||
new Shortcut('bookreader.shortcuts.close',
|
||||
(ctx: any) => ctx.closeDialog()
|
||||
, 'Escape'),
|
||||
]
|
||||
24
komga-webui/src/functions/toc.ts
Normal file
24
komga-webui/src/functions/toc.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import {TocEntry} from '@/types/epub'
|
||||
|
||||
export function flattenToc(toc: TocEntry[], maxLevel: number = 0, currentLevel: number = 0, active?: string): TocEntry[] {
|
||||
const r: TocEntry[] = []
|
||||
|
||||
for (const item of toc) {
|
||||
const flat = Object.assign({}, item, {level: currentLevel}) as TocEntry
|
||||
if (flat.href === active) flat.current = true
|
||||
const children: TocEntry[] = []
|
||||
if (item.children) {
|
||||
children.push(...flattenToc(item.children, maxLevel, currentLevel + 1, active))
|
||||
}
|
||||
if (currentLevel >= maxLevel) {
|
||||
delete flat.children
|
||||
r.push(flat)
|
||||
r.push(...children)
|
||||
} else {
|
||||
flat.children = children.length > 0 ? children : undefined
|
||||
r.push(flat)
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
|
@ -38,6 +38,10 @@ export function bookPageThumbnailUrl(bookId: string, page: number): string {
|
|||
return `${urls.originNoSlash}/api/v1/books/${bookId}/pages/${page}/thumbnail`
|
||||
}
|
||||
|
||||
export function bookManifestUrl(bookId: string): string {
|
||||
return `${urls.originNoSlash}/api/v1/books/${bookId}/manifest`
|
||||
}
|
||||
|
||||
export function seriesFileUrl(seriesId: string): string {
|
||||
return `${urls.originNoSlash}/api/v1/series/${seriesId}/file`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -636,6 +636,18 @@
|
|||
"HARDLINK": "Hardlink/Copy Files",
|
||||
"MOVE": "Move Files"
|
||||
},
|
||||
"epubreader": {
|
||||
"appearances": {
|
||||
"day": "Day",
|
||||
"night": "Night",
|
||||
"sepia": "Sepia"
|
||||
},
|
||||
"column_count": {
|
||||
"auto": "Auto",
|
||||
"one": "One",
|
||||
"two": "Two"
|
||||
}
|
||||
},
|
||||
"historical_event_type": {
|
||||
"BookConverted": "Book converted",
|
||||
"BookFileDeleted": "Book file deleted",
|
||||
|
|
@ -688,6 +700,29 @@
|
|||
"XLARGE": "X-Large (1200px)"
|
||||
}
|
||||
},
|
||||
"epubreader": {
|
||||
"settings": {
|
||||
"column_count": "Column count",
|
||||
"layout": "Layout",
|
||||
"layout_paginated": "Paginated",
|
||||
"layout_scroll": "Scroll",
|
||||
"page_margins": "Page margins",
|
||||
"viewing_theme": "Viewing theme"
|
||||
},
|
||||
"shortcuts": {
|
||||
"cycle_pagination": "Cycle column count",
|
||||
"cycle_viewing_theme": "Cycle viewing theme",
|
||||
"font_size_decrease": "Decrease font size",
|
||||
"font_size_increase": "Increase font size",
|
||||
"menus": "Menus",
|
||||
"next": "Forward",
|
||||
"previous": "Back",
|
||||
"reader_navigation": "Reader Navigation",
|
||||
"scroll": "Change layout to scroll",
|
||||
"settings": "Settings",
|
||||
"show_hide_toc": "Show/hide table of contents"
|
||||
}
|
||||
},
|
||||
"error_codes": {
|
||||
"ERR_1000": "File could not be accessed during analysis",
|
||||
"ERR_1001": "Media type is not supported",
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export const persistedModule: Module<any, any> = {
|
|||
animations: true,
|
||||
background: '',
|
||||
},
|
||||
epubreader: {},
|
||||
browsingPageSize: undefined as unknown as number,
|
||||
collection: {
|
||||
filter: {},
|
||||
|
|
@ -105,6 +106,9 @@ export const persistedModule: Module<any, any> = {
|
|||
setWebreaderBackground(state, val) {
|
||||
state.webreader.background = val
|
||||
},
|
||||
setEpubreaderSettings(state, val) {
|
||||
state.epubreader = val
|
||||
},
|
||||
setBrowsingPageSize(state, val) {
|
||||
state.browsingPageSize = val
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import colors from 'vuetify/lib/util/colors'
|
|||
|
||||
import {Touch} from 'vuetify/lib/directives'
|
||||
import i18n from '@/i18n'
|
||||
import IconFormatLineSpacingDown from '@/components/icons/IconFormatLineSpacingDown.vue'
|
||||
|
||||
Vue.use(Vuetify, {
|
||||
directives: {
|
||||
|
|
@ -16,6 +17,11 @@ Vue.use(Vuetify, {
|
|||
export default new Vuetify({
|
||||
icons: {
|
||||
iconfont: 'mdi',
|
||||
values: {
|
||||
formatLineSpacingDown: {
|
||||
component: IconFormatLineSpacingDown,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
lang: {
|
||||
|
|
|
|||
|
|
@ -263,6 +263,12 @@ const router = new Router({
|
|||
component: () => import(/* webpackChunkName: "read-book" */ './views/BookReader.vue'),
|
||||
props: (route) => ({bookId: route.params.bookId}),
|
||||
},
|
||||
{
|
||||
path: '/book/:bookId/read-epub',
|
||||
name: 'read-epub',
|
||||
component: () => import(/* webpackChunkName: "read-epub" */ './views/EpubReader.vue'),
|
||||
props: (route) => ({bookId: route.params.bookId}),
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
name: 'notfound',
|
||||
|
|
@ -281,7 +287,7 @@ const router = new Router({
|
|||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (!['read-book', 'browse-book', 'browse-series'].includes(<string>to.name)) {
|
||||
if (!['read-book', 'read-epub', 'browse-book', 'browse-series'].includes(<string>to.name)) {
|
||||
document.title = 'Komga'
|
||||
}
|
||||
|
||||
|
|
|
|||
125
komga-webui/src/styles/r2d2bc/popover.css.resource
Normal file
125
komga-webui/src/styles/r2d2bc/popover.css.resource
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright 2018-2020 DITA (AM Consulting LLC)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Developed on behalf of: DITA
|
||||
* Licensed to: CAST under one or more contributor license agreements.
|
||||
*/
|
||||
|
||||
.d2-popover {
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
top: 0;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
width: 80%;
|
||||
background: #fafafa;
|
||||
opacity: 0;
|
||||
border-radius: 0.5em;
|
||||
border: 1px solid #c3c3c3;
|
||||
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3);
|
||||
line-height: 0;
|
||||
-webkit-transition-property: opacity, -webkit-transform;
|
||||
transition-property: opacity, transform;
|
||||
-webkit-transition-duration: 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
-webkit-transition-timing-function: ease;
|
||||
transition-timing-function: ease;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) !important;
|
||||
}
|
||||
.d2-popover.is-active {
|
||||
-webkit-transform: scale(1) translateZ(0);
|
||||
transform: scale(1) translateZ(0);
|
||||
opacity: 0.97;
|
||||
}
|
||||
.d2-popover.is-scrollable:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0.3375em;
|
||||
left: 0.3375em;
|
||||
z-index: 14;
|
||||
display: block;
|
||||
height: 0.78125em;
|
||||
width: 0.625em;
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTJweCIgaGVpZ2h0PSIxNXB4IiB2aWV3Qm94PSIwIDAgMTIgMTUiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pbllNaW4iPgogICAgPGcgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9IkFycm93IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxLjAwMDAwMCwgMS4wMDAwMDApIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiPgogICAgICAgICAgICA8cGF0aCBkPSJNNSwwIEw1LDExLjUiIGlkPSJMaW5lIj48L3BhdGg+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0wLjUsNy41IEw1LjAyNzY5Mjc5LDEyLjAyNzY5MjgiIGlkPSJMaW5lIj48L3BhdGg+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik00LjUsNy41IEw5LjAyNzY5Mjc5LDEyLjAyNzY5MjgiIGlkPSJMaW5lLTIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDcuMDAwMDAwLCAxMC4wMDAwMDApIHNjYWxlKC0xLCAxKSB0cmFuc2xhdGUoLTcuMDAwMDAwLCAtMTAuMDAwMDAwKSAiPjwvcGF0aD4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPgo=");
|
||||
background-size: cover;
|
||||
opacity: 0.1;
|
||||
transition-properties: opacity;
|
||||
-webkit-transition-duration: 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
-webkit-transition-timing-function: ease;
|
||||
transition-timing-function: ease;
|
||||
}
|
||||
.d2-popover.is-scrollable .d2-d2-popover__wrapper:before, .d2-d2-popover.is-scrollable .d2-d2-popover__wrapper:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 12;
|
||||
left: 0;
|
||||
}
|
||||
.d2-d2-popover.is-scrollable .d2-d2-popover__wrapper:before {
|
||||
top: -1px;
|
||||
height: 1.1em;
|
||||
border-radius: 0.5em 0.5em 0 0;
|
||||
background-image: -webkit-linear-gradient(top, #fafafa 50%, rgba(250, 250, 250, 0) 100%);
|
||||
background-image: linear-gradient(to bottom, #fafafa 50%, rgba(250, 250, 250, 0) 100%);
|
||||
}
|
||||
.d2-popover.is-scrollable .d2-d2-popover__wrapper:after {
|
||||
bottom: -1px;
|
||||
height: 1.2em;
|
||||
border-radius: 0 0 0.5em 0.5em;
|
||||
background-image: -webkit-linear-gradient(bottom, #fafafa 50%, rgba(250, 250, 250, 0) 100%);
|
||||
background-image: linear-gradient(to top, #fafafa 50%, rgba(250, 250, 250, 0) 100%);
|
||||
}
|
||||
.d2-popover.is-scrollable ::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.d2-popover.is-fully-scrolled:after, .d2-popover.is-fully-scrolled:before {
|
||||
opacity: 0;
|
||||
-webkit-transition-delay: 0;
|
||||
transition-delay: 0;
|
||||
}
|
||||
|
||||
.d2-popover-wrapper {
|
||||
position: relative;
|
||||
z-index: 14;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
box-sizing: inherit;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
background-color: #fafafa;
|
||||
border-radius: 0.5em;
|
||||
line-height: 0;
|
||||
/*max-width: 353px;*/
|
||||
}
|
||||
.d2-popover-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 8;
|
||||
display: inline-block;
|
||||
max-height: 90vh;
|
||||
padding: 1.1em 1.3em 1.2em;
|
||||
box-sizing: inherit;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
background: #fafafa;
|
||||
border-radius: 0.5em;
|
||||
-webkit-font-smoothing: subpixel-antialiased;
|
||||
line-height: normal;
|
||||
}
|
||||
121
komga-webui/src/styles/r2d2bc/popup.css.resource
Normal file
121
komga-webui/src/styles/r2d2bc/popup.css.resource
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright 2018-2020 DITA (AM Consulting LLC)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Developed on behalf of: DITA
|
||||
* Licensed to: CAST under one or more contributor license agreements.
|
||||
*/
|
||||
|
||||
.d2-popup {
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
top: 0;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
max-width: 90%;
|
||||
background: #fafafa;
|
||||
opacity: 0;
|
||||
border-radius: 0.5em;
|
||||
border: 1px solid #c3c3c3;
|
||||
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3);
|
||||
line-height: 0;
|
||||
-webkit-transition-property: opacity, -webkit-transform;
|
||||
transition-property: opacity, transform;
|
||||
-webkit-transition-duration: 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
-webkit-transition-timing-function: ease;
|
||||
transition-timing-function: ease;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) !important;
|
||||
}
|
||||
.d2-popup.is-active {
|
||||
-webkit-transform: scale(1) translateZ(0);
|
||||
transform: scale(1) translateZ(0);
|
||||
opacity: 0.97;
|
||||
}
|
||||
.d2-popup.is-scrollable:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0.3375em;
|
||||
left: 0.3375em;
|
||||
z-index: 14;
|
||||
display: block;
|
||||
height: 0.78125em;
|
||||
width: 0.625em;
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTJweCIgaGVpZ2h0PSIxNXB4IiB2aWV3Qm94PSIwIDAgMTIgMTUiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pbllNaW4iPgogICAgPGcgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9IkFycm93IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxLjAwMDAwMCwgMS4wMDAwMDApIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiPgogICAgICAgICAgICA8cGF0aCBkPSJNNSwwIEw1LDExLjUiIGlkPSJMaW5lIj48L3BhdGg+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0wLjUsNy41IEw1LjAyNzY5Mjc5LDEyLjAyNzY5MjgiIGlkPSJMaW5lIj48L3BhdGg+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik00LjUsNy41IEw5LjAyNzY5Mjc5LDEyLjAyNzY5MjgiIGlkPSJMaW5lLTIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDcuMDAwMDAwLCAxMC4wMDAwMDApIHNjYWxlKC0xLCAxKSB0cmFuc2xhdGUoLTcuMDAwMDAwLCAtMTAuMDAwMDAwKSAiPjwvcGF0aD4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPgo=");
|
||||
background-size: cover;
|
||||
opacity: 0.1;
|
||||
transition-properties: opacity;
|
||||
-webkit-transition-duration: 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
-webkit-transition-timing-function: ease;
|
||||
transition-timing-function: ease;
|
||||
}
|
||||
.d2-popup.is-scrollable .d2-popup__wrapper:before, .d2-popup.is-scrollable .d2-popup__wrapper:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 12;
|
||||
left: 0;
|
||||
}
|
||||
.d2-popup.is-scrollable .d2-popup__wrapper:before {
|
||||
top: -1px;
|
||||
height: 1.1em;
|
||||
border-radius: 0.5em 0.5em 0 0;
|
||||
background-image: -webkit-linear-gradient(top, #fafafa 50%, rgba(250, 250, 250, 0) 100%);
|
||||
background-image: linear-gradient(to bottom, #fafafa 50%, rgba(250, 250, 250, 0) 100%);
|
||||
}
|
||||
.d2-popup.is-scrollable .d2-popup__wrapper:after {
|
||||
bottom: -1px;
|
||||
height: 1.2em;
|
||||
border-radius: 0 0 0.5em 0.5em;
|
||||
background-image: -webkit-linear-gradient(bottom, #fafafa 50%, rgba(250, 250, 250, 0) 100%);
|
||||
background-image: linear-gradient(to top, #fafafa 50%, rgba(250, 250, 250, 0) 100%);
|
||||
}
|
||||
.d2-popup.is-scrollable ::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.d2-popup.is-fully-scrolled:after, .d2-popup.is-fully-scrolled:before {
|
||||
opacity: 0;
|
||||
-webkit-transition-delay: 0;
|
||||
transition-delay: 0;
|
||||
}
|
||||
|
||||
.d2-popup-wrapper {
|
||||
position: relative;
|
||||
z-index: 14;
|
||||
width: 22em;
|
||||
display: inline-block;
|
||||
box-sizing: inherit;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
background-color: #fafafa;
|
||||
border-radius: 0.5em;
|
||||
/*line-height: 1;*/
|
||||
max-width: 353px;
|
||||
}
|
||||
.d2-popup-content {
|
||||
position: relative;
|
||||
z-index: 8;
|
||||
display: inline-block;
|
||||
max-height: 15em;
|
||||
padding: 1.1em 1.3em 1.2em;
|
||||
box-sizing: inherit;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
background: #fafafa;
|
||||
border-radius: 0.5em;
|
||||
-webkit-font-smoothing: subpixel-antialiased;
|
||||
line-height: normal !important;
|
||||
}
|
||||
261
komga-webui/src/styles/r2d2bc/style.css.resource
Normal file
261
komga-webui/src/styles/r2d2bc/style.css.resource
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
* Copyright 2018-2020 DITA (AM Consulting LLC)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Developed on behalf of: DITA
|
||||
* Licensed to: CAST under one or more contributor license agreements.
|
||||
*/
|
||||
|
||||
:root {
|
||||
--RS__highlightColor: rgba(255, 255, 0, 0.5);
|
||||
--RS__highlightMixBlendMode: multiply;
|
||||
--RS__highlightHoverColor: rgba(255, 255, 0, 0.75);
|
||||
--RS__underlineBorderColor: rgba(255, 255, 0, 1);
|
||||
--RS__underlineHoverColor: rgba(255, 255, 0, 0.1);
|
||||
|
||||
--RS__definitionsColor: rgba(255, 111, 111, 0.5);
|
||||
--RS__definitionsMixBlendMode: multiply;
|
||||
--RS__definitionsHoverColor: rgba(255, 111, 111, 0.75);
|
||||
--RS__definitionsUnderlineBorderColor: rgba(255, 111, 111, 1);
|
||||
--RS__definitionsUnderlineHoverColor: rgba(255, 111, 111, 0.1);
|
||||
|
||||
--USER__maxMediaHeight: 95vh;
|
||||
}
|
||||
|
||||
.R2_CLASS_HIGHLIGHT_AREA[data-marker="0"] {
|
||||
background-color: var(--RS__highlightColor) !important;
|
||||
mix-blend-mode: var(--RS__highlightMixBlendMode) !important;
|
||||
z-index: 1;
|
||||
}
|
||||
.R2_CLASS_HIGHLIGHT_AREA[data-marker="0"]:hover,
|
||||
.R2_CLASS_HIGHLIGHT_AREA[data-marker="0"].hover {
|
||||
background-color: var(--RS__highlightHoverColor) !important;
|
||||
}
|
||||
.R2_CLASS_HIGHLIGHT_AREA[data-marker="1"] {
|
||||
border-bottom: 2px solid var(--RS__underlineBorderColor);
|
||||
mix-blend-mode: var(--RS__highlightMixBlendMode) !important;
|
||||
z-index: 1;
|
||||
}
|
||||
.R2_CLASS_HIGHLIGHT_AREA[data-marker="1"]:hover,
|
||||
.R2_CLASS_HIGHLIGHT_AREA[data-marker="1"].hover {
|
||||
background-color: var(--RS__underlineHoverColor) !important;
|
||||
}
|
||||
|
||||
#R2_ID_DEFINITIONS_CONTAINER .R2_CLASS_HIGHLIGHT_AREA[data-marker="0"] {
|
||||
background-color: var(--RS__definitionsColor) !important;
|
||||
mix-blend-mode: var(--RS__definitionsMixBlendMode) !important;
|
||||
z-index: 1;
|
||||
}
|
||||
#R2_ID_DEFINITIONS_CONTAINER .R2_CLASS_HIGHLIGHT_AREA[data-marker="0"]:hover,
|
||||
#R2_ID_DEFINITIONS_CONTAINER .R2_CLASS_HIGHLIGHT_AREA[data-marker="0"].hover {
|
||||
background-color: var(--RS__definitionsHoverColor) !important;
|
||||
}
|
||||
#R2_ID_DEFINITIONS_CONTAINER .R2_CLASS_HIGHLIGHT_AREA[data-marker="1"] {
|
||||
border-bottom: 2px solid var(--RS__definitionsUnderlineBorderColor);
|
||||
mix-blend-mode: var(--RS__definitionsMixBlendMode) !important;
|
||||
z-index: 1;
|
||||
}
|
||||
#R2_ID_DEFINITIONS_CONTAINER .R2_CLASS_HIGHLIGHT_AREA[data-marker="1"]:hover,
|
||||
#R2_ID_DEFINITIONS_CONTAINER .R2_CLASS_HIGHLIGHT_AREA[data-marker="1"].hover {
|
||||
background-color: var(--RS__definitionsUnderlineHoverColor) !important;
|
||||
}
|
||||
|
||||
:root[style] .r2-mo-active,
|
||||
:root .r2-mo-active {
|
||||
background-color: yellow !important;
|
||||
color: black !important;
|
||||
}
|
||||
:root[style*="readium-night-on"] .r2-mo-active {
|
||||
background-color: #333333 !important;
|
||||
color: white !important;
|
||||
}
|
||||
:root[style*="readium-sepia-on"] .r2-mo-active {
|
||||
background-color: silver !important;
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
.orange {
|
||||
background: rgba(255, 165, 0, 0.5) !important;
|
||||
border-bottom: solid 2px rgb(255, 165, 0) !important;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
.red {
|
||||
background: rgba(255,0,0,0.5) !important;
|
||||
border-bottom: solid 2px rgb(255,0,0) !important;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
.blue {
|
||||
background: rgba(0,0,255,0.5) !important;
|
||||
border-bottom: solid 2px rgb(0,0,255) !important;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
.purple {
|
||||
background: rgba(102,0,153,0.5) !important;
|
||||
border-bottom: solid 2px rgb(102,0,153) !important;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
.green {
|
||||
background: rgba(0,102,0,0.5) !important;
|
||||
border-bottom: solid 2px rgb(0,102,0) !important;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
.gray {
|
||||
background: rgba(85,85,85,0.5) !important;
|
||||
border-bottom: solid 2px rgb(85,85,85) !important;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
.orangetext {
|
||||
color: rgb(255, 165, 0) !important;
|
||||
}
|
||||
.redtext {
|
||||
color: rgb(255,0,0) !important;
|
||||
}
|
||||
.bluetext {
|
||||
color: rgb(0,0,255) !important;
|
||||
}
|
||||
.purpletext {
|
||||
color: rgb(102,0,153) !important;
|
||||
}
|
||||
.greentext {
|
||||
color: rgb(0,102,0) !important;
|
||||
}
|
||||
.graytext {
|
||||
color: rgb(85,85,85) !important;
|
||||
}
|
||||
[data-tts-current-word="true"][data-tts-color="orange"] {
|
||||
background: rgba(255, 165, 0, 0.5) !important;
|
||||
border-bottom: solid 2px rgb(255, 165, 0) !important;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
[data-tts-current-line="true"][data-tts-color="orange"] {
|
||||
background: rgba(255, 165, 0, 0.5) !important;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
[data-tts-current-word="true"][data-tts-color="red"] {
|
||||
background: rgba(255,0,0,0.5) !important;
|
||||
border-bottom: solid 2px rgba(255,0,0) !important;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
[data-tts-current-line="true"][data-tts-color="red"] {
|
||||
background: rgba(255,0,0,0.5) !important;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[data-tts-current-word="true"][data-tts-color="blue"] {
|
||||
background: rgba(0,0,255,0.5) !important;
|
||||
border-bottom: solid 2px rgba(0,0,255) !important;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
[data-tts-current-line="true"][data-tts-color="blue"] {
|
||||
background: rgba(0,0,255,0.5) !important;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[data-tts-current-word="true"][data-tts-color="purple"] {
|
||||
background: rgba(102,0,153,0.5) !important;
|
||||
border-bottom: solid 2px rgba(102,0,153) !important;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
[data-tts-current-line="true"][data-tts-color="purple"] {
|
||||
background: rgba(102,0,153,0.5) !important;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[data-tts-current-word="true"][data-tts-color="green"] {
|
||||
background: rgba(0,102,0,0.5) !important;
|
||||
border-bottom: solid 2px rgba(0,102,0) !important;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
[data-tts-current-line="true"][data-tts-color="green"] {
|
||||
background: rgba(0,102,0,0.5) !important;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[data-tts-current-word="true"][data-tts-color="gray"] {
|
||||
background: rgba(85,85,85,0.5) !important;
|
||||
border-bottom: solid 2px rgba(85,85,85) !important;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
[data-tts-current-line="true"][data-tts-color="gray"] {
|
||||
background: rgba(85,85,85,0.5) !important;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.icon:hover .icon-tooltip {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.icon-tooltip {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
/*top: 50%;*/
|
||||
transform: translate(-50%, -105%);
|
||||
background: #DADADA;
|
||||
padding: 0.25rem 0.5rem;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 22rem;
|
||||
min-width: 100px;
|
||||
z-index: 1000;
|
||||
border: 1px dotted #5b5852;
|
||||
}
|
||||
|
||||
.icon-tooltip:before, .icon-tooltip:after {
|
||||
content: '';
|
||||
display: block;
|
||||
}
|
||||
|
||||
.icon-tooltip:before {
|
||||
content: " ";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 5px solid #5b5852;
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
img {
|
||||
max-height: var(--USER__maxMediaHeight) !important;
|
||||
}
|
||||
|
||||
#R2_ID_GUTTER_RIGHT_CONTAINER {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -20px;
|
||||
}
|
||||
29
komga-webui/src/styles/readium/LICENSE
Normal file
29
komga-webui/src/styles/readium/LICENSE
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2017, Readium
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
905
komga-webui/src/styles/readium/ReadiumCSS-after.css.resource
Normal file
905
komga-webui/src/styles/readium/ReadiumCSS-after.css.resource
Normal file
|
|
@ -0,0 +1,905 @@
|
|||
/* Readium CSS
|
||||
Config module
|
||||
|
||||
A file allowing implementers to customize flags for reading modes,
|
||||
user settings, etc.
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
/* Custom medias
|
||||
Syntax: @custom-media --variable (prop: value) */
|
||||
|
||||
/* Responsive columns
|
||||
The minimum width for which responsive columns (2 -> 1 and vice versa,
|
||||
depending on the current font-size) must be enabled */
|
||||
|
||||
/* Mobile columns
|
||||
The minimum and maximum width for mobile devices.
|
||||
We’re forcing the landscape orientation by default,
|
||||
and must still investigate large tablets (iPad Pro, Surface Pro 3, etc.). */
|
||||
|
||||
/* Custom selectors
|
||||
Syntax: @custom-selector :--variable selector
|
||||
The selectors you will use for flags/switches
|
||||
You can alternatively use classes or custom data-* attributes */
|
||||
|
||||
/* User view = paged | scrolled */
|
||||
|
||||
/* Font-family override */
|
||||
|
||||
/* Advanced settings */
|
||||
|
||||
/* Reading Modes */
|
||||
|
||||
/* Filters (images) */
|
||||
|
||||
/* Accessibility normalization */
|
||||
|
||||
/* Accessibility font. You can add selectors, using “, ” as a separator, if you have multiple fonts */
|
||||
|
||||
/* Direction i.e. ltr and rtl */
|
||||
|
||||
/* Readium CSS
|
||||
Pagination module
|
||||
|
||||
A set of styles to paginate ePublications
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
/* Config */
|
||||
|
||||
/* Columns are responsive by default, even if column-width is set in pixels,
|
||||
which means two-page spread will switch to single page depending on current font-size.
|
||||
If you want more control, I’m afraid you’ll have to update colWidth/colGap dynamically,
|
||||
which is how a significant amount of RS do at the moment. */
|
||||
|
||||
/* Default for smartphone portrait (small screens) */
|
||||
|
||||
:root {
|
||||
/* Your columns’ width floor */
|
||||
--RS__colWidth: 45em; /* The width at which we’ll switch to 2 columns by default. PS: you can’t set it in rem */
|
||||
|
||||
/* Ideal number of columns (depending on columns’ width floor) */
|
||||
--RS__colCount: 1;
|
||||
|
||||
/* Gap between columns (in pixels so that it won’t resize with font-size) */
|
||||
--RS__colGap: 0;
|
||||
|
||||
/* Optimal line-length (rem will take :root font-size into account, whatever the body’s font-size) */
|
||||
--RS__maxLineLength: 40rem;
|
||||
|
||||
/* Default page horizontal margins (in pixels so that it won’t resize with font-size) */
|
||||
--RS__pageGutter: 20px; /* See if colGap and pageGutter can be the same var */
|
||||
}
|
||||
|
||||
/* Reset page margins for Forward compatibility */
|
||||
|
||||
@page {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* :root selector has same specificity as a class i.e. 0010
|
||||
We might have to change that to html / context
|
||||
-> https://css-tricks.com/almanac/selectors/r/root/ */
|
||||
|
||||
:root {
|
||||
|
||||
/* In case you use left position to scroll, can be removed if using transform: translateX() */
|
||||
position: relative;
|
||||
|
||||
-webkit-column-width: var(--RS__colWidth);
|
||||
-moz-column-width: var(--RS__colWidth);
|
||||
column-width: var(--RS__colWidth);
|
||||
|
||||
/* Init pagination */
|
||||
/* TODO: document columns’ logic cos it might feel weird at first */
|
||||
-webkit-column-count: var(--RS__colCount);
|
||||
-moz-column-count: var(--RS__colCount);
|
||||
column-count: var(--RS__colCount);
|
||||
|
||||
-webkit-column-gap: var(--RS__colGap);
|
||||
-moz-column-gap: var(--RS__colGap);
|
||||
column-gap: var(--RS__colGap);
|
||||
|
||||
/* Default is balance and we want columns to be filled entirely (100vh) */
|
||||
-moz-column-fill: auto;
|
||||
column-fill: auto;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
max-width: 100%;
|
||||
max-height: 100vh;
|
||||
min-width: 100%;
|
||||
min-height: 100vh;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
|
||||
/* Column size will depend on this if we want to make it responsive */
|
||||
font-size: 100% !important;
|
||||
|
||||
-webkit-text-size-adjust: 100%;
|
||||
|
||||
/* Switch to newer box model (not inherited by authors’ styles) */
|
||||
box-sizing: border-box;
|
||||
|
||||
/* Fix bug for older Chrome */
|
||||
-webkit-perspective: 1;
|
||||
/* Prevents options pop-up when long tap in webkit */
|
||||
-webkit-touch-callout: none;
|
||||
}
|
||||
|
||||
body {
|
||||
/* overflow: hidden; bugfix: contents won’t paginate in Firefox and one sample in Safari */
|
||||
width: 100%;
|
||||
|
||||
/* Limit line-length but we have to reset when 2 columns and control the viewport.
|
||||
By using max-width + margin auto, margins will shrink when font-size increases,
|
||||
which is what would be expected in terms of typography. */
|
||||
max-width: var(--RS__maxLineLength) !important;
|
||||
padding: 0 var(--RS__pageGutter) !important;
|
||||
margin: 0 auto !important;
|
||||
|
||||
/* We need a minimum padding on body so that descandants/ascendants in italic/script are not cut-off.
|
||||
Drawback: we have to use border-box so that it doesn’t screw the box model,
|
||||
which means it impacts colWidth and max-width */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* We’ll now redefine margins and columns depending on the minimum width available
|
||||
The goal is having the simplest model possible and avoid targeting devices */
|
||||
|
||||
/* Smartphone landscape */
|
||||
|
||||
@media screen and (min-width: 35em) {
|
||||
:root {
|
||||
--RS__pageGutter: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tablet portrait */
|
||||
|
||||
@media screen and (min-width: 45em) {
|
||||
:root {
|
||||
--RS__pageGutter: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop + tablet large */
|
||||
|
||||
/* We get the previous settings, we just change the margins */
|
||||
|
||||
@media screen and (min-width: 75em) {
|
||||
:root {
|
||||
--RS__pageGutter: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
/* At this point (80em or so), constraining line length must be done at the web view/iframe level, or by limiting the size of :root itself */
|
||||
|
||||
/* Responsive columns */
|
||||
|
||||
@media screen and (min-width: 60em), screen and (min-device-width: 36em) and (max-device-width: 47em) and (orientation: landscape) {
|
||||
:root {
|
||||
/* The size at which we want 2 columns to switch to 1 (depending on font-size) */
|
||||
--RS__colWidth: 20em; /* 20 * 16 = 320px but 20 * 28 = 560px so it will switch to 1 col @ 175% font-size (user-setting) on an iPad */
|
||||
/* We constrain to 2 columns so that we can never get 3 or 4, etc. */
|
||||
--RS__colCount: 2;
|
||||
--RS__maxLineLength: 39.99rem; /* If we don’t use this, colNumber user setting won’t work in Safari… */
|
||||
}
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
Scroll module
|
||||
|
||||
A set of styles to scroll ePublications
|
||||
This module overrides pagination
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
:root[style*="readium-scroll-on"] {
|
||||
|
||||
/* Reset columns, auto + auto = columns can’t be created */
|
||||
-webkit-columns: auto auto !important;
|
||||
-moz-columns: auto auto !important;
|
||||
columns: auto auto !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
max-width: none !important;
|
||||
max-height: none !important;
|
||||
/* Reset html size so that the user can scroll */
|
||||
min-width: 0 !important;
|
||||
min-height: 0 !important;
|
||||
}
|
||||
|
||||
/* Make sure line-length is limited in all configs */
|
||||
|
||||
:root[style*="readium-scroll-on"] body {
|
||||
--RS__maxLineLength: 40rem !important;
|
||||
|
||||
margin-top: var(--RS__pageGutter) !important;
|
||||
margin-bottom: var(--RS__pageGutter) !important;
|
||||
}
|
||||
|
||||
/* Scroll mode horizontal */
|
||||
|
||||
/* Vertical writing needs body height set */
|
||||
|
||||
/* Do we add a top/bottom margin for body in vertical scroll or not? */
|
||||
|
||||
/* Readium CSS
|
||||
Default highlights
|
||||
|
||||
A stylesheet for user highlights
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
/* User Highlights */
|
||||
|
||||
.readiumCSS-yellow-highlight {
|
||||
background-color: rgba(255, 255, 0, 0.5);
|
||||
}
|
||||
|
||||
.readiumCSS-green-highlight {
|
||||
background-color: rgba(0, 255, 0, 0.5);
|
||||
}
|
||||
|
||||
.readiumCSS-orange-highlight {
|
||||
background-color: rgba(255, 165, 0, 0.5);
|
||||
}
|
||||
|
||||
.readiumCSS-pink-highlight {
|
||||
background-color: rgba(255, 105, 180, 0.5);
|
||||
}
|
||||
|
||||
/* Media overlays */
|
||||
|
||||
.readiumCSS-mo-active-default {
|
||||
color: black !important;
|
||||
background-color: yellow !important;
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
Night mode
|
||||
|
||||
A preset theme for night mode
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
/* CONFIG */
|
||||
|
||||
/* [style*="--USER__appearance"] can be used to increase specificity but performance hit */
|
||||
|
||||
:root[style*="readium-night-on"] {
|
||||
--RS__backgroundColor: #000000;
|
||||
--RS__textColor: #FEFEFE;
|
||||
|
||||
--RS__linkColor: #63caff;
|
||||
--RS__visitedColor: #0099E5;
|
||||
|
||||
/* This can be customized but initial will re-use default value of the browser */
|
||||
--RS__selectionBackgroundColor: #b4d8fe;
|
||||
--RS__selectionTextColor: inherit;
|
||||
}
|
||||
|
||||
/* we don’t need to redeclare bg-color and color for :root since we will inherit and update from day/default mode */
|
||||
|
||||
:root[style*="readium-night-on"] *:not(a) {
|
||||
color: inherit !important;
|
||||
background-color: transparent !important;
|
||||
border-color: currentColor !important;
|
||||
}
|
||||
|
||||
:root[style*="readium-night-on"] svg text {
|
||||
fill: currentColor !important;
|
||||
stroke: none !important;
|
||||
}
|
||||
|
||||
:root[style*="readium-night-on"] a:link,
|
||||
:root[style*="readium-night-on"] a:link * {
|
||||
color: var(--RS__linkColor) !important;
|
||||
}
|
||||
|
||||
:root[style*="readium-night-on"] a:visited,
|
||||
:root[style*="readium-night-on"] a:visited * {
|
||||
color: var(--RS__visitedColor) !important;
|
||||
}
|
||||
|
||||
:root[style*="readium-night-on"] img[class*="gaiji"],
|
||||
:root[style*="readium-night-on"] *[epub\:type~="titlepage"] img:only-child {
|
||||
-webkit-filter: invert(100%);
|
||||
filter: invert(100%);
|
||||
}
|
||||
|
||||
/* Darken all images on user’s demand */
|
||||
|
||||
:root[style*="readium-night-on"][style*="readium-darken-on"] img {
|
||||
-webkit-filter: brightness(80%);
|
||||
filter: brightness(80%);
|
||||
}
|
||||
|
||||
/* Invert all images on user’s demand */
|
||||
|
||||
:root[style*="readium-night-on"][style*="readium-invert-on"] img {
|
||||
-webkit-filter: invert(100%);
|
||||
filter: invert(100%);
|
||||
}
|
||||
|
||||
/* Darken and invert on user’s demand */
|
||||
|
||||
:root[style*="readium-night-on"][style*="readium-darken-on"][style*="readium-invert-on"] img {
|
||||
-webkit-filter: brightness(80%) invert(100%);
|
||||
filter: brightness(80%) invert(100%);
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
Sepia mode
|
||||
|
||||
A preset theme for sepia mode
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
/* CONFIG */
|
||||
|
||||
:root[style*="readium-sepia-on"] {
|
||||
--RS__backgroundColor: #faf4e8;
|
||||
--RS__textColor: #121212;
|
||||
|
||||
--RS__linkColor: #0000EE;
|
||||
--RS__visitedColor: #551A8B;
|
||||
|
||||
/* This can be customized but initial will re-use default value of the browser */
|
||||
--RS__selectionBackgroundColor: #b4d8fe;
|
||||
--RS__selectionTextColor: inherit;
|
||||
|
||||
--RS__maxLineLength: 40.01rem; /* Forcing a reflow in Blink/Webkit so that blend mode can work */
|
||||
}
|
||||
|
||||
/* we don’t need to redeclare bg-color and color for :root since we will inherit and update from day/default mode */
|
||||
|
||||
:root[style*="readium-sepia-on"] body {
|
||||
/* Should be transparent but Chrome bug https://bugs.chromium.org/p/chromium/issues/detail?id=711955&q=mix-blend-mode&colspec=ID%20Pri%20M%20Stars%20ReleaseBlock%20Component%20Status%20Owner%20Summary%20OS%20Modified */
|
||||
|
||||
color: inherit;
|
||||
background-color: var(--RS__backgroundColor);
|
||||
}
|
||||
|
||||
:root[style*="readium-sepia-on"] a:link,
|
||||
:root[style*="readium-sepia-on"] a:link * {
|
||||
color: var(--RS__linkColor);
|
||||
}
|
||||
|
||||
:root[style*="readium-sepia-on"] a:visited,
|
||||
:root[style*="readium-sepia-on"] a:visited * {
|
||||
color: var(--RS__visitedColor);
|
||||
}
|
||||
|
||||
:root[style*="readium-sepia-on"] svg,
|
||||
:root[style*="readium-sepia-on"] img {
|
||||
/* Make sure the proper bg-color is used for the blend mode */
|
||||
background-color: transparent !important;
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
OS Accessibility Modes
|
||||
|
||||
A stylesheet to deal with OS accessibility settings
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
/* Windows high contrast colors are mapped to CSS system color keywords
|
||||
See http://www.gwhitworth.com/blog/2017/04/how-to-use-ms-high-contrast */
|
||||
|
||||
@media screen and (-ms-high-contrast: active) {
|
||||
:root {
|
||||
color: windowText !important;
|
||||
background-color: window !important;
|
||||
}
|
||||
|
||||
/* The following selectors are super funky but it makes sure everything is inherited, this is indeed critical for this mode */
|
||||
:root :not(#\#):not(#\#):not(#\#),
|
||||
:root :not(#\#):not(#\#):not(#\#) :not(#\#):not(#\#):not(#\#)
|
||||
:root :not(#\#):not(#\#):not(#\#) :not(#\#):not(#\#):not(#\#) :not(#\#):not(#\#):not(#\#) {
|
||||
color: inherit !important;
|
||||
background-color: inherit !important;
|
||||
}
|
||||
|
||||
.readiumCSS-mo-active-default {
|
||||
color: highlightText !important;
|
||||
background-color: highlight !important;
|
||||
}
|
||||
|
||||
/* For links, hyperlink keyword is automatically set */
|
||||
|
||||
/* Should we also set user highlights? */
|
||||
}
|
||||
|
||||
@media screen and (-ms-high-contrast: white-on-black) {
|
||||
:root[style*="readium-night-on"] img[class*="gaiji"],
|
||||
:root[style*="readium-night-on"] *[epub\:type~="titlepage"] img:only-child {
|
||||
-webkit-filter: none !important;
|
||||
filter: none !important;
|
||||
}
|
||||
:root[style*="readium-night-on"][style*="readium-invert-on"] img {
|
||||
-webkit-filter: none !important;
|
||||
filter: none !important;
|
||||
}
|
||||
:root[style*="readium-night-on"][style*="readium-darken-on"][style*="readium-invert-on"] img {
|
||||
-webkit-filter: brightness(80%);
|
||||
filter: brightness(80%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Will be true on recent versions of iOS and MacOS if inverted setting enabled by the user */
|
||||
|
||||
@media screen and (inverted-colors) {
|
||||
:root[style*="readium-night-on"] img[class*="gaiji"],
|
||||
:root[style*="readium-night-on"] *[epub\:type~="titlepage"] img:only-child {
|
||||
-webkit-filter: none !important;
|
||||
filter: none !important;
|
||||
}
|
||||
:root[style*="readium-night-on"][style*="readium-invert-on"] img {
|
||||
-webkit-filter: none !important;
|
||||
filter: none !important;
|
||||
}
|
||||
:root[style*="readium-night-on"][style*="readium-darken-on"][style*="readium-invert-on"] img {
|
||||
-webkit-filter: brightness(80%);
|
||||
filter: brightness(80%);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (monochrome) {
|
||||
/* Grayscale (Implemented in Safari, what about eInk?) */
|
||||
/* Must deal with anything color (contrast) so must be managed at the night/sepia/theme level :( */
|
||||
}
|
||||
|
||||
@media screen and (prefers-reduced-motion) {
|
||||
/* If reduced motion is set on MacOS, in case we have animation/transition */
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
Columns number pref
|
||||
|
||||
A submodule managing columns number for user settings
|
||||
Part of “Chrome Advanced” class – no flag required.
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
/* Number of columns = 1 | 2 */
|
||||
|
||||
/* We still need to see if we allow users to force number of columns for all configs, currently it behaves as an "auto" setting */
|
||||
|
||||
/* apply col setting except for mobile portrait */
|
||||
|
||||
@media screen and (min-width: 60em), screen and (min-device-width: 36em) and (max-device-width: 47em) and (orientation: landscape) {
|
||||
:root[style*="--USER__colCount: 1"],
|
||||
:root[style*="--USER__colCount:1"],
|
||||
:root[style*="--USER__colCount: 2"],
|
||||
:root[style*="--USER__colCount:2"] {
|
||||
-webkit-column-count: var(--USER__colCount);
|
||||
-moz-column-count: var(--USER__colCount);
|
||||
column-count: var(--USER__colCount);
|
||||
}
|
||||
|
||||
/* If one column, make sure we limit line-length */
|
||||
:root[style*="--USER__colCount: 1"],
|
||||
:root[style*="--USER__colCount:1"] {
|
||||
--RS__maxLineLength: 40rem !important; /* This is the only way for the user setting to work in webkit… */
|
||||
--RS__colWidth: 100vw;
|
||||
}
|
||||
|
||||
/* If smartphone landscape, and 2 columns, col width the same as iPad landscape + desktop */
|
||||
:root[style*="--USER__colCount: 2"],
|
||||
:root[style*="--USER__colCount:2"] {
|
||||
--RS__colWidth: auto; /* User explicitely tells he/she wants 2 columns, we reset floor value */
|
||||
}
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
Page margins pref
|
||||
|
||||
A submodule managing page margins for user settings
|
||||
Part of “Chrome Advanced” class – no flag required.
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
/* Page Margins: the user margin is a factor of the page gutter e.g. 0.5, 0.75, 1, 1.25, 1.5, etc. */
|
||||
|
||||
:root[style*="--USER__pageMargins"] body {
|
||||
padding: 0 calc(var(--RS__pageGutter) * var(--USER__pageMargins)) !important;
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
Custom colors pref
|
||||
|
||||
A submodule managing custom colors for user settings
|
||||
Part of “Chrome Advanced” class – no flag required.
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
:root[style*="--USER__backgroundColor"] {
|
||||
background-color: var(--USER__backgroundColor) !important;
|
||||
}
|
||||
|
||||
:root[style*="--USER__backgroundColor"] * {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
:root[style*="--USER__textColor"] {
|
||||
color: var(--USER__textColor) !important;
|
||||
}
|
||||
|
||||
:root[style*="--USER__textColor"] *:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6):not(pre) {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
Text align pref
|
||||
|
||||
A submodule managing text-align for user settings
|
||||
Part of “User Overrides Advanced” class – “advanced settings” flag required.
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
:root[style*="readium-advanced-on"][style*="--USER__textAlign"] {
|
||||
text-align: var(--USER__textAlign);
|
||||
}
|
||||
|
||||
:root[style*="readium-advanced-on"][style*="--USER__textAlign"] body,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__textAlign"] *:not(blockquote):not(figcaption) p,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__textAlign"] li {
|
||||
text-align: inherit !important;
|
||||
-moz-text-align-last: auto !important;
|
||||
-epub-text-align-last: auto !important;
|
||||
text-align-last: auto !important;
|
||||
}
|
||||
|
||||
/* In case something goes wrong at the programmatic level + rtl for body + rtl in ltr */
|
||||
|
||||
:root[style*="readium-advanced-on"][dir="rtl"][style*="--USER__textAlign: left"],
|
||||
:root[style*="readium-advanced-on"][dir="rtl"][style*="--USER__textAlign:left"],
|
||||
:root[style*="readium-advanced-on"][style*="--USER__textAlign: left"] *[dir="rtl"],
|
||||
:root[style*="readium-advanced-on"][style*="--USER__textAlign:left"] *[dir="rtl"] {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Edge, if logical value is used, think of it as a polyfill. For LTR, it will fall back to the default, which is left */
|
||||
|
||||
:root[style*="readium-advanced-on"][dir="rtl"][style*="--USER__textAlign: start"],
|
||||
:root[style*="readium-advanced-on"][dir="rtl"][style*="--USER__textAlign:start"] {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
Hyphenation pref
|
||||
|
||||
A submodule managing hyphens for user settings
|
||||
Part of “User Overrides Advanced” class – “advanced settings” flag required.
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
/* Managing hyphenation automatically for text-align values */
|
||||
|
||||
:root[style*="readium-advanced-on"][style*="--USER__textAlign: justify"] body,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__textAlign:justify"] body {
|
||||
-webkit-hyphens: auto;
|
||||
-moz-hyphens: auto;
|
||||
-ms-hyphens: auto;
|
||||
-epub-hyphens: auto;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
:root[style*="readium-advanced-on"][style*="--USER__textAlign: left"] body,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__textAlign:left"] body,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__textAlign: right"] body,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__textAlign:right"] body {
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
-epub-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
/* Managing the user override */
|
||||
|
||||
:root[style*="readium-advanced-on"][style*="--USER__bodyHyphens"] {
|
||||
-webkit-hyphens: var(--USER__bodyHyphens) !important;
|
||||
-moz-hyphens: var(--USER__bodyHyphens) !important;
|
||||
-ms-hyphens: var(--USER__bodyHyphens) !important;
|
||||
-epub-hyphens: var(--USER__bodyHyphens) !important;
|
||||
hyphens: var(--USER__bodyHyphens) !important;
|
||||
}
|
||||
|
||||
/* Sorry, we can’t use `:matches`, `:-moz-any` or `:-webkit-any` because MS doesn’t support it yet */
|
||||
|
||||
:root[style*="readium-advanced-on"][style*="--USER__bodyHyphens"] body,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__bodyHyphens"] p,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__bodyHyphens"] li,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__bodyHyphens"] div,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__bodyHyphens"] dd {
|
||||
-webkit-hyphens: inherit;
|
||||
-moz-hyphens: inherit;
|
||||
-ms-hyphens: inherit;
|
||||
-epub-hyphens: inherit;
|
||||
hyphens: inherit;
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
Font Family pref
|
||||
|
||||
A submodule managing font-family for user settings
|
||||
Part of “User Overrides” class – “font override” flag required.
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
:root[style*="readium-font-on"][style*="--USER__fontFamily"] {
|
||||
font-family: var(--USER__fontFamily) !important;
|
||||
}
|
||||
|
||||
:root[style*="readium-font-on"][style*="--USER__fontFamily"] body,
|
||||
:root[style*="readium-font-on"][style*="--USER__fontFamily"] p,
|
||||
:root[style*="readium-font-on"][style*="--USER__fontFamily"] li,
|
||||
:root[style*="readium-font-on"][style*="--USER__fontFamily"] div,
|
||||
:root[style*="readium-font-on"][style*="--USER__fontFamily"] dt,
|
||||
:root[style*="readium-font-on"][style*="--USER__fontFamily"] dd {
|
||||
font-family: inherit !important;
|
||||
}
|
||||
|
||||
:root[style*="readium-font-on"][style*="--USER__fontFamily"] i:not([lang]),
|
||||
:root[style*="readium-font-on"][style*="--USER__fontFamily"] i:not([xml\:lang]),
|
||||
:root[style*="readium-font-on"][style*="--USER__fontFamily"] em:not([lang]),
|
||||
:root[style*="readium-font-on"][style*="--USER__fontFamily"] em:not([xml\:lang]),
|
||||
:root[style*="readium-font-on"][style*="--USER__fontFamily"] cite:not([lang]),
|
||||
:root[style*="readium-font-on"][style*="--USER__fontFamily"] cite:not([xml\:lang]),
|
||||
:root[style*="readium-font-on"][style*="--USER__fontFamily"] b:not([lang]),
|
||||
:root[style*="readium-font-on"][style*="--USER__fontFamily"] b:not([xml\:lang]),
|
||||
:root[style*="readium-font-on"][style*="--USER__fontFamily"] strong:not([lang]),
|
||||
:root[style*="readium-font-on"][style*="--USER__fontFamily"] strong:not([xml\:lang]),
|
||||
:root[style*="readium-font-on"][style*="--USER__fontFamily"] span:not([lang]),
|
||||
:root[style*="readium-font-on"][style*="--USER__fontFamily"] span:not([xml\:lang]) {
|
||||
font-family: inherit !important;
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
A11y font pref
|
||||
|
||||
A submodule managing a11y text normalization for user settings
|
||||
Part of “User Overrides” class – “font override” flag required.
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
/* For AccessibleDfA, we need to normalize font-weight and font-style since only the normal style is available */
|
||||
|
||||
:root[style*="readium-font-on"][style*="AccessibleDfA"] {
|
||||
/* We won’t use the variable there since we need fallbacks for missing characters */
|
||||
font-family: AccessibleDfA, Verdana, Tahoma, "Trebuchet MS", sans-serif !important;
|
||||
--RS__lineHeightCompensation: 1.167;
|
||||
}
|
||||
|
||||
:root[style*="readium-font-on"][style*="IA Writer Duospace"] {
|
||||
/* We won’t use the variable there since we need fallbacks for missing characters */
|
||||
font-family: "IA Writer Duospace", Menlo, "DejaVu Sans Mono", "Bitstream Vera Sans Mono", Courier, monospace !important;
|
||||
--RS__lineHeightCompensation: 1.167;
|
||||
}
|
||||
|
||||
:root[style*="readium-font-on"][style*="readium-a11y-on"] {
|
||||
font-family: var(--USER__fontFamily) !important;
|
||||
--RS__lineHeightCompensation: 1.167;
|
||||
}
|
||||
|
||||
/* Maybe users want a setting to normalize any font offered so there is a “a11y Normalize” flag for it */
|
||||
|
||||
:root[style*="readium-font-on"][style*="AccessibleDfA"],
|
||||
:root[style*="readium-font-on"][style*="IA Writer Duospace"],
|
||||
:root[style*="readium-font-on"][style*="readium-a11y-on"] {
|
||||
font-style: normal !important;
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
/* Targeting everything except code. Note that Open Dyslexic has a monospaced font for code */
|
||||
|
||||
:root[style*="readium-font-on"][style*="AccessibleDfA"] *:not(code):not(var):not(kbd):not(samp),
|
||||
:root[style*="readium-font-on"][style*="IA Writer Duospace"] *:not(code):not(var):not(kbd):not(samp),
|
||||
:root[style*="readium-font-on"][style*="readium-a11y-on"] *:not(code):not(var):not(kbd):not(samp) {
|
||||
font-family: inherit !important;
|
||||
font-style: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
}
|
||||
|
||||
/* Normalizing text-decoration, subs and sups */
|
||||
|
||||
:root[style*="readium-font-on"][style*="AccessibleDfA"] *,
|
||||
:root[style*="readium-font-on"][style*="IA Writer Duospace"] *,
|
||||
:root[style*="readium-font-on"][style*="readium-a11y-on"] * {
|
||||
text-decoration: none !important;
|
||||
font-variant-caps: normal !important;
|
||||
font-variant-numeric: normal !important;
|
||||
font-variant-position: normal !important;
|
||||
}
|
||||
|
||||
:root[style*="readium-font-on"][style*="AccessibleDfA"] sup,
|
||||
:root[style*="readium-font-on"][style*="IA Writer Duospace"] sup,
|
||||
:root[style*="readium-font-on"][style*="readium-a11y-on"] sup,
|
||||
:root[style*="readium-font-on"][style*="AccessibleDfA"] sub,
|
||||
:root[style*="readium-font-on"][style*="IA Writer Duospace"] sub,
|
||||
:root[style*="readium-font-on"][style*="readium-a11y-on"] sub {
|
||||
font-size: 1rem !important;
|
||||
vertical-align: baseline !important;
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
Font size pref
|
||||
|
||||
A submodule managing font-size for user settings
|
||||
Part of “User Overrides” class – no flag required.
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
:root[style*="--USER__fontSize"] {
|
||||
font-size: var(--USER__fontSize) !important;
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
Line height pref
|
||||
|
||||
A submodule managing line-height for user settings
|
||||
Part of “User Overrides Advanced” class – “advanced settings” flag required.
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
:root[style*="readium-advanced-on"][style*="--USER__lineHeight"] {
|
||||
line-height: calc((var(--USER__lineHeight) + (2ex - 1ch) - ((1rem - 16px) * 0.1667)) * var(--RS__lineHeightCompensation)) !important;
|
||||
}
|
||||
|
||||
:root[style*="readium-advanced-on"][style*="--USER__lineHeight"] body,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__lineHeight"] p,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__lineHeight"] li,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__lineHeight"] div {
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
Para spacing pref
|
||||
|
||||
A submodule managing paragraphs’ top and bottom margins for user settings
|
||||
Part of “User Overrides Advanced” class – “advanced settings” flag required.
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
:root[style*="readium-advanced-on"][style*="--USER__paraSpacing"] p {
|
||||
margin-top: var(--USER__paraSpacing) !important;
|
||||
margin-bottom: var(--USER__paraSpacing) !important;
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
Para indent pref
|
||||
|
||||
A submodule managing paragraphs’ text-indent for user settings
|
||||
Part of “User Overrides Advanced” class – “advanced settings” flag required.
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
:root[style*="readium-advanced-on"][style*="--USER__paraIndent"] p {
|
||||
text-indent: var(--USER__paraIndent) !important;
|
||||
}
|
||||
|
||||
/* If there are inline-block elements in paragraphs, text-indent will inherit so we must reset it */
|
||||
|
||||
:root[style*="readium-advanced-on"][style*="--USER__paraIndent"] p *,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__paraIndent"] p:first-letter {
|
||||
text-indent: 0 !important;
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
Word spacing pref
|
||||
|
||||
A submodule managing word-spacing for user settings
|
||||
Part of “User Overrides Advanced” class – “advanced settings” flag required.
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
:root[style*="readium-advanced-on"][style*="--USER__wordSpacing"] h1,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__wordSpacing"] h2,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__wordSpacing"] h3,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__wordSpacing"] h4,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__wordSpacing"] h5,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__wordSpacing"] h6,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__wordSpacing"] p,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__wordSpacing"] li,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__wordSpacing"] div {
|
||||
word-spacing: var(--USER__wordSpacing);
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
Letter spacing pref
|
||||
|
||||
A submodule managing letter-spacing for user settings
|
||||
Part of “User Overrides Advanced” class – “advanced settings” flag required.
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
:root[style*="readium-advanced-on"][style*="--USER__letterSpacing"] h1,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__letterSpacing"] h2,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__letterSpacing"] h3,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__letterSpacing"] h4,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__letterSpacing"] h5,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__letterSpacing"] h6,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__letterSpacing"] p,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__letterSpacing"] li,
|
||||
:root[style*="readium-advanced-on"][style*="--USER__letterSpacing"] div {
|
||||
letter-spacing: var(--USER__letterSpacing);
|
||||
font-variant: none;
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
Font size normalize
|
||||
|
||||
A stylesheet to normalize font-size
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
/* STYLES */
|
||||
|
||||
/* :root is used so that you can quickly add a class or attribute if you prefer e.g. `:root[data-rs-normalize]` */
|
||||
|
||||
/* We create a default so that you don’t need to explicitly set one in the DOM.
|
||||
Once the “Publisher’s styles” checkbox is unchecked, the normalize is applied automatically */
|
||||
|
||||
:root[style*="readium-advanced-on"] {
|
||||
--USER__typeScale: 1.2; /* This is the default type scale you’ll find in most publications */
|
||||
}
|
||||
|
||||
:root[style*="readium-advanced-on"] p,
|
||||
:root[style*="readium-advanced-on"] li,
|
||||
:root[style*="readium-advanced-on"] div,
|
||||
:root[style*="readium-advanced-on"] pre,
|
||||
:root[style*="readium-advanced-on"] dd {
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
|
||||
:root[style*="readium-advanced-on"] h1 {
|
||||
/* Fallback if browser doesn’t support vars */
|
||||
font-size: 1.75rem !important;
|
||||
font-size: calc(((1rem * var(--USER__typeScale)) * var(--USER__typeScale)) * var(--USER__typeScale)) !important;
|
||||
}
|
||||
|
||||
:root[style*="readium-advanced-on"] h2 {
|
||||
/* Fallback if browser doesn’t support vars */
|
||||
font-size: 1.5rem !important;
|
||||
font-size: calc((1rem * var(--USER__typeScale)) * var(--USER__typeScale)) !important;
|
||||
}
|
||||
|
||||
:root[style*="readium-advanced-on"] h3 {
|
||||
/* Fallback if browser doesn’t support vars */
|
||||
font-size: 1.25rem !important;
|
||||
font-size: calc(1rem * var(--USER__typeScale)) !important;
|
||||
}
|
||||
|
||||
:root[style*="readium-advanced-on"] h4,
|
||||
:root[style*="readium-advanced-on"] h5,
|
||||
:root[style*="readium-advanced-on"] h6 {
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
|
||||
:root[style*="readium-advanced-on"] small {
|
||||
font-size: smaller !important;
|
||||
}
|
||||
|
||||
:root[style*="readium-advanced-on"] sub,
|
||||
:root[style*="readium-advanced-on"] sup {
|
||||
font-size: 67.5% !important;
|
||||
}
|
||||
|
||||
/* The following styles kick in if you define the typeScale variable in the DOM.
|
||||
No need to repeat declarations which don’t make use of the variable */
|
||||
|
||||
:root[style*="readium-advanced-on"][style*="--USER__typeScale"] h1 {
|
||||
font-size: calc(((1rem * var(--USER__typeScale)) * var(--USER__typeScale)) * var(--USER__typeScale)) !important;
|
||||
}
|
||||
|
||||
:root[style*="readium-advanced-on"][style*="--USER__typeScale"] h2 {
|
||||
font-size: calc((1rem * var(--USER__typeScale)) * var(--USER__typeScale)) !important;
|
||||
}
|
||||
|
||||
:root[style*="readium-advanced-on"][style*="--USER__typeScale"] h3 {
|
||||
font-size: calc(1rem * var(--USER__typeScale)) !important;
|
||||
}
|
||||
602
komga-webui/src/styles/readium/ReadiumCSS-before.css.resource
Normal file
602
komga-webui/src/styles/readium/ReadiumCSS-before.css.resource
Normal file
|
|
@ -0,0 +1,602 @@
|
|||
/* Readium CSS
|
||||
Config module
|
||||
|
||||
A file allowing implementers to customize flags for reading modes,
|
||||
user settings, etc.
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
/* Custom medias
|
||||
Syntax: @custom-media --variable (prop: value) */
|
||||
|
||||
/* Responsive columns
|
||||
The minimum width for which responsive columns (2 -> 1 and vice versa,
|
||||
depending on the current font-size) must be enabled */
|
||||
|
||||
/* Mobile columns
|
||||
The minimum and maximum width for mobile devices.
|
||||
We’re forcing the landscape orientation by default,
|
||||
and must still investigate large tablets (iPad Pro, Surface Pro 3, etc.). */
|
||||
|
||||
/* Custom selectors
|
||||
Syntax: @custom-selector :--variable selector
|
||||
The selectors you will use for flags/switches
|
||||
You can alternatively use classes or custom data-* attributes */
|
||||
|
||||
/* User view = paged | scrolled */
|
||||
|
||||
/* Font-family override */
|
||||
|
||||
/* Advanced settings */
|
||||
|
||||
/* Reading Modes */
|
||||
|
||||
/* Filters (images) */
|
||||
|
||||
/* Accessibility normalization */
|
||||
|
||||
/* Accessibility font. You can add selectors, using “, ” as a separator, if you have multiple fonts */
|
||||
|
||||
/* Direction i.e. ltr and rtl */
|
||||
|
||||
/* Readium CSS
|
||||
Base module
|
||||
|
||||
A minimal stylesheet for all ebooks
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
@namespace url("http://www.w3.org/1999/xhtml");
|
||||
|
||||
@namespace epub url("http://www.idpf.org/2007/ops");
|
||||
|
||||
@namespace m url("http://www.w3.org/1998/Math/MathML/");
|
||||
|
||||
@namespace svg url("http://www.w3.org/2000/svg");
|
||||
|
||||
/* Define viewport, HTML5-style */
|
||||
|
||||
@-ms-viewport {
|
||||
width: device-width;
|
||||
}
|
||||
|
||||
@viewport {
|
||||
width: device-width;
|
||||
zoom: 1;
|
||||
}
|
||||
|
||||
:root {
|
||||
/* Default font-stacks */
|
||||
--RS__oldStyleTf: "Iowan Old Style", "Sitka Text", Palatino, "Book Antiqua", serif;
|
||||
--RS__modernTf: Athelas, Constantia, Georgia, serif;
|
||||
--RS__sansTf: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
--RS__humanistTf: Seravek, Calibri, Roboto, Arial, sans-serif;
|
||||
--RS__monospaceTf: "Andale Mono", Consolas, monospace;
|
||||
|
||||
/* Config */
|
||||
--RS__baseFontFamily: var(--RS__oldStyleTf);
|
||||
|
||||
/* For square-ish fonts (CJK, Indic, etc.), we must apply some compensation in dynamic leading. Default is 1 i.e. no compensation */
|
||||
--RS__lineHeightCompensation: 1;
|
||||
|
||||
/* Dynamic leading based on typeface metrics + font-size setting */
|
||||
--RS__baseLineHeight: calc((1em + (2ex - 1ch) - ((1rem - 16px) * 0.1667)) * var(--RS__lineHeightCompensation));
|
||||
}
|
||||
|
||||
/* Set default font for the html doc, so that it can be overridden by the authors’s stylesheet */
|
||||
|
||||
html {
|
||||
font-family: var(--RS__baseFontFamily);
|
||||
/* Fallback line-height */
|
||||
line-height: 1.6; /* Fits a little bit better for all languages than 1.5 */
|
||||
line-height: var(--RS__baseLineHeight);
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
/* 1.5 being too loose with larger font-sizes, we reset headings to normal (which value is 1.125–1.375 for our font-stacks) */
|
||||
|
||||
h1, h2, h3 {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
:lang(ja),
|
||||
:lang(zh),
|
||||
:lang(ko) {
|
||||
word-wrap: break-word;
|
||||
-webkit-line-break: strict;
|
||||
-epub-line-break: strict;
|
||||
line-break: strict;
|
||||
}
|
||||
|
||||
/* Set default font for Math */
|
||||
|
||||
math {
|
||||
font-family: "Latin Modern Math", "STIX Two Math", "XITS Math", "STIX Math", "Libertinus Math", "TeX Gyre Termes Math", "TeX Gyre Bonum Math", "TeX Gyre Schola", "DejaVu Math TeX Gyre", "TeX Gyre Pagella Math", "Asana Math", "Cambria Math", "Lucida Bright Math", "Minion Math", STIXGeneral, STIXSizeOneSym, Symbol, "Times New Roman", serif;
|
||||
}
|
||||
|
||||
/* Language Overrides
|
||||
That will only work if either html or body have a (xml:)lang attribute, not for inline overrides */
|
||||
|
||||
:lang(am) {
|
||||
--RS__baseFontFamily: Kefa, Nyala, Roboto, Noto, "Noto Sans Ethiopic", serif;
|
||||
--RS__lineHeightCompensation: 1.167;
|
||||
}
|
||||
|
||||
:lang(ar) {
|
||||
--RS__baseFontFamily: "Geeza Pro", "Arabic Typesetting", Roboto, Noto, "Noto Naskh Arabic", "Times New Roman", serif;
|
||||
}
|
||||
|
||||
:lang(bn) {
|
||||
--RS__baseFontFamily: "Kohinoor Bangla", "Bangla Sangam MN", Vrinda, Roboto, Noto, "Noto Sans Bengali", sans-serif;
|
||||
--RS__lineHeightCompensation: 1.067;
|
||||
}
|
||||
|
||||
:lang(bo) {
|
||||
--RS__baseFontFamily: Kailasa, "Microsoft Himalaya", Roboto, Noto, "Noto Sans Tibetan", sans-serif;
|
||||
}
|
||||
|
||||
:lang(chr) {
|
||||
--RS__baseFontFamily: "Plantagenet Cherokee", Roboto, Noto, "Noto Sans Cherokee";
|
||||
--RS__lineHeightCompensation: 1.167;
|
||||
}
|
||||
|
||||
:lang(fa) {
|
||||
--RS__baseFontFamily: "Geeza Pro", "Arabic Typesetting", Roboto, Noto, "Noto Naskh Arabic", "Times New Roman", serif;
|
||||
}
|
||||
|
||||
:lang(gu) {
|
||||
--RS__baseFontFamily: "Gujarati Sangam MN", "Nirmala UI", Shruti, Roboto, Noto, "Noto Sans Gujarati", sans-serif;
|
||||
--RS__lineHeightCompensation: 1.167;
|
||||
}
|
||||
|
||||
:lang(he) {
|
||||
--RS__baseFontFamily: "New Peninim MT", "Arial Hebrew", Gisha, "Times New Roman", Roboto, Noto, "Noto Sans Hebrew" sans-serif;
|
||||
--RS__lineHeightCompensation: 1.1;
|
||||
}
|
||||
|
||||
:lang(hi) {
|
||||
--RS__baseFontFamily: "Kohinoor Devanagari", "Devanagari Sangam MN", Kokila, "Nirmala UI", Roboto, Noto, "Noto Sans Devanagari", sans-serif;
|
||||
|
||||
--RS__lineHeightCompensation: 1.1;
|
||||
}
|
||||
|
||||
:lang(hy) {
|
||||
--RS__baseFontFamily: Mshtakan, Sylfaen, Roboto, Noto, "Noto Serif Armenian", serif;
|
||||
}
|
||||
|
||||
:lang(iu) {
|
||||
--RS__baseFontFamily: "Euphemia UCAS", Euphemia, Roboto, Noto, "Noto Sans Canadian Aboriginal", sans-serif;
|
||||
}
|
||||
|
||||
:lang(ja) {
|
||||
--RS__baseFontFamily: "游ゴシック体", YuGothic, "ヒラギノ丸ゴ", "Hiragino Sans", "Yu Gothic UI", "Meiryo UI", "MS Gothic", Roboto, Noto, "Noto Sans CJK JP", sans-serif;
|
||||
|
||||
/* For CJK, the line-height is usually 15–20% more than for Latin */
|
||||
--RS__lineHeightCompensation: 1.167;
|
||||
|
||||
/* Extra variables for Japanese font-stacks as we may want to reuse them for user settings + default */
|
||||
--RS__serif-ja: "MS P明朝", "MS PMincho", "Hiragino Mincho Pro", "ヒラギノ明朝 Pro W3", "游明朝", "YuMincho", "MS 明朝", "MS Mincho", "Hiragino Mincho ProN", serif;
|
||||
--RS__sans-serif-ja: "MS Pゴシック", "MS PGothic", "Hiragino Kaku Gothic Pro W3", "ヒラギノ角ゴ Pro W3", "Hiragino Sans GB", "ヒラギノ角ゴシック W3", "游ゴシック", "YuGothic", "MS ゴシック", "MS Gothic", "Hiragino Sans", sans-serif;
|
||||
--RS__serif-ja-v: "MS 明朝", "MS Mincho", "Hiragino Mincho Pro", "ヒラギノ明朝 Pro W3", "游明朝", "YuMincho", "MS P明朝", "MS PMincho", "Hiragino Mincho ProN", serif;
|
||||
--RS__sans-serif-ja-v: "MS ゴシック", "MS Gothic", "Hiragino Kaku Gothic Pro W3", "ヒラギノ角ゴ Pro W3", "Hiragino Sans GB", "ヒラギノ角ゴシック W3", "游ゴシック", "YuGothic", "MS Pゴシック", "MS PGothic", "Hiragino Sans", sans-serif;
|
||||
}
|
||||
|
||||
:lang(km) {
|
||||
--RS__baseFontFamily: "Khmer Sangam MN", "Leelawadee UI", "Khmer UI", Roboto, Noto, "Noto Sans Khmer", sans-serif;
|
||||
--RS__lineHeightCompensation: 1.067;
|
||||
}
|
||||
|
||||
:lang(kn) {
|
||||
--RS__baseFontFamily: "Kannada Sangam MN", "Nirmala UI", Tunga, Roboto, Noto, "Noto Sans Kannada", sans-serif;
|
||||
--RS__lineHeightCompensation: 1.1;
|
||||
}
|
||||
|
||||
:lang(ko) {
|
||||
--RS__baseFontFamily: "Nanum Gothic", "Apple SD Gothic Neo", "Malgun Gothic", Roboto, Noto, "Noto Sans CJK KR", sans-serif;
|
||||
|
||||
/* For CJK, the line-height is usually 15–20% more than for Latin */
|
||||
--RS__lineHeightCompensation: 1.167;
|
||||
}
|
||||
|
||||
:lang(lo) {
|
||||
--RS__baseFontFamily: "Lao Sangam MN", "Leelawadee UI", "Lao UI", Roboto, Noto, "Noto Sans Lao", sans-serif;
|
||||
}
|
||||
|
||||
:lang(ml) {
|
||||
--RS__baseFontFamily: "Malayalam Sangam MN", "Nirmala UI", Kartika, Roboto, Noto, "Noto Sans Malayalam", sans-serif;
|
||||
--RS__lineHeightCompensation: 1.067;
|
||||
}
|
||||
|
||||
:lang(or) {
|
||||
--RS__baseFontFamily: "Oriya Sangam MN", "Nirmala UI", Kalinga, Roboto, Noto, "Noto Sans Oriya", sans-serif;
|
||||
--RS__lineHeightCompensation: 1.167;
|
||||
}
|
||||
|
||||
:lang(pa) {
|
||||
--RS__baseFontFamily: "Gurmukhi MN", "Nirmala UI", Kartika, Roboto, Noto, "Noto Sans Gurmukhi", sans-serif;
|
||||
--RS__lineHeightCompensation: 1.1;
|
||||
}
|
||||
|
||||
:lang(si) {
|
||||
--RS__baseFontFamily: "Sinhala Sangam MN", "Nirmala UI", "Iskoola Pota", Roboto, Noto, "Noto Sans Sinhala", sans-serif;
|
||||
--RS__lineHeightCompensation: 1.167;
|
||||
}
|
||||
|
||||
:lang(ta) {
|
||||
--RS__baseFontFamily: "Tamil Sangam MN", "Nirmala UI", Latha, Roboto, Noto, "Noto Sans Tamil", sans-serif;
|
||||
--RS__lineHeightCompensation: 1.067;
|
||||
}
|
||||
|
||||
:lang(te) {
|
||||
--RS__baseFontFamily: "Kohinoor Telugu", "Telugu Sangam MN", "Nirmala UI", Gautami, Roboto, Noto, "Noto Sans Telugu", sans-serif;
|
||||
}
|
||||
|
||||
:lang(th) {
|
||||
--RS__baseFontFamily: "Thonburi", "Leelawadee UI", "Cordia New", Roboto, Noto, "Noto Sans Thai", sans-serif;
|
||||
--RS__lineHeightCompensation: 1.067;
|
||||
}
|
||||
|
||||
/* The following will also work for zh-Hans */
|
||||
|
||||
:lang(zh) {
|
||||
--RS__baseFontFamily: "方体", "PingFang SC", "黑体", "Heiti SC", "Microsoft JhengHei UI", "Microsoft JhengHei", Roboto, Noto, "Noto Sans CJK SC", sans-serif;
|
||||
|
||||
/* For CJK, the line-height is usually 15–20% more than for Latin */
|
||||
--RS__lineHeightCompensation: 1.167;
|
||||
}
|
||||
|
||||
:lang(zh-Hant),
|
||||
:lang(zh-TW) {
|
||||
--RS__baseFontFamily: "方體", "PingFang TC", "黑體", "Heiti TC", "Microsoft JhengHei UI", "Microsoft JhengHei", Roboto, Noto, "Noto Sans CJK TC", sans-serif;
|
||||
|
||||
/* For CJK, the line-height is usually 15–20% more than for Latin */
|
||||
--RS__lineHeightCompensation: 1.167;
|
||||
}
|
||||
|
||||
:lang(zh-HK) {
|
||||
--RS__baseFontFamily: "方體", "PingFang HK", "方體", "PingFang TC", "黑體", "Heiti TC", "Microsoft JhengHei UI", "Microsoft JhengHei", Roboto, Noto, "Noto Sans CJK TC", sans-serif;
|
||||
|
||||
/* For CJK, the line-height is usually 15–20% more than for Latin */
|
||||
--RS__lineHeightCompensation: 1.167;
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
Day/Default mode
|
||||
|
||||
A preset theme for day mode, which is the default
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
/* CONFIG */
|
||||
|
||||
:root {
|
||||
--RS__backgroundColor: #FFFFFF;
|
||||
--RS__textColor: #121212;
|
||||
|
||||
/* This can be customized but initial will re-use default value of the browser */
|
||||
--RS__selectionBackgroundColor: #b4d8fe;
|
||||
--RS__selectionTextColor: inherit;
|
||||
}
|
||||
|
||||
:root {
|
||||
color: var(--RS__textColor) !important;
|
||||
background-color: var(--RS__backgroundColor) !important;
|
||||
}
|
||||
|
||||
/* Note: Though `::selection` was present in drafts of CSS Selectors Level 3, it was removed during the Candidate Recommendation phase because its behavior was under-specified (especially with nested elements) and interoperability wasn’t achieved. Source: https://developer.mozilla.org/en-US/docs/Web/CSS/::selection */
|
||||
|
||||
::-moz-selection {
|
||||
color: var(--RS__selectionTextColor);
|
||||
background-color: var(--RS__selectionBackgroundColor);
|
||||
}
|
||||
|
||||
::selection {
|
||||
color: var(--RS__selectionTextColor);
|
||||
background-color: var(--RS__selectionBackgroundColor);
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
Fonts module
|
||||
|
||||
A stylesheet for embedded fonts
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
/* /!\ Mind the path (relative to the folders in which you have stylesheets and the fonts) */
|
||||
|
||||
/*@font-face {*/
|
||||
/* font-family: AccessibleDfA;*/
|
||||
/* font-style: normal;*/
|
||||
/* font-weight: normal;*/
|
||||
/* src: local("AccessibleDfA"),*/
|
||||
/* url("fonts/AccessibleDfA.otf") format("opentype");*/
|
||||
/*}*/
|
||||
|
||||
/*@font-face {*/
|
||||
/* font-family: "IA Writer Duospace";*/
|
||||
/* font-style: normal;*/
|
||||
/* font-weight: normal;*/
|
||||
/* src: local("iAWriterDuospace-Regular"),*/
|
||||
/* url("fonts/iAWriterDuospace-Regular.ttf") format("truetype");*/
|
||||
/*}*/
|
||||
|
||||
/* If you have different weights/styles,
|
||||
use `font-weight` and `font-style`,
|
||||
not prefixes in the font-family name,
|
||||
or else it will be a nightmare to manage in user settings. */
|
||||
|
||||
/* Readium CSS
|
||||
HTML5 SR Patch stylesheet
|
||||
|
||||
A set of style to adjust HTML5 Suggested Rendering to paginated content
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
/* Fragmentation */
|
||||
|
||||
body {
|
||||
widows: 2;
|
||||
orphans: 2;
|
||||
}
|
||||
|
||||
figcaption, th, td {
|
||||
widows: 1;
|
||||
orphans: 1;
|
||||
}
|
||||
|
||||
h2, h3, h4, h5, h6, dt,
|
||||
hr, caption {
|
||||
-webkit-column-break-after: avoid;
|
||||
page-break-after: avoid;
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6, dt,
|
||||
figure, tr {
|
||||
-webkit-column-break-inside: avoid;
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
/* Hyphenation */
|
||||
|
||||
body {
|
||||
-webkit-hyphenate-character: "\002D";
|
||||
-moz-hyphenate-character: "\002D";
|
||||
-ms-hyphenate-character: "\002D";
|
||||
hyphenate-character: "\002D";
|
||||
-webkit-hyphenate-limit-lines: 3;
|
||||
-ms-hyphenate-limit-lines: 3;
|
||||
hyphenate-limit-lines: 3;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6, dt,
|
||||
figcaption, pre, caption, address,
|
||||
center, code, var {
|
||||
-ms-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-webkit-hyphens: none;
|
||||
-epub-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
/* OTF */
|
||||
|
||||
body {
|
||||
font-variant-numeric: oldstyle-nums proportional-nums;
|
||||
}
|
||||
|
||||
:lang(ja) body,
|
||||
:lang(zh) body,
|
||||
:lang(ko) body {
|
||||
font-variant-numeric: lining-nums proportional-nums;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6, dt {
|
||||
font-variant-numeric: lining-nums proportional-nums;
|
||||
}
|
||||
|
||||
table {
|
||||
font-variant-numeric: lining-nums tabular-nums;
|
||||
}
|
||||
|
||||
code, var {
|
||||
font-variant-ligatures: none;
|
||||
font-variant-numeric: lining-nums tabular-nums slashed-zero;
|
||||
}
|
||||
|
||||
rt {
|
||||
font-variant-east-asian: ruby;
|
||||
}
|
||||
|
||||
:lang(ar) {
|
||||
font-variant-ligatures: common-ligatures;
|
||||
}
|
||||
|
||||
:lang(ko) {
|
||||
font-kerning: normal;
|
||||
}
|
||||
|
||||
/* Night mode */
|
||||
|
||||
hr {
|
||||
color: inherit;
|
||||
border-color: currentColor;
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border-color: currentColor;
|
||||
}
|
||||
|
||||
/* Horizontal Spacing */
|
||||
|
||||
figure, blockquote {
|
||||
margin: 1em 5%;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
:lang(ja) figure, :lang(ja) blockquote,
|
||||
:lang(zh-Hant) figure, :lang(zh-Hant) blockquote,
|
||||
:lang(zh-TW) figure, :lang(zh-TW) blockquote,
|
||||
:lang(mn) figure, :lang(mn) blockquote {
|
||||
margin: 5% 1em;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
ul, ol {
|
||||
padding-left: 5%;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
:lang(ja) ul, :lang(ja) ol,
|
||||
:lang(zh-Hant) ul, :lang(zh-Hant) ol,
|
||||
:lang(zh-TW) ul, :lang(zh-TW) ol,
|
||||
:lang(mn) ul, :lang(mn) ol {
|
||||
padding-top: 5%;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
dd {
|
||||
margin-left: 5%;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
:lang(ja) dd,
|
||||
:lang(zh-Hant) dd,
|
||||
:lang(zh-TW) dd,
|
||||
:lang(mn) dd {
|
||||
margin-top: 5%;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
-ms-tab-size: 2;
|
||||
-moz-tab-size: 2;
|
||||
-webkit-tab-size: 2;
|
||||
tab-size: 2;
|
||||
}
|
||||
|
||||
/* Normalization */
|
||||
|
||||
abbr[title], acronym[title] {
|
||||
text-decoration: dotted underline;
|
||||
}
|
||||
|
||||
nobr wbr {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
/* Make ruby text and parentheses non-selectable (TBC) */
|
||||
|
||||
ruby > rt, ruby > rp {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Internationalization */
|
||||
|
||||
*:lang(ja),
|
||||
*:lang(zh),
|
||||
*:lang(ko),
|
||||
:lang(ja) cite, :lang(ja) dfn, :lang(ja) em, :lang(ja) i,
|
||||
:lang(zh) cite, :lang(zh) dfn, :lang(zh) em, :lang(zh) i,
|
||||
:lang(ko) cite, :lang(ko) dfn, :lang(ko) em, :lang(ko) i {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
:lang(ja) a,
|
||||
:lang(zh) a,
|
||||
:lang(ko) a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Readium CSS
|
||||
Safeguards module
|
||||
|
||||
A set of styles to prevent common issues in pagination
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
/* Config */
|
||||
|
||||
/* We’ll be using an "RS__" prefix so that we can prevent collisions with authors’ CSS */
|
||||
|
||||
:root {
|
||||
/* max-width for media, you can override that via JS if not compiled to static */
|
||||
--RS__maxMediaWidth: 100%;
|
||||
|
||||
/* max-height for media, you can override that via JS if not compiled to static
|
||||
Please consider figures might have a figcaption, which is why 95vh in the first place */
|
||||
--RS__maxMediaHeight: 95vh;
|
||||
|
||||
/* value for medias’ box-sizing */
|
||||
--RS__boxSizingMedia: border-box;
|
||||
|
||||
/* value for table’s box-sizing */
|
||||
--RS__boxSizingTable: border-box;
|
||||
}
|
||||
|
||||
/* Sanitize line-heights in webkit e.g. raised cap without a declared line-height
|
||||
See effect by checking this demo in Safari: https://codepen.io/JayPanoz/pen/gRmzrE
|
||||
Note: glyphs has to be reset to inline for CJK */
|
||||
|
||||
html {
|
||||
-webkit-line-box-contain: block glyphs replaced;
|
||||
}
|
||||
|
||||
:lang(ja) {
|
||||
-webkit-line-box-contain: block inline replaced;
|
||||
}
|
||||
|
||||
/* Wrap long strings if larger than line-length */
|
||||
|
||||
a, h1, h2, h3, h4, h5, h6 {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
div {
|
||||
max-width: var(--RS__maxMediaWidth);
|
||||
}
|
||||
|
||||
/* Size medias */
|
||||
|
||||
/* You can override CSS variables by re-defining it for all elements or a specific one */
|
||||
|
||||
img, svg, audio, video {
|
||||
|
||||
/* Object-fit allows us to keep the correct aspect-ratio */
|
||||
object-fit: contain;
|
||||
|
||||
width: auto;
|
||||
height: auto;
|
||||
|
||||
/* Some files don’t have max-width */
|
||||
max-width: var(--RS__maxMediaWidth);
|
||||
|
||||
/* We’re setting a max-height, especially for covers */
|
||||
max-height: var(--RS__maxMediaHeight) !important;
|
||||
/* We probably don’t need to use modern box-sizing as auto behaves like it */
|
||||
box-sizing: var(--RS__boxSizingMedia);
|
||||
|
||||
/* For page-break, we must use those 3
|
||||
We can’t use a variable there, webkit seems to no support them for those properties */
|
||||
-webkit-column-break-inside: avoid;
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
/* Try preventing border being cut-off, webkit + blink have content-box by default */
|
||||
|
||||
table {
|
||||
max-width: var(--RS__maxMediaWidth);
|
||||
box-sizing: var(--RS__boxSizingTable);
|
||||
}
|
||||
169
komga-webui/src/styles/readium/ReadiumCSS-default.css.resource
Normal file
169
komga-webui/src/styles/readium/ReadiumCSS-default.css.resource
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
/* Readium CSS
|
||||
Default module
|
||||
|
||||
A stylesheet for unstyled ebooks based on HTML5 Suggested Rendering
|
||||
Note: works in combination with Base module
|
||||
|
||||
Repo: https://github.com/readium/readium-css */
|
||||
|
||||
/* CONFIG */
|
||||
|
||||
:root {
|
||||
--RS__compFontFamily: var(--RS__baseFontFamily);
|
||||
--RS__codeFontFamily: var(--RS__monospaceTf);
|
||||
|
||||
--RS__typeScale: 1.125; /* 1.067 | 1.125 | 1.2 | 1.25 | 1.333 | 1.414 | 1.5 | 1.618 */
|
||||
--RS__baseFontSize: 100%;
|
||||
|
||||
--RS__flowSpacing: 1.5rem;
|
||||
--RS__paraSpacing: 0;
|
||||
--RS__paraIndent: 1em;
|
||||
|
||||
--RS__linkColor: #0000EE;
|
||||
--RS__visitedColor: #551A8B;
|
||||
|
||||
--RS__primaryColor: ;
|
||||
--RS__secondaryColor: ;
|
||||
}
|
||||
|
||||
/* STYLES */
|
||||
|
||||
/* Typo */
|
||||
mark {
|
||||
background: yellow;
|
||||
|
||||
}
|
||||
mark.current {
|
||||
background: orange;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: var(--RS__baseFontSize);
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: var(--RS__compFontFamily);
|
||||
}
|
||||
|
||||
/* Flow content */
|
||||
|
||||
blockquote, figure, p, pre,
|
||||
aside, footer, form, hr {
|
||||
margin-top: var(--RS__flowSpacing);
|
||||
margin-bottom: var(--RS__flowSpacing);
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: var(--RS__paraSpacing);
|
||||
margin-bottom: var(--RS__paraSpacing);
|
||||
text-indent: var(--RS__paraIndent);
|
||||
}
|
||||
|
||||
h1 + p, h2 + p, h3 + p, h4 + p, h5 + p, h6 + p,
|
||||
hr + p {
|
||||
text-indent: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: var(--RS__codeFontFamily);
|
||||
}
|
||||
|
||||
/* Phrasing content */
|
||||
|
||||
code, kbd, samp, tt {
|
||||
font-family: var(--RS__codeFontFamily);
|
||||
}
|
||||
|
||||
sub, sup {
|
||||
position: relative;
|
||||
font-size: 67.5%;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.2ex;
|
||||
}
|
||||
|
||||
sup {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
:link {
|
||||
color: var(--RS__linkColor);
|
||||
}
|
||||
|
||||
:visited {
|
||||
color: var(--RS__visitedColor);
|
||||
}
|
||||
|
||||
/* Headings */
|
||||
|
||||
h1 {
|
||||
margin-top: calc(var(--RS__flowSpacing) * 2);
|
||||
margin-bottom: calc(var(--RS__flowSpacing) * 2);
|
||||
/* The following is base font size * typescale power of 3 */
|
||||
font-size: calc(((1em * var(--RS__typeScale)) * var(--RS__typeScale)) * var(--RS__typeScale));
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: calc(var(--RS__flowSpacing) * 2);
|
||||
margin-bottom: var(--RS__flowSpacing);
|
||||
/* The following is base font size * typescale power of 2 */
|
||||
font-size: calc((1em * var(--RS__typeScale)) * var(--RS__typeScale));
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: var(--RS__flowSpacing);
|
||||
margin-bottom: var(--RS__flowSpacing);
|
||||
font-size: calc(1em * var(--RS__typeScale));
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-top: var(--RS__flowSpacing);
|
||||
margin-bottom: var(--RS__flowSpacing);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
h5 {
|
||||
margin-top: var(--RS__flowSpacing);
|
||||
margin-bottom: var(--RS__flowSpacing);
|
||||
font-size: 1em;
|
||||
font-variant: small-caps;
|
||||
}
|
||||
|
||||
h6 {
|
||||
margin-top: var(--RS__flowSpacing);
|
||||
margin-bottom: 0;
|
||||
font-size: 1em;
|
||||
text-transform: lowercase;
|
||||
font-variant: small-caps;
|
||||
}
|
||||
|
||||
/* Lists */
|
||||
|
||||
dl, ol, ul {
|
||||
margin-top: var(--RS__flowSpacing);
|
||||
margin-bottom: var(--RS__flowSpacing);
|
||||
}
|
||||
|
||||
/* Table */
|
||||
|
||||
table {
|
||||
margin: var(--RS__flowSpacing) 0;
|
||||
border: 1px solid currentColor;
|
||||
border-collapse: collapse;
|
||||
empty-cells: show;
|
||||
}
|
||||
|
||||
thead, tbody, tfoot, table > tr {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 4px;
|
||||
border: 1px solid currentColor;
|
||||
}
|
||||
7
komga-webui/src/types/epub.ts
Normal file
7
komga-webui/src/types/epub.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export interface TocEntry {
|
||||
title: string,
|
||||
href?: string,
|
||||
children?: TocEntry[],
|
||||
current?: boolean,
|
||||
level?: number,
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import i18n from '@/i18n'
|
|||
import {MediaStatus} from '@/types/enum-books'
|
||||
import {getFileSize} from '@/functions/file'
|
||||
import {ReadListDto} from '@/types/komga-readlists'
|
||||
import {getBookReadRouteFromMediaProfile} from '@/functions/book-format'
|
||||
|
||||
export enum ItemTypes {
|
||||
BOOK, SERIES, COLLECTION, READLIST
|
||||
|
|
@ -169,7 +170,7 @@ export class BookItem extends Item<BookDto> {
|
|||
|
||||
fabTo(): RawLocation {
|
||||
return {
|
||||
name: 'read-book',
|
||||
name: getBookReadRouteFromMediaProfile(this.item?.media?.mediaProfile),
|
||||
params: {bookId: this.item.id},
|
||||
query: {context: this.item?.context?.origin, contextId: this.item?.context?.id},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ export interface MediaDto {
|
|||
status: string,
|
||||
mediaType: string,
|
||||
pagesCount: number,
|
||||
comment: string
|
||||
comment: string,
|
||||
mediaProfile: string,
|
||||
}
|
||||
|
||||
export interface PageDto {
|
||||
|
|
|
|||
|
|
@ -339,6 +339,7 @@ import {SeriesDto} from '@/types/komga-series'
|
|||
import jsFileDownloader from 'js-file-downloader'
|
||||
import screenfull from 'screenfull'
|
||||
import {ItemTypes} from '@/types/items'
|
||||
import {getBookReadRouteFromMediaProfile} from '@/functions/book-format'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'BookReader',
|
||||
|
|
@ -741,7 +742,7 @@ export default Vue.extend({
|
|||
if (!this.$_.isEmpty(this.siblingPrevious)) {
|
||||
this.jumpToPreviousBook = false
|
||||
this.$router.push({
|
||||
name: 'read-book',
|
||||
name: getBookReadRouteFromMediaProfile(this.siblingPrevious.media.mediaProfile),
|
||||
params: {bookId: this.siblingPrevious.id.toString()},
|
||||
query: {context: this.context.origin, contextId: this.context.id, incognito: this.incognito.toString()},
|
||||
})
|
||||
|
|
@ -753,7 +754,7 @@ export default Vue.extend({
|
|||
} else {
|
||||
this.jumpToNextBook = false
|
||||
this.$router.push({
|
||||
name: 'read-book',
|
||||
name: getBookReadRouteFromMediaProfile(this.siblingNext.media.mediaProfile),
|
||||
params: {bookId: this.siblingNext.id.toString()},
|
||||
query: {context: this.context.origin, contextId: this.context.id, incognito: this.incognito.toString()},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@
|
|||
<v-btn color="accent"
|
||||
small
|
||||
:title="$t('browse_book.read_book')"
|
||||
:to="{name: 'read-book', params: { bookId: bookId}, query: { context: context.origin, contextId: context.id}}"
|
||||
:to="{name: readRouteName, params: { bookId: bookId}, query: { context: context.origin, contextId: context.id}}"
|
||||
:disabled="!canRead"
|
||||
>
|
||||
<v-icon left small>mdi-book-open-page-variant</v-icon>
|
||||
|
|
@ -205,7 +205,7 @@
|
|||
<v-col cols="auto">
|
||||
<v-btn small
|
||||
:title="$t('browse_book.read_incognito')"
|
||||
:to="{name: 'read-book', params: { bookId: bookId}, query: { context: context.origin, contextId: context.id, incognito: true}}"
|
||||
:to="{name: readRouteName, params: { bookId: bookId}, query: { context: context.origin, contextId: context.id, incognito: true}}"
|
||||
:disabled="!canRead"
|
||||
>
|
||||
<v-icon left small>mdi-incognito</v-icon>
|
||||
|
|
@ -240,7 +240,7 @@
|
|||
<v-btn color="accent"
|
||||
small
|
||||
:title="$t('browse_book.read_book')"
|
||||
:to="{name: 'read-book', params: { bookId: bookId}, query: { context: context.origin, contextId: context.id}}"
|
||||
:to="{name: readRouteName, params: { bookId: bookId}, query: { context: context.origin, contextId: context.id}}"
|
||||
:disabled="!canRead"
|
||||
>
|
||||
<v-icon left small>mdi-book-open-page-variant</v-icon>
|
||||
|
|
@ -251,7 +251,7 @@
|
|||
<v-col cols="auto">
|
||||
<v-btn small
|
||||
:title="$t('browse_book.read_incognito')"
|
||||
:to="{name: 'read-book', params: { bookId: bookId}, query: { context: context.origin, contextId: context.id, incognito: true}}"
|
||||
:to="{name: readRouteName, params: { bookId: bookId}, query: { context: context.origin, contextId: context.id, incognito: true}}"
|
||||
:disabled="!canRead"
|
||||
>
|
||||
<v-icon left small>mdi-incognito</v-icon>
|
||||
|
|
@ -409,7 +409,7 @@ import BookActionsMenu from '@/components/menus/BookActionsMenu.vue'
|
|||
import ItemCard from '@/components/ItemCard.vue'
|
||||
import ToolbarSticky from '@/components/bars/ToolbarSticky.vue'
|
||||
import {groupAuthorsByRole} from '@/functions/authors'
|
||||
import {getBookFormatFromMediaType} from '@/functions/book-format'
|
||||
import {getBookFormatFromMediaType, getBookReadRouteFromMediaProfile} from '@/functions/book-format'
|
||||
import {getPagesLeft, getReadProgress, getReadProgressPercentage} from '@/functions/book-progress'
|
||||
import {getBookTitleCompact} from '@/functions/book-title'
|
||||
import {bookFileUrl, bookThumbnailUrl} from '@/functions/urls'
|
||||
|
|
@ -487,6 +487,9 @@ export default Vue.extend({
|
|||
next()
|
||||
},
|
||||
computed: {
|
||||
readRouteName(): string {
|
||||
return getBookReadRouteFromMediaProfile(this.book.media.mediaProfile)
|
||||
},
|
||||
isAdmin(): boolean {
|
||||
return this.$store.getters.meAdmin
|
||||
},
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@
|
|||
<v-btn color="accent"
|
||||
small
|
||||
:title="$t('browse_book.read_book')"
|
||||
:to="{name: 'read-book', params: { bookId: book.id}, query: { context: context.origin, contextId: context.id}}"
|
||||
:to="{name: readRouteName, params: { bookId: book.id}, query: { context: context.origin, contextId: context.id}}"
|
||||
:disabled="!canRead"
|
||||
>
|
||||
<v-icon left small>mdi-book-open-page-variant</v-icon>
|
||||
|
|
@ -228,7 +228,7 @@
|
|||
<v-col cols="auto">
|
||||
<v-btn small
|
||||
:title="$t('browse_book.read_incognito')"
|
||||
:to="{name: 'read-book', params: { bookId: book.id}, query: { context: context.origin, contextId: context.id, incognito: true}}"
|
||||
:to="{name: readRouteName, params: { bookId: book.id}, query: { context: context.origin, contextId: context.id, incognito: true}}"
|
||||
:disabled="!canRead"
|
||||
>
|
||||
<v-icon left small>mdi-incognito</v-icon>
|
||||
|
|
@ -263,7 +263,7 @@
|
|||
<v-btn color="accent"
|
||||
small
|
||||
:title="$t('browse_book.read_book')"
|
||||
:to="{name: 'read-book', params: { bookId: book.id}, query: { context: context.origin, contextId: context.id}}"
|
||||
:to="{name: readRouteName, params: { bookId: book.id}, query: { context: context.origin, contextId: context.id}}"
|
||||
:disabled="!canRead"
|
||||
>
|
||||
<v-icon left small>mdi-book-open-page-variant</v-icon>
|
||||
|
|
@ -274,7 +274,7 @@
|
|||
<v-col cols="auto">
|
||||
<v-btn small
|
||||
:title="$t('browse_book.read_incognito')"
|
||||
:to="{name: 'read-book', params: { bookId: book.id}, query: { context: context.origin, contextId: context.id, incognito: true}}"
|
||||
:to="{name: readRouteName, params: { bookId: book.id}, query: { context: context.origin, contextId: context.id, incognito: true}}"
|
||||
:disabled="!canRead"
|
||||
>
|
||||
<v-icon left small>mdi-incognito</v-icon>
|
||||
|
|
@ -484,7 +484,7 @@ import BookActionsMenu from '@/components/menus/BookActionsMenu.vue'
|
|||
import ItemCard from '@/components/ItemCard.vue'
|
||||
import ToolbarSticky from '@/components/bars/ToolbarSticky.vue'
|
||||
import {groupAuthorsByRole} from '@/functions/authors'
|
||||
import {getBookFormatFromMediaType} from '@/functions/book-format'
|
||||
import {getBookFormatFromMediaType, getBookReadRouteFromMediaProfile} from '@/functions/book-format'
|
||||
import {getPagesLeft, getReadProgress, getReadProgressPercentage} from '@/functions/book-progress'
|
||||
import {getBookTitleCompact} from '@/functions/book-title'
|
||||
import {bookFileUrl, seriesThumbnailUrl} from '@/functions/urls'
|
||||
|
|
@ -596,6 +596,9 @@ export default Vue.extend({
|
|||
next()
|
||||
},
|
||||
computed: {
|
||||
readRouteName(): string {
|
||||
return getBookReadRouteFromMediaProfile(this.book.media.mediaProfile)
|
||||
},
|
||||
isAdmin(): boolean {
|
||||
return this.$store.getters.meAdmin
|
||||
},
|
||||
|
|
|
|||
821
komga-webui/src/views/EpubReader.vue
Normal file
821
komga-webui/src/views/EpubReader.vue
Normal file
|
|
@ -0,0 +1,821 @@
|
|||
<template>
|
||||
<div id="root" :key="bookId">
|
||||
<v-slide-y-transition>
|
||||
<v-toolbar
|
||||
v-if="showToolbars"
|
||||
dense elevation="1"
|
||||
class="settings full-width"
|
||||
style="position: fixed; top: 0;z-index: 14"
|
||||
>
|
||||
<v-btn
|
||||
icon
|
||||
@click="closeBook"
|
||||
>
|
||||
<v-icon>mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
:disabled="!hasToc && !hasLandmarks && !hasPageList"
|
||||
icon
|
||||
@click="showToc = !showToc">
|
||||
<v-icon>mdi-table-of-contents</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-toolbar-title> {{ bookTitle }}</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<v-btn
|
||||
icon
|
||||
:disabled="!screenfull.isEnabled"
|
||||
@click="screenfull.isFullscreen ? screenfull.exit() : enterFullscreen()">
|
||||
<v-icon>{{ fullscreenIcon }}</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
icon
|
||||
@click="showHelp = !showHelp">
|
||||
<v-icon>mdi-help-circle</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<!-- <v-btn-->
|
||||
<!-- icon-->
|
||||
<!-- @click="showExplorer = !showExplorer"-->
|
||||
<!-- >-->
|
||||
<!-- <v-icon>mdi-view-grid</v-icon>-->
|
||||
<!-- </v-btn>-->
|
||||
|
||||
<v-btn
|
||||
icon
|
||||
@click="toggleSettings"
|
||||
>
|
||||
<v-icon>mdi-cog</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
</v-slide-y-transition>
|
||||
|
||||
<v-slide-y-reverse-transition>
|
||||
<!-- Bottom Toolbar-->
|
||||
<v-toolbar
|
||||
dense
|
||||
elevation="1"
|
||||
class="settings full-width"
|
||||
style="position: fixed; bottom: 0;z-index: 14"
|
||||
horizontal
|
||||
v-if="showToolbars"
|
||||
>
|
||||
<v-btn icon @click="previousBook">
|
||||
<v-icon>mdi-undo</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-spacer/>
|
||||
|
||||
<v-btn
|
||||
icon
|
||||
:disabled="!historyCanGoBack"
|
||||
@click="historyBack"
|
||||
>
|
||||
<v-icon>mdi-chevron-left</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
icon
|
||||
:disabled="!historyCanGoForward"
|
||||
@click="historyForward"
|
||||
>
|
||||
<v-icon>mdi-chevron-right</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-spacer/>
|
||||
|
||||
<v-btn icon @click="nextBook">
|
||||
<v-icon>mdi-redo</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
</v-slide-y-reverse-transition>
|
||||
|
||||
<v-navigation-drawer
|
||||
v-model="showToc"
|
||||
fixed
|
||||
temporary
|
||||
|
||||
:width="$vuetify.breakpoint.smAndUp ? 500 : $vuetify.breakpoint.width - 50"
|
||||
style="z-index: 15"
|
||||
>
|
||||
<v-tabs grow>
|
||||
<v-tab v-if="hasToc">
|
||||
<v-icon>mdi-table-of-contents</v-icon>
|
||||
</v-tab>
|
||||
<v-tab v-if="hasLandmarks">
|
||||
<v-icon>mdi-eiffel-tower</v-icon>
|
||||
</v-tab>
|
||||
<v-tab v-if="hasPageList">
|
||||
<v-icon>mdi-numeric</v-icon>
|
||||
</v-tab>
|
||||
|
||||
<v-tab-item v-if="hasToc" class="scrolltab">
|
||||
<toc-list :toc="tableOfContents" @goto="goToEntry" class="scrolltab-content"/>
|
||||
</v-tab-item>
|
||||
<v-tab-item v-if="hasLandmarks" class="scrolltab">
|
||||
<toc-list :toc="landmarks" @goto="goToEntry" class="scrolltab-content"/>
|
||||
</v-tab-item>
|
||||
<v-tab-item v-if="hasPageList" class="scrolltab">
|
||||
<toc-list :toc="pageList" @goto="goToEntry" class="scrolltab-content"/>
|
||||
</v-tab-item>
|
||||
</v-tabs>
|
||||
</v-navigation-drawer>
|
||||
|
||||
<header id="headerMenu"/>
|
||||
|
||||
<div id="D2Reader-Container" style="height: 100vh" :class="appearanceClass('bg')">
|
||||
<main tabindex=-1 id="iframe-wrapper" style="height: 100vh">
|
||||
<div id="reader-loading"></div>
|
||||
<div id="reader-error"></div>
|
||||
<div id="reader-info-top">
|
||||
<span class="book-title"></span>
|
||||
</div>
|
||||
<div id="reader-info-bottom">
|
||||
<div style="display: flex;justify-content: center;">
|
||||
<span id="chapter-position"></span>
|
||||
<span id="chapter-title"></span>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<a id="previous-chapter" rel="prev" role="button" aria-labelledby="previous-label"
|
||||
style="left: 50%;position: fixed;color: #000;height: 24px;background: #d3d3d33b; width: 150px;transform: translate(-50%, 0); display: block"
|
||||
:style="`top: ${showToolbars ? 48 : 0}px`"
|
||||
>
|
||||
<v-icon style="left: calc(50% - 12px); position: relative;">mdi-chevron-up</v-icon>
|
||||
</a>
|
||||
<a id="next-chapter" rel="next" role="button" aria-labelledby="next-label"
|
||||
style="bottom: 0;left: 50%;position: fixed;color: #000;height: 24px;background: #d3d3d33b; width: 150px;transform: translate(-50%, 0); display: block">
|
||||
<v-icon style="left: calc(50% - 12px);position: relative;">mdi-chevron-down</v-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<footer id="footerMenu">
|
||||
<a rel="prev" class="disabled" role="button" aria-labelledby="previous-label" style="top: 50%;left:0;position: fixed;height: 100px;
|
||||
background: #d3d3d33b;">
|
||||
<v-icon style="top: calc(50% - 12px);
|
||||
position: relative;">mdi-chevron-left
|
||||
</v-icon>
|
||||
</a>
|
||||
<a rel="next" class="disabled" role="button" aria-labelledby="next-label" style="top: 50%;right:0;position: fixed;height: 100px;
|
||||
background: #d3d3d33b;">
|
||||
<v-icon style="top: calc(50% - 12px);position: relative;">mdi-chevron-right</v-icon>
|
||||
</a>
|
||||
</footer>
|
||||
|
||||
<v-bottom-sheet
|
||||
v-model="showSettings"
|
||||
:close-on-content-click="false"
|
||||
max-width="500"
|
||||
@keydown.esc.stop=""
|
||||
scrollable
|
||||
>
|
||||
<v-card>
|
||||
<v-toolbar dark color="primary">
|
||||
<v-btn icon dark @click="showSettings = false">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
<v-toolbar-title>{{ $t('bookreader.reader_settings') }}</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
|
||||
<v-card-text class="pa-0">
|
||||
<v-list class="full-height full-width">
|
||||
<v-subheader class="font-weight-black text-h6">{{ $t('bookreader.settings.general') }}</v-subheader>
|
||||
<v-list-item>
|
||||
<settings-switch v-model="alwaysFullscreen" :label="$t('bookreader.settings.always_fullscreen')"
|
||||
:disabled="!screenfull.isEnabled"/>
|
||||
</v-list-item>
|
||||
|
||||
<v-subheader class="font-weight-black text-h6">{{ $t('bookreader.settings.display') }}</v-subheader>
|
||||
|
||||
<v-list-item>
|
||||
<v-list-item-title>{{ $t('epubreader.settings.viewing_theme') }}</v-list-item-title>
|
||||
<v-btn
|
||||
v-for="(a, i) in appearances"
|
||||
:key="i"
|
||||
:value="a.value"
|
||||
:color="a.color"
|
||||
:class="a.class"
|
||||
class="mx-1"
|
||||
@click="appearance = a.value"
|
||||
>
|
||||
<v-icon v-if="appearance === a.value">mdi-check</v-icon>
|
||||
</v-btn>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item>
|
||||
<v-list-item-title>{{ $t('epubreader.settings.layout') }}</v-list-item-title>
|
||||
<v-btn-toggle mandatory v-model="verticalScroll" class="py-3">
|
||||
<v-btn :value="true">{{ $t('epubreader.settings.layout_scroll') }}</v-btn>
|
||||
<v-btn :value="false">{{ $t('epubreader.settings.layout_paginated') }}</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item v-if="!verticalScroll">
|
||||
<v-list-item-title>{{ $t('epubreader.settings.column_count') }}</v-list-item-title>
|
||||
<v-btn-toggle mandatory v-model="columnCount" class="py-3">
|
||||
<v-btn v-for="(c, i) in columnCounts" :key="i" :value="c.value">{{ c.text }}</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item class="justify-center">
|
||||
<v-btn depressed @click="fontSize-=10">
|
||||
<v-icon small>mdi-format-title</v-icon>
|
||||
</v-btn>
|
||||
<span class="caption mx-8" style="width: 2rem">{{ fontSize }}%</span>
|
||||
<v-btn depressed @click="fontSize+=10">
|
||||
<v-icon>mdi-format-title</v-icon>
|
||||
</v-btn>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item class="justify-center">
|
||||
<v-btn depressed @click="lineHeight-=.1">
|
||||
<v-icon>$formatLineSpacingDown</v-icon>
|
||||
</v-btn>
|
||||
<span class="caption mx-8" style="width: 2rem">{{ Math.round(lineHeight * 100) }}%</span>
|
||||
<v-btn depressed @click="lineHeight+=.1">
|
||||
<v-icon>mdi-format-line-spacing</v-icon>
|
||||
</v-btn>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item>
|
||||
<v-slider
|
||||
v-model="pageMargins"
|
||||
:label="$t('epubreader.settings.page_margins')"
|
||||
min="0.5"
|
||||
max="4"
|
||||
step="0.25"
|
||||
ticks="always"
|
||||
tick-size="3"
|
||||
/>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-bottom-sheet>
|
||||
|
||||
<v-snackbar
|
||||
v-model="notification.enabled"
|
||||
centered
|
||||
:timeout="notification.timeout"
|
||||
>
|
||||
<p class="text-h6 text-center ma-0">
|
||||
{{ notification.message }}
|
||||
</p>
|
||||
</v-snackbar>
|
||||
|
||||
<shortcut-help-dialog
|
||||
v-model="showHelp"
|
||||
:shortcuts="shortcutsHelp"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import D2Reader, {Locator} from '@d-i-t-a/reader'
|
||||
import {bookManifestUrl} from '@/functions/urls'
|
||||
import {BookDto} from '@/types/komga-books'
|
||||
import {getBookTitleCompact} from '@/functions/book-title'
|
||||
import {SeriesDto} from '@/types/komga-series'
|
||||
import {Context, ContextOrigin} from '@/types/context'
|
||||
import SettingsSwitch from '@/components/SettingsSwitch.vue'
|
||||
import SettingsSelect from '@/components/SettingsSelect.vue'
|
||||
import {TocEntry} from '@/types/epub'
|
||||
import TocList from '@/components/TocList.vue'
|
||||
import {Locations} from '@d-i-t-a/reader/dist/types/model/Locator'
|
||||
import {epubShortcutsMenus, epubShortcutsSettings, shortcutsD2Reader} from '@/functions/shortcuts/epubreader'
|
||||
import {flattenToc} from '@/functions/toc'
|
||||
import ShortcutHelpDialog from '@/components/dialogs/ShortcutHelpDialog.vue'
|
||||
import screenfull from 'screenfull'
|
||||
import {getBookReadRouteFromMediaProfile} from '@/functions/book-format'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'EpubReader',
|
||||
components: {ShortcutHelpDialog, TocList, SettingsSelect, SettingsSwitch},
|
||||
data: function () {
|
||||
return {
|
||||
screenfull,
|
||||
fullscreenIcon: 'mdi-fullscreen',
|
||||
d2Reader: {} as D2Reader,
|
||||
book: undefined as unknown as BookDto,
|
||||
series: undefined as unknown as SeriesDto,
|
||||
siblingPrevious: {} as BookDto,
|
||||
siblingNext: {} as BookDto,
|
||||
incognito: false,
|
||||
context: {} as Context,
|
||||
contextName: '',
|
||||
showSettings: false,
|
||||
showToolbars: false,
|
||||
showToc: false,
|
||||
showHelp: false,
|
||||
shortcuts: {} as any,
|
||||
appearances: [
|
||||
{
|
||||
text: this.$t('enums.epubreader.appearances.day').toString(),
|
||||
value: 'readium-default-on',
|
||||
color: 'white',
|
||||
class: 'black--text',
|
||||
},
|
||||
{
|
||||
text: this.$t('enums.epubreader.appearances.sepia').toString(),
|
||||
value: 'readium-sepia-on',
|
||||
color: '#faf4e8',
|
||||
class: 'black--text',
|
||||
},
|
||||
{
|
||||
text: this.$t('enums.epubreader.appearances.night').toString(),
|
||||
value: 'readium-night-on',
|
||||
color: 'black',
|
||||
class: 'white--text',
|
||||
},
|
||||
],
|
||||
columnCounts: [
|
||||
{text: this.$t('enums.epubreader.column_count.auto').toString(), value: 'auto'},
|
||||
{text: this.$t('enums.epubreader.column_count.one').toString(), value: '1'},
|
||||
{text: this.$t('enums.epubreader.column_count.two').toString(), value: '2'},
|
||||
],
|
||||
settings: {
|
||||
appearance: 'readium-default-on',
|
||||
pageMargins: 1,
|
||||
lineHeight: 1,
|
||||
fontSize: 100,
|
||||
verticalScroll: false,
|
||||
columnCount: 'auto',
|
||||
alwaysFullscreen: false,
|
||||
},
|
||||
tocs: {
|
||||
toc: undefined as unknown as TocEntry[],
|
||||
landmarks: undefined as unknown as TocEntry[],
|
||||
pageList: undefined as unknown as TocEntry[],
|
||||
},
|
||||
currentLocation: undefined as unknown as Locator,
|
||||
historyCanGoBack: false,
|
||||
historyCanGoForward: false,
|
||||
notification: {
|
||||
enabled: false,
|
||||
message: '',
|
||||
timeout: 4000,
|
||||
},
|
||||
clickTimer: undefined,
|
||||
forceUpdate: false,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$vuetify.rtl = false
|
||||
this.shortcuts = this.$_.keyBy([...epubShortcutsSettings, ...epubShortcutsMenus], x => x.key)
|
||||
if (screenfull.isEnabled) screenfull.on('change', this.fullscreenChanged)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.d2Reader.stop()
|
||||
},
|
||||
destroyed() {
|
||||
this.$vuetify.rtl = (this.$t('common.locale_rtl') === 'true')
|
||||
if (screenfull.isEnabled) {
|
||||
screenfull.off('change', this.fullscreenChanged)
|
||||
screenfull.exit()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
Object.assign(this.settings, this.$store.state.persistedState.epubreader)
|
||||
this.settings.alwaysFullscreen = this.$store.state.persistedState.webreader.alwaysFullscreen
|
||||
|
||||
this.setup(this.bookId)
|
||||
},
|
||||
props: {
|
||||
bookId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
if (to.params.bookId !== from.params.bookId) {
|
||||
// route update means either:
|
||||
// - going to previous/next book, in this case the query.page is not set, so it will default to first page
|
||||
// - pressing the back button of the browser and navigating to the previous book, in this case the query.page is set, so we honor it
|
||||
this.d2Reader.stop()
|
||||
this.setup(to.params.bookId, Number(to.query.page))
|
||||
}
|
||||
next()
|
||||
},
|
||||
computed: {
|
||||
shortcutsHelp(): object {
|
||||
return {
|
||||
[this.$t('bookreader.shortcuts.reader_navigation').toString()]: [...shortcutsD2Reader],
|
||||
[this.$t('bookreader.shortcuts.settings').toString()]: [...epubShortcutsSettings],
|
||||
[this.$t('bookreader.shortcuts.menus').toString()]: epubShortcutsMenus,
|
||||
}
|
||||
},
|
||||
tableOfContents(): TocEntry[] {
|
||||
if (this.tocs.toc) return flattenToc(this.tocs.toc, 1, 0, this.currentLocation?.href)
|
||||
return []
|
||||
},
|
||||
landmarks(): TocEntry[] {
|
||||
if (this.tocs.landmarks) return flattenToc(this.tocs.landmarks, 1, 0, this.currentLocation?.href)
|
||||
return []
|
||||
},
|
||||
pageList(): TocEntry[] {
|
||||
if (this.tocs.pageList) return flattenToc(this.tocs.pageList, 1, 0, this.currentLocation?.href)
|
||||
return []
|
||||
},
|
||||
hasToc(): boolean {
|
||||
return this.tocs.toc?.length > 0
|
||||
},
|
||||
hasLandmarks(): boolean {
|
||||
return this.tocs.landmarks?.length > 0
|
||||
},
|
||||
hasPageList(): boolean {
|
||||
return this.tocs.pageList?.length > 0
|
||||
},
|
||||
bookTitle(): string {
|
||||
if (!!this.book && !!this.series)
|
||||
return getBookTitleCompact(this.book.metadata.title, this.series.metadata.title)
|
||||
return this.book?.metadata?.title
|
||||
},
|
||||
appearance: {
|
||||
get: function (): string {
|
||||
return this.settings.appearance
|
||||
},
|
||||
set: function (color: string): void {
|
||||
if (this.appearances.map(x => x.value).includes(color)) {
|
||||
this.settings.appearance = color
|
||||
this.d2Reader.applyUserSettings({appearance: color})
|
||||
this.$store.commit('setEpubreaderSettings', this.settings)
|
||||
}
|
||||
},
|
||||
},
|
||||
verticalScroll: {
|
||||
get: function (): boolean {
|
||||
return this.settings.verticalScroll
|
||||
},
|
||||
set: function (value: string): void {
|
||||
this.settings.verticalScroll = value
|
||||
this.d2Reader.applyUserSettings({verticalScroll: value})
|
||||
this.$store.commit('setEpubreaderSettings', this.settings)
|
||||
},
|
||||
},
|
||||
columnCount: {
|
||||
get: function (): boolean {
|
||||
return this.settings.columnCount
|
||||
},
|
||||
set: function (value: string): void {
|
||||
if (this.columnCounts.map(x => x.value).includes(value)) {
|
||||
this.settings.columnCount = value
|
||||
this.d2Reader.applyUserSettings({columnCount: value})
|
||||
this.$store.commit('setEpubreaderSettings', this.settings)
|
||||
}
|
||||
},
|
||||
},
|
||||
pageMargins: {
|
||||
get: function (): number {
|
||||
return this.settings.pageMargins
|
||||
},
|
||||
set: function (value: number): void {
|
||||
this.settings.pageMargins = value
|
||||
this.d2Reader.applyUserSettings({pageMargins: value})
|
||||
this.$store.commit('setEpubreaderSettings', this.settings)
|
||||
},
|
||||
},
|
||||
lineHeight: {
|
||||
get: function (): number {
|
||||
return this.settings.lineHeight
|
||||
},
|
||||
set: function (value: number): void {
|
||||
this.settings.lineHeight = value
|
||||
this.d2Reader.applyUserSettings({lineHeight: value})
|
||||
this.$store.commit('setEpubreaderSettings', this.settings)
|
||||
},
|
||||
},
|
||||
fontSize: {
|
||||
get: function (): number {
|
||||
return this.settings.fontSize
|
||||
},
|
||||
set: function (value: number): void {
|
||||
this.settings.fontSize = value
|
||||
this.d2Reader.applyUserSettings({fontSize: value})
|
||||
this.$store.commit('setEpubreaderSettings', this.settings)
|
||||
},
|
||||
},
|
||||
alwaysFullscreen: {
|
||||
get: function (): boolean {
|
||||
return this.settings.alwaysFullscreen
|
||||
},
|
||||
set: function (alwaysFullscreen: boolean): void {
|
||||
this.settings.alwaysFullscreen = alwaysFullscreen
|
||||
this.$store.commit('setWebreaderAlwaysFullscreen', alwaysFullscreen)
|
||||
if (alwaysFullscreen) this.enterFullscreen()
|
||||
else screenfull.isEnabled && screenfull.exit()
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
previousBook() {
|
||||
if (!this.$_.isEmpty(this.siblingPrevious)) {
|
||||
this.jumpToPreviousBook = false
|
||||
this.$router.push({
|
||||
name: getBookReadRouteFromMediaProfile(this.siblingPrevious.media.mediaProfile),
|
||||
params: {bookId: this.siblingPrevious.id.toString()},
|
||||
query: {context: this.context.origin, contextId: this.context.id, incognito: this.incognito.toString()},
|
||||
})
|
||||
}
|
||||
},
|
||||
nextBook() {
|
||||
if (this.$_.isEmpty(this.siblingNext)) {
|
||||
this.closeBook()
|
||||
} else {
|
||||
this.jumpToNextBook = false
|
||||
this.$router.push({
|
||||
name: getBookReadRouteFromMediaProfile(this.siblingNext.media.mediaProfile),
|
||||
params: {bookId: this.siblingNext.id.toString()},
|
||||
query: {context: this.context.origin, contextId: this.context.id, incognito: this.incognito.toString()},
|
||||
})
|
||||
}
|
||||
},
|
||||
enterFullscreen() {
|
||||
if (screenfull.isEnabled) screenfull.request(document.documentElement, {navigationUI: 'hide'})
|
||||
},
|
||||
switchFullscreen() {
|
||||
if (screenfull.isEnabled) screenfull.isFullscreen ? screenfull.exit() : this.enterFullscreen()
|
||||
},
|
||||
fullscreenChanged() {
|
||||
if (screenfull.isEnabled && screenfull.isFullscreen) this.fullscreenIcon = 'mdi-fullscreen-exit'
|
||||
else this.fullscreenIcon = 'mdi-fullscreen'
|
||||
},
|
||||
toggleToolbars() {
|
||||
this.showToolbars = !this.showToolbars
|
||||
},
|
||||
toggleSettings() {
|
||||
this.showSettings = !this.showSettings
|
||||
},
|
||||
toggleTableOfContents() {
|
||||
this.showToc = !this.showToc
|
||||
},
|
||||
toggleHelp() {
|
||||
this.showHelp = !this.showHelp
|
||||
},
|
||||
keyPressed(e: KeyboardEvent) {
|
||||
this.shortcuts[e.key]?.execute(this)
|
||||
},
|
||||
clickThrough(e: MouseEvent) {
|
||||
if (e.detail === 1) {
|
||||
this.clickTimer = setTimeout(() => {
|
||||
this.toggleToolbars()
|
||||
}, 200)
|
||||
}
|
||||
if (e.detail === 2) {
|
||||
clearTimeout(this.clickTimer)
|
||||
}
|
||||
},
|
||||
async setup(bookId: string) {
|
||||
this.book = await this.$komgaBooks.getBook(bookId)
|
||||
this.series = await this.$komgaSeries.getOneSeries(this.book.seriesId)
|
||||
|
||||
// parse query params to get context and contextId
|
||||
if (this.$route.query.contextId && this.$route.query.context
|
||||
&& Object.values(ContextOrigin).includes(this.$route.query.context as ContextOrigin)) {
|
||||
this.context = {
|
||||
origin: this.$route.query.context as ContextOrigin,
|
||||
id: this.$route.query.contextId as string,
|
||||
}
|
||||
this.book.context = this.context
|
||||
this.contextName = (await (this.$komgaReadLists.getOneReadList(this.context.id))).name
|
||||
document.title = `Komga - ${this.contextName} - ${this.book.metadata.title}`
|
||||
} else {
|
||||
document.title = `Komga - ${getBookTitleCompact(this.book.metadata.title, this.series.metadata.title)}`
|
||||
}
|
||||
|
||||
// parse query params to get incognito mode
|
||||
this.incognito = !!(this.$route.query.incognito && this.$route.query.incognito.toString().toLowerCase() === 'true')
|
||||
|
||||
this.d2Reader = await D2Reader.load({
|
||||
url: new URL(bookManifestUrl(this.bookId)),
|
||||
userSettings: this.settings,
|
||||
storageType: 'memory',
|
||||
injectables: [
|
||||
// webpack will process the new URL (https://webpack.js.org/guides/asset-modules/#url-assets)
|
||||
// we use a different extension so that the css-loader rule is not used (see vue.config.js)
|
||||
{
|
||||
type: 'style',
|
||||
url: new URL('../styles/readium/ReadiumCSS-before.css.resource', import.meta.url).toString(),
|
||||
r2before: true,
|
||||
},
|
||||
{
|
||||
type: 'style',
|
||||
url: new URL('../styles/readium/ReadiumCSS-default.css.resource', import.meta.url).toString(),
|
||||
r2default: true,
|
||||
},
|
||||
{
|
||||
type: 'style',
|
||||
url: new URL('../styles/readium/ReadiumCSS-after.css.resource', import.meta.url).toString(),
|
||||
r2after: true,
|
||||
},
|
||||
{type: 'style', url: new URL('../styles/r2d2bc/popup.css.resource', import.meta.url).toString()},
|
||||
{type: 'style', url: new URL('../styles/r2d2bc/popover.css.resource', import.meta.url).toString()},
|
||||
{type: 'style', url: new URL('../styles/r2d2bc/style.css.resource', import.meta.url).toString()},
|
||||
],
|
||||
requestConfig: {
|
||||
credentials: 'include',
|
||||
},
|
||||
attributes: {
|
||||
margin: 0, // subtract this from the iframe height, when setting the iframe minimum height
|
||||
navHeight: 10, // used for positioning the toolbox
|
||||
iframePaddingTop: 20, // top padding inside iframe
|
||||
bottomInfoHeight: 35, // #reader-info-bottom height
|
||||
},
|
||||
rights: {
|
||||
enableBookmarks: false,
|
||||
enableAnnotations: false,
|
||||
enableTTS: false,
|
||||
enableSearch: false,
|
||||
enableTimeline: false,
|
||||
enableDefinitions: false,
|
||||
enableContentProtection: false,
|
||||
enableMediaOverlays: false,
|
||||
enablePageBreaks: true,
|
||||
autoGeneratePositions: false,
|
||||
enableLineFocus: false,
|
||||
customKeyboardEvents: false,
|
||||
enableHistory: true,
|
||||
enableCitations: false,
|
||||
enableConsumption: false,
|
||||
},
|
||||
api: {
|
||||
updateCurrentLocation: this.updateCurrentLocation,
|
||||
keydownFallthrough: this.keyPressed,
|
||||
clickThrough: this.clickThrough,
|
||||
},
|
||||
})
|
||||
|
||||
this.tocs.toc = this.d2Reader.tableOfContents
|
||||
this.tocs.landmarks = this.d2Reader.landmarks
|
||||
this.tocs.pageList = this.d2Reader.pageList
|
||||
|
||||
if (this.alwaysFullscreen) this.enterFullscreen()
|
||||
|
||||
try {
|
||||
if (this?.context.origin === ContextOrigin.READLIST) {
|
||||
this.siblingNext = await this.$komgaReadLists.getBookSiblingNext(this.context.id, bookId)
|
||||
} else {
|
||||
this.siblingNext = await this.$komgaBooks.getBookSiblingNext(bookId)
|
||||
}
|
||||
} catch (e) {
|
||||
this.siblingNext = {} as BookDto
|
||||
}
|
||||
try {
|
||||
if (this?.context.origin === ContextOrigin.READLIST) {
|
||||
this.siblingPrevious = await this.$komgaReadLists.getBookSiblingPrevious(this.context.id, bookId)
|
||||
} else {
|
||||
this.siblingPrevious = await this.$komgaBooks.getBookSiblingPrevious(bookId)
|
||||
}
|
||||
} catch (e) {
|
||||
this.siblingPrevious = {} as BookDto
|
||||
}
|
||||
},
|
||||
historyBack() {
|
||||
this.d2Reader.historyBack()
|
||||
},
|
||||
historyForward() {
|
||||
this.d2Reader.historyForward()
|
||||
},
|
||||
updateCurrentLocation(location: Locator): Promise<Locator> {
|
||||
// handle history
|
||||
this.historyCanGoBack = this.d2Reader.historyCurrentIndex > 0
|
||||
this.historyCanGoForward = this.d2Reader.historyCurrentIndex < this.d2Reader.history?.length - 1
|
||||
|
||||
this.currentLocation = location
|
||||
return new Promise(function (resolve, _) {
|
||||
resolve(location)
|
||||
})
|
||||
},
|
||||
appearanceClass(suffix?: string): string {
|
||||
let c = this.appearance.replace('readium-', '').replace('-on', '').replace('default', 'day')
|
||||
if (suffix) c += `-${suffix}`
|
||||
return c
|
||||
},
|
||||
goToEntry(tocEntry: TocEntry) {
|
||||
if (tocEntry.href !== undefined) {
|
||||
const url = new URL(tocEntry.href)
|
||||
let locations = {
|
||||
progression: 0,
|
||||
} as Locations
|
||||
let href = tocEntry.href
|
||||
if (url.hash) {
|
||||
locations = {
|
||||
fragment: url.hash.slice(1),
|
||||
}
|
||||
href = tocEntry.href.substring(0, tocEntry.href.indexOf('#'))
|
||||
}
|
||||
let locator = {
|
||||
href: href,
|
||||
locations: locations,
|
||||
}
|
||||
this.d2Reader.goTo(locator)
|
||||
this.showToc = false
|
||||
}
|
||||
},
|
||||
closeDialog() {
|
||||
if (this.showToc) {
|
||||
this.showToc = false
|
||||
return
|
||||
}
|
||||
if (this.showSettings) {
|
||||
this.showSettings = false
|
||||
return
|
||||
}
|
||||
if (this.showToolbars) {
|
||||
this.showToolbars = false
|
||||
return
|
||||
}
|
||||
this.closeBook()
|
||||
},
|
||||
closeBook() {
|
||||
this.$router.push(
|
||||
{
|
||||
name: this.book.oneshot ? 'browse-oneshot' : 'browse-book',
|
||||
params: {bookId: this.bookId.toString(), seriesId: this.book.seriesId},
|
||||
query: {context: this.context.origin, contextId: this.context.id},
|
||||
})
|
||||
},
|
||||
cycleViewingTheme() {
|
||||
const i = (this.appearances.map(x => x.value).indexOf(this.settings.appearance) + 1) % this.appearances.length
|
||||
const newValue = this.appearances[i]
|
||||
this.appearance = newValue.value
|
||||
const text = this.$t(newValue.text)
|
||||
this.sendNotification(`${this.$t('epubreader.settings.viewing_theme')}: ${text}`)
|
||||
},
|
||||
changeLayout(scroll: boolean) {
|
||||
this.verticalScroll = scroll
|
||||
const text = scroll ? this.$t('epubreader.settings.layout_scroll') : this.$t('epubreader.settings.layout_paginated')
|
||||
this.sendNotification(`${this.$t('epubreader.settings.layout')}: ${text}`)
|
||||
},
|
||||
cyclePagination() {
|
||||
if (this.verticalScroll) {
|
||||
this.columnCount = 'auto'
|
||||
this.changeLayout(false)
|
||||
} else {
|
||||
const i = (this.columnCounts.map(x => x.value).indexOf(this.settings.columnCount) + 1) % this.columnCounts.length
|
||||
const newValue = this.columnCounts[i]
|
||||
this.columnCount = newValue.value
|
||||
const text = this.$t(newValue.text)
|
||||
this.sendNotification(`${this.$t('epubreader.settings.column_count')}: ${text}`)
|
||||
}
|
||||
},
|
||||
changeFontSize(increase: boolean) {
|
||||
this.fontSize += increase ? 10 : -10
|
||||
},
|
||||
sendNotification(message: string, timeout: number = 4000) {
|
||||
this.notification.timeout = timeout
|
||||
this.notification.message = message
|
||||
this.notification.enabled = true
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<style src="@d-i-t-a/reader/dist/reader.css"/>
|
||||
<style scoped>
|
||||
.settings {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.full-height {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sepia-bg {
|
||||
background-color: #faf4e8;
|
||||
}
|
||||
|
||||
.sepia {
|
||||
color: #faf4e8;
|
||||
}
|
||||
|
||||
.day-bg {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.day {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.night-bg {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.night {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.scrolltab {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.scrolltab-content {
|
||||
max-height: calc(100vh - 48px);
|
||||
}
|
||||
</style>
|
||||
78
komga-webui/tests/unit/functions/toc.spec.ts
Normal file
78
komga-webui/tests/unit/functions/toc.spec.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import {TocEntry} from '@/types/epub'
|
||||
import {flattenToc} from '@/functions/toc'
|
||||
|
||||
describe('Multiple levels', () => {
|
||||
const initial = [
|
||||
{title: '1'},
|
||||
{
|
||||
title: '2',
|
||||
children: [
|
||||
{title: '2.1'},
|
||||
{
|
||||
title: '2.2',
|
||||
children: [
|
||||
{
|
||||
title: '2.2.1',
|
||||
children: [
|
||||
{title: '2.2.1.1'},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
] as TocEntry[]
|
||||
|
||||
test('given toc when flattened then it should be flat with correct levels', () => {
|
||||
const flattened = flattenToc(initial)
|
||||
|
||||
expect(flattened.length).toEqual(6)
|
||||
|
||||
expect(flattened[0].title).toEqual('1')
|
||||
expect(flattened[0].level).toEqual(0)
|
||||
expect(flattened[0].children).toEqual(undefined)
|
||||
|
||||
expect(flattened[1].title).toEqual('2')
|
||||
expect(flattened[1].level).toEqual(0)
|
||||
expect(flattened[1].children).toEqual(undefined)
|
||||
|
||||
expect(flattened[2].title).toEqual('2.1')
|
||||
expect(flattened[2].level).toEqual(1)
|
||||
expect(flattened[2].children).toEqual(undefined)
|
||||
|
||||
expect(flattened[3].title).toEqual('2.2')
|
||||
expect(flattened[3].level).toEqual(1)
|
||||
expect(flattened[3].children).toEqual(undefined)
|
||||
|
||||
expect(flattened[4].title).toEqual('2.2.1')
|
||||
expect(flattened[4].level).toEqual(2)
|
||||
expect(flattened[4].children).toEqual(undefined)
|
||||
|
||||
expect(flattened[5].title).toEqual('2.2.1.1')
|
||||
expect(flattened[5].level).toEqual(3)
|
||||
expect(flattened[5].children).toEqual(undefined)
|
||||
})
|
||||
|
||||
test('given toc when flattened to a max of 2 then it should be flat with correct levels', () => {
|
||||
const flattened = flattenToc(initial, 1)
|
||||
|
||||
expect(flattened.length).toEqual(2)
|
||||
|
||||
expect(flattened[0].title).toEqual('1')
|
||||
expect(flattened[0].level).toEqual(0)
|
||||
expect(flattened[0].children).toEqual(undefined)
|
||||
|
||||
expect(flattened[1].title).toEqual('2')
|
||||
expect(flattened[1].level).toEqual(0)
|
||||
expect(flattened[1].children?.length).toEqual(4)
|
||||
|
||||
expect(flattened[1].children!![0].title).toEqual('2.1')
|
||||
expect(flattened[1].children!![0].level).toEqual(1)
|
||||
expect(flattened[1].children!![1].title).toEqual('2.2')
|
||||
expect(flattened[1].children!![1].level).toEqual(1)
|
||||
expect(flattened[1].children!![2].title).toEqual('2.2.1')
|
||||
expect(flattened[1].children!![2].level).toEqual(2)
|
||||
expect(flattened[1].children!![3].title).toEqual('2.2.1.1')
|
||||
expect(flattened[1].children!![3].level).toEqual(3)
|
||||
})
|
||||
})
|
||||
|
|
@ -12,4 +12,22 @@ module.exports = {
|
|||
enableInSFC: false,
|
||||
},
|
||||
},
|
||||
|
||||
// custom rule for readium and r2d2bc css that needs to be made available, but untouched
|
||||
configureWebpack: {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: [
|
||||
/readium\/.*\.css.resource$/,
|
||||
/r2d2bc\/.*\.css.resource$/,
|
||||
],
|
||||
type: 'asset/resource',
|
||||
generator: {
|
||||
filename: '[hash].css[query]',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue