paginar 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +13 -0
- package/.env +1 -0
- package/.vscode/extensions.json +3 -0
- package/README.md +14 -0
- package/demo/slot/slot-chapter1.html +142 -0
- package/demo/slot/slot-chapter2.html +359 -0
- package/demo/slot/slot-chapter3.html +259 -0
- package/demo/summary/_content/1chapter.html +97 -0
- package/demo/summary/_content/2chapter.html +311 -0
- package/demo/summary/_content/3chapter.html +211 -0
- package/demo/summary/index.html +99 -0
- package/dist/favicon.ico +0 -0
- package/dist/index.es.js +10224 -0
- package/dist/index.es.js.map +1 -0
- package/dist/style.css +975 -0
- package/index.html +18 -0
- package/package.json +35 -0
- package/postcss.config.js +7 -0
- package/public/favicon.ico +0 -0
- package/src/App.ce.vue +104 -0
- package/src/assets/main.css +100 -0
- package/src/components/EngineWrapper.vue +23 -0
- package/src/components/FileContent.ce.vue +20 -0
- package/src/components/FooterSlot.ce.vue +22 -0
- package/src/components/HeaderSlot/HeaderSlot.ce.vue +37 -0
- package/src/components/HeaderSlot/OptionsButton/OptionsButton.ce.vue +63 -0
- package/src/components/HeaderSlot/OptionsButton/OptionsColor.vue +28 -0
- package/src/components/HeaderSlot/OptionsButton/OptionsColumns.vue +41 -0
- package/src/components/HeaderSlot/OptionsButton/OptionsDropdown.vue +40 -0
- package/src/components/HeaderSlot/OptionsButton/OptionsFontFamily.vue +17 -0
- package/src/components/HeaderSlot/OptionsButton/OptionsFontSize.vue +17 -0
- package/src/components/HeaderSlot/SummaryButton/SummaryButton.ce.vue +64 -0
- package/src/components/HeaderSlot/SummaryButton/SummaryDropdown.vue +29 -0
- package/src/components/NavigationButton.ce.vue +34 -0
- package/src/components/ReaderWrapper.vue +16 -0
- package/src/components/icons/Close.vue +6 -0
- package/src/components/icons/DoubleColumn.vue +15 -0
- package/src/components/icons/Expand.vue +7 -0
- package/src/components/icons/Eye.vue +9 -0
- package/src/components/icons/Home.vue +6 -0
- package/src/components/icons/Moon.vue +5 -0
- package/src/components/icons/SingleColumn.vue +3 -0
- package/src/components/icons/Summary.vue +10 -0
- package/src/components/icons/Sun.vue +5 -0
- package/src/components/icons/ThreeDots.vue +8 -0
- package/src/composables/useEstimatePages.js +28 -0
- package/src/composables/usePagination.js +57 -0
- package/src/composables/useReaderSettings.js +125 -0
- package/src/composables/useStyles.js +38 -0
- package/src/composables/useTextContent.js +40 -0
- package/src/main.js +21 -0
- package/src/tailwind.css +975 -0
- package/tailwind.config.js +12 -0
- package/vite.config.js +32 -0
package/index.html
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8" />
|
|
6
|
+
<link rel="icon" href="/favicon.ico" />
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
|
+
<title>Paginar</title>
|
|
9
|
+
</head>
|
|
10
|
+
|
|
11
|
+
<body>
|
|
12
|
+
<div>
|
|
13
|
+
<p><a href="/demo/summary/index.html">Sumário (arquivos externos)</a></p>
|
|
14
|
+
<p><a href="/demo/slot/slot-chapter1.html">Slot (conteúdo direto na pagina)</a></p>
|
|
15
|
+
</div>
|
|
16
|
+
</body>
|
|
17
|
+
|
|
18
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "paginar",
|
|
3
|
+
"author": "Editora Sabiá (https://sabia.pub/)",
|
|
4
|
+
"description": "A flipping page web component for reading long texts on the brower",
|
|
5
|
+
"main": "dist/index.es.js",
|
|
6
|
+
"module": "dist/index.es.js",
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/educkf/paginar.git"
|
|
11
|
+
},
|
|
12
|
+
"license": "Apache-2.0",
|
|
13
|
+
"homepage": "https://github.com/educkf/paginar",
|
|
14
|
+
"scripts": {
|
|
15
|
+
"dev": "concurrently \"npm run start:css\" \"npm run start\"",
|
|
16
|
+
"start": "vite",
|
|
17
|
+
"start:css": "tailwindcss -o src/tailwind.css --watch",
|
|
18
|
+
"build": "npm run build:css && vite build",
|
|
19
|
+
"build:css": "tailwindcss -o src/tailwind.css",
|
|
20
|
+
"preview": "vite preview --port 4173"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@vueuse/core": "^9.2.0",
|
|
24
|
+
"vue": "^3.2.37"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@vitejs/plugin-vue": "^3.0.1",
|
|
28
|
+
"autoprefixer": "^10.4.8",
|
|
29
|
+
"concurrently": "^7.4.0",
|
|
30
|
+
"postcss": "^8.4.16",
|
|
31
|
+
"sass": "^1.54.2",
|
|
32
|
+
"tailwindcss": "^3.1.8",
|
|
33
|
+
"vite": "^3.0.1"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
Binary file
|
package/src/App.ce.vue
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, onMounted, defineProps } from 'vue'
|
|
3
|
+
import { useWindowSize, watchDebounced } from '@vueuse/core'
|
|
4
|
+
|
|
5
|
+
import HeaderSlot from './components/HeaderSlot/HeaderSlot.ce.vue'
|
|
6
|
+
import FileContent from './components/FileContent.ce.vue'
|
|
7
|
+
import EngineWrapper from './components/EngineWrapper.vue'
|
|
8
|
+
import ReaderWrapper from './components/ReaderWrapper.vue'
|
|
9
|
+
import FooterSlot from './components/FooterSlot.ce.vue'
|
|
10
|
+
|
|
11
|
+
import usePagination from './composables/usePagination'
|
|
12
|
+
import useEstimatePages from './composables/useEstimatePages'
|
|
13
|
+
import useTextContent from './composables/useTextContent'
|
|
14
|
+
import useReaderSettings from './composables/useReaderSettings'
|
|
15
|
+
import useStyles from './composables/useStyles'
|
|
16
|
+
|
|
17
|
+
const props = defineProps({
|
|
18
|
+
bookTitle: String,
|
|
19
|
+
bookContent: String,
|
|
20
|
+
readerSettings: String,
|
|
21
|
+
cssFile: String
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const { initSettings, baseFont, textFont, fontSize, columns, setColumns } = useReaderSettings
|
|
25
|
+
const { currentPage, totalPages, init } = usePagination
|
|
26
|
+
const { content } = useTextContent
|
|
27
|
+
const { width, height } = useWindowSize()
|
|
28
|
+
|
|
29
|
+
const readerComponent = ref(null)
|
|
30
|
+
const contentArea = ref(null)
|
|
31
|
+
const rootComponent = ref(null)
|
|
32
|
+
|
|
33
|
+
onMounted(async () => {
|
|
34
|
+
init(readerComponent, contentArea)
|
|
35
|
+
initSettings(props.readerSettings)
|
|
36
|
+
|
|
37
|
+
if (props.cssFile) {
|
|
38
|
+
useStyles.stylesheetLoader(props.cssFile, rootComponent)
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
watchDebounced(
|
|
43
|
+
[width, height, content, columns, fontSize, textFont],
|
|
44
|
+
() => {
|
|
45
|
+
if (width.value < 1024 && columns.value === 'doubleColumns')
|
|
46
|
+
setColumns('singleColumns')
|
|
47
|
+
else
|
|
48
|
+
useEstimatePages.estimate(readerComponent, contentArea)
|
|
49
|
+
},
|
|
50
|
+
{ debounce: 125, maxWait: 250 }
|
|
51
|
+
)
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<template>
|
|
55
|
+
<main ref="rootComponent" :style="`font-family: ${baseFont}`">
|
|
56
|
+
<HeaderSlot>
|
|
57
|
+
<template #header>
|
|
58
|
+
<slot name="header">
|
|
59
|
+
<p class="text-white text-center text-xs">
|
|
60
|
+
{{ props.bookTitle }}
|
|
61
|
+
</p>
|
|
62
|
+
</slot>
|
|
63
|
+
</template>
|
|
64
|
+
|
|
65
|
+
<template #summaryTop>
|
|
66
|
+
<slot name="summaryTop"></slot>
|
|
67
|
+
</template>
|
|
68
|
+
|
|
69
|
+
<template #summaryBottom>
|
|
70
|
+
<slot name="summaryBottom"></slot>
|
|
71
|
+
</template>
|
|
72
|
+
|
|
73
|
+
<template #optionsTop>
|
|
74
|
+
<slot name="optionsTop"></slot>
|
|
75
|
+
</template>
|
|
76
|
+
|
|
77
|
+
<template #optionsBottom>
|
|
78
|
+
<slot name="optionsBottom"></slot>
|
|
79
|
+
</template>
|
|
80
|
+
</HeaderSlot>
|
|
81
|
+
|
|
82
|
+
<EngineWrapper>
|
|
83
|
+
<div id="reader-component" ref="readerComponent">
|
|
84
|
+
<ReaderWrapper>
|
|
85
|
+
<section id="content-area" ref="contentArea"
|
|
86
|
+
:style="`font-family: ${textFont}; font-size: ${fontSize}px`"
|
|
87
|
+
>
|
|
88
|
+
<slot name="content">
|
|
89
|
+
<!-- default when no content slot, and there is a summary -->
|
|
90
|
+
<FileContent :bookContent="bookContent" />
|
|
91
|
+
</slot>
|
|
92
|
+
</section>
|
|
93
|
+
</ReaderWrapper>
|
|
94
|
+
</div>
|
|
95
|
+
</EngineWrapper>
|
|
96
|
+
|
|
97
|
+
<FooterSlot
|
|
98
|
+
:currPage="currentPage"
|
|
99
|
+
:totalPages="totalPages"
|
|
100
|
+
/>
|
|
101
|
+
</main>
|
|
102
|
+
</template>
|
|
103
|
+
|
|
104
|
+
<style lang="scss" src="./assets/main.css"></style>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
header.wrapper {
|
|
2
|
+
width: 200px;
|
|
3
|
+
margin: 0 auto;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
#engine {
|
|
7
|
+
height: calc(100vh - 7.6rem);
|
|
8
|
+
position: relative;
|
|
9
|
+
right: 0;
|
|
10
|
+
left: 0;
|
|
11
|
+
bottom: 0;
|
|
12
|
+
width: 100%;
|
|
13
|
+
|
|
14
|
+
&.light {
|
|
15
|
+
background-color: #fff7e0
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
&.dark {
|
|
19
|
+
background-color: #2d2d2d;
|
|
20
|
+
|
|
21
|
+
#content-area {
|
|
22
|
+
color: #fff
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#reader-component {
|
|
28
|
+
width: 100%;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.typeArea {
|
|
32
|
+
width: 100%;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.engineWrapper {
|
|
36
|
+
height: calc(100% - 2rem);
|
|
37
|
+
display: flex;
|
|
38
|
+
justify-content: space-between;
|
|
39
|
+
position: relative;
|
|
40
|
+
right: 0;
|
|
41
|
+
left: 0;
|
|
42
|
+
bottom: 0;
|
|
43
|
+
width: 100%;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.doubleColumns {
|
|
47
|
+
-moz-column-count: 2;
|
|
48
|
+
column-count: 2;
|
|
49
|
+
grid-column-gap: 0;
|
|
50
|
+
-moz-column-gap: 0;
|
|
51
|
+
column-gap: 0;
|
|
52
|
+
height: 100%;
|
|
53
|
+
padding: 0;
|
|
54
|
+
width: 100%;
|
|
55
|
+
|
|
56
|
+
@media only screen and (max-width: 1020px) {
|
|
57
|
+
-moz-column-count: 1;
|
|
58
|
+
column-count: 1;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.singleColumns {
|
|
63
|
+
column-count: 1;
|
|
64
|
+
column-gap: 0;
|
|
65
|
+
height: 100%;
|
|
66
|
+
padding: 0;
|
|
67
|
+
width: 100%;
|
|
68
|
+
|
|
69
|
+
#content-area {
|
|
70
|
+
max-width: 37em;
|
|
71
|
+
margin: 0 auto;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
.nav-prev {
|
|
77
|
+
@apply left-0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.nav-next {
|
|
81
|
+
@apply right-0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.viewer-nav {
|
|
85
|
+
height: 100%;
|
|
86
|
+
|
|
87
|
+
@apply fixed md:relative z-10 inset-y-0 transition duration-500 ease-in-out self-center w-10 cursor-pointer flex-shrink-0;
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
&:focus,
|
|
91
|
+
&:active {
|
|
92
|
+
border: none !important;
|
|
93
|
+
box-shadow: none !important;
|
|
94
|
+
outline-color: transparent !important;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.viewer-nav-icon {
|
|
99
|
+
@apply opacity-0 md:opacity-80;
|
|
100
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import NavigationButton from './NavigationButton.ce.vue'
|
|
3
|
+
|
|
4
|
+
import usePagination from '../composables/usePagination'
|
|
5
|
+
import useReaderSettings from '../composables/useReaderSettings'
|
|
6
|
+
|
|
7
|
+
const { next, prev } = usePagination
|
|
8
|
+
const { mode } = useReaderSettings
|
|
9
|
+
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<div id="engine"
|
|
14
|
+
class="pt-10 transition-colors"
|
|
15
|
+
:class="mode"
|
|
16
|
+
>
|
|
17
|
+
<div class="engineWrapper overflow-hidden">
|
|
18
|
+
<NavigationButton target="prev" @clicked="prev()" />
|
|
19
|
+
<slot></slot>
|
|
20
|
+
<NavigationButton target="next" @clicked="next()" />
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { defineProps, onMounted } from 'vue'
|
|
3
|
+
|
|
4
|
+
import useTextContent from '../composables/useTextContent'
|
|
5
|
+
const { content, initContent } = useTextContent
|
|
6
|
+
|
|
7
|
+
const props = defineProps({
|
|
8
|
+
bookContent: String
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
onMounted(() => {
|
|
12
|
+
initContent(props.bookContent)
|
|
13
|
+
})
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div>
|
|
18
|
+
<div v-html="content"></div>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import usePagination from '../composables/usePagination';
|
|
3
|
+
const { totalPages, currentPage, set } = usePagination
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<div class="bg-gray-800 flex justify-center items-center p-2 text-white">
|
|
8
|
+
<div>
|
|
9
|
+
<input type="range"
|
|
10
|
+
:min="1"
|
|
11
|
+
:max="totalPages"
|
|
12
|
+
:value="currentPage"
|
|
13
|
+
@input="event => set(Number(event.target.value))"
|
|
14
|
+
>
|
|
15
|
+
|
|
16
|
+
<slot name="footer">
|
|
17
|
+
<span>Página {{ currentPage }} de {{ totalPages }}</span>
|
|
18
|
+
<slot name="default"></slot>
|
|
19
|
+
</slot>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</template>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import SummaryButton from './SummaryButton/SummaryButton.ce.vue';
|
|
3
|
+
import OptionsButton from './OptionsButton/OptionsButton.ce.vue';
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<div class="bg-gray-800 flex justify-between items-center p-4">
|
|
8
|
+
<summary-button>
|
|
9
|
+
<template #summaryTop>
|
|
10
|
+
<slot name="summaryTop" />
|
|
11
|
+
</template>
|
|
12
|
+
<template #summaryBottom>
|
|
13
|
+
<slot name="summaryBottom" />
|
|
14
|
+
</template>
|
|
15
|
+
</summary-button>
|
|
16
|
+
|
|
17
|
+
<div>
|
|
18
|
+
<slot name="header" />
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<options-button>
|
|
22
|
+
<template #optionsTop>
|
|
23
|
+
<slot name="optionsTop" />
|
|
24
|
+
</template>
|
|
25
|
+
<template #optionsBottom>
|
|
26
|
+
<slot name="optionsBottom" />
|
|
27
|
+
</template>
|
|
28
|
+
</options-button>
|
|
29
|
+
</div>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<style>
|
|
33
|
+
.headerSlot {
|
|
34
|
+
text-align: center;
|
|
35
|
+
border: 1px dashed #000;
|
|
36
|
+
}
|
|
37
|
+
</style>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import { onClickOutside, onKeyStroke, useFullscreen } from '@vueuse/core'
|
|
4
|
+
|
|
5
|
+
import IconThreeDots from '../../icons/ThreeDots.vue'
|
|
6
|
+
import IconClose from '../../icons/Close.vue'
|
|
7
|
+
import IconExpand from '../../icons/Expand.vue'
|
|
8
|
+
import OptionsDropdown from './OptionsDropdown.vue'
|
|
9
|
+
|
|
10
|
+
const show = ref(false)
|
|
11
|
+
const button = ref(null)
|
|
12
|
+
|
|
13
|
+
const toggleSummary = () => show.value = !show.value
|
|
14
|
+
const hide = () => show.value = false
|
|
15
|
+
|
|
16
|
+
onClickOutside(button, () => hide())
|
|
17
|
+
onKeyStroke('Escape', () => hide())
|
|
18
|
+
|
|
19
|
+
const { toggle: toggleFullScreen } = useFullscreen()
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<div ref="button" class="relative">
|
|
24
|
+
<div class="flex">
|
|
25
|
+
<button @click.prevent="toggleSummary()" class="border p-3 shadow flex items-center" :class="{
|
|
26
|
+
'border-white text-white': invertBackground || show,
|
|
27
|
+
'border-gray-800 text-gray-800 dark:border-white dark:text-white': !invertBackground && !show,
|
|
28
|
+
'bg-terra': show
|
|
29
|
+
}" id="summary-menu" aria-haspopup="true" :aria-expanded="show">
|
|
30
|
+
<span class="block mr-3">
|
|
31
|
+
Opções
|
|
32
|
+
</span>
|
|
33
|
+
|
|
34
|
+
<IconClose v-if="show" class="w-6 h-6" />
|
|
35
|
+
<IconThreeDots v-else class="w-6 h-6" />
|
|
36
|
+
</button>
|
|
37
|
+
|
|
38
|
+
<button @click.prevent="toggleFullScreen()" class="hidden md:flex items-center border p-3 shadow ml-3" :class="{
|
|
39
|
+
'border-white text-white': invertBackground,
|
|
40
|
+
'border-gray-800 text-gray-800 dark:border-white dark:text-white': !invertBackground
|
|
41
|
+
}">
|
|
42
|
+
<IconExpand class="w-6 h-6" />
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<transition enter-active-class="transition ease-out duration-100 transform" enter-class="opacity-0 scale-95"
|
|
47
|
+
enter-to-class="opacity-100 scale-100" leave-active-class="transition ease-in duration-75 transform"
|
|
48
|
+
leave-class="opacity-100 scale-100" leave-to-class="opacity-0 scale-95"
|
|
49
|
+
>
|
|
50
|
+
<OptionsDropdown v-show="show">
|
|
51
|
+
<template #optionsTop>
|
|
52
|
+
<slot name="optionsTop"/>
|
|
53
|
+
</template>
|
|
54
|
+
<template #optionsBottom>
|
|
55
|
+
<slot name="optionsBottom"/>
|
|
56
|
+
</template>
|
|
57
|
+
</OptionsDropdown>
|
|
58
|
+
</transition>
|
|
59
|
+
</div>
|
|
60
|
+
</template>
|
|
61
|
+
|
|
62
|
+
<style>
|
|
63
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import IconSun from '../../icons/Sun.vue'
|
|
3
|
+
import IconMoon from '../../icons/Moon.vue'
|
|
4
|
+
|
|
5
|
+
import useReaderSettings from '../../../composables/useReaderSettings'
|
|
6
|
+
const { mode, setMode } = useReaderSettings
|
|
7
|
+
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<button @click.prevent="setMode('light')"
|
|
12
|
+
title="Leitura diurna"
|
|
13
|
+
class="font-light text-base border border-urucum w-full h-10 flex justify-center items-center"
|
|
14
|
+
:class="{
|
|
15
|
+
'bg-gray-100 text-gray-800 border-2 border-gray-500': mode === 'light'
|
|
16
|
+
}">
|
|
17
|
+
<IconSun class="w-5 h-5" />
|
|
18
|
+
</button>
|
|
19
|
+
|
|
20
|
+
<button @click.prevent="setMode('dark')"
|
|
21
|
+
title="Leitura noturna"
|
|
22
|
+
class="font-light text-base border border-urucum w-full h-10 flex justify-center items-center"
|
|
23
|
+
:class="{
|
|
24
|
+
'bg-gray-100 text-gray-800 border-2 border-gray-500': mode === 'dark'
|
|
25
|
+
}">
|
|
26
|
+
<IconMoon class="w-5 h-5" />
|
|
27
|
+
</button>
|
|
28
|
+
</template>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { useWindowSize } from '@vueuse/core'
|
|
3
|
+
import SingleColumn from '../../icons/SingleColumn.vue'
|
|
4
|
+
import DoubleColumn from '../../icons/DoubleColumn.vue'
|
|
5
|
+
|
|
6
|
+
import useReaderSettings from '../../../composables/useReaderSettings'
|
|
7
|
+
const { columns, setColumns } = useReaderSettings
|
|
8
|
+
const { width } = useWindowSize()
|
|
9
|
+
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<div class="w-full hidden md:grid grid-cols-3 gap-2 mt-3">
|
|
14
|
+
<button
|
|
15
|
+
class="col-span-1 text-center cursor-pointer py-3 rounded-sm border"
|
|
16
|
+
:class="{
|
|
17
|
+
'border-gray-300': columns !== 'singleColumns',
|
|
18
|
+
'bg-gray-100 text-gray-800 border-2 border-gray-500': columns === 'singleColumns'
|
|
19
|
+
}"
|
|
20
|
+
@click.prevent="setColumns('singleColumns')"
|
|
21
|
+
aria-label="Coluna única"
|
|
22
|
+
title="Coluna única"
|
|
23
|
+
>
|
|
24
|
+
<SingleColumn class="mx-auto h-10 opacity-75" />
|
|
25
|
+
</button>
|
|
26
|
+
|
|
27
|
+
<button
|
|
28
|
+
class="col-span-2 text-center cursor-pointer py-3 rounded-sm border"
|
|
29
|
+
:class="{
|
|
30
|
+
'border-gray-300': columns !== 'doubleColumns',
|
|
31
|
+
'bg-gray-100 text-gray-800 border-2 border-gray-500': columns === 'doubleColumns',
|
|
32
|
+
'opacity-30 bg-gray-300': width < 1024
|
|
33
|
+
}"
|
|
34
|
+
@click.prevent="width >= 1024 && setColumns('doubleColumns')"
|
|
35
|
+
aria-label="Coluna dupla"
|
|
36
|
+
title="Coluna dupla"
|
|
37
|
+
>
|
|
38
|
+
<DoubleColumn name="DoubleColumn" class="mx-auto h-10 divide-opacity-75" />
|
|
39
|
+
</button>
|
|
40
|
+
</div>
|
|
41
|
+
</template>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import IconEye from '../../icons/Eye.vue'
|
|
3
|
+
import OptionsColumns from './OptionsColumns.vue';
|
|
4
|
+
import OptionsFontFamily from './OptionsFontFamily.vue';
|
|
5
|
+
import OptionsFontSize from './OptionsFontSize.vue';
|
|
6
|
+
import OptionsColor from './OptionsColor.vue'
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<div class="absolute top-14 py-4 px-3 shadow-lg w-60 md:w-104
|
|
11
|
+
text-areia -right-2 md:right-16 z-10 bg-white
|
|
12
|
+
"
|
|
13
|
+
role="Opções"
|
|
14
|
+
aria-orientation="vertical"
|
|
15
|
+
aria-labelledby="options-menu"
|
|
16
|
+
>
|
|
17
|
+
<div>
|
|
18
|
+
|
|
19
|
+
<slot name="optionsTop" />
|
|
20
|
+
|
|
21
|
+
<div class="w-full flex justify-center items-center py-3">
|
|
22
|
+
<IconEye class="w-6 h-6" />
|
|
23
|
+
<span class="ml-4">Visualização</span>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<OptionsColumns />
|
|
27
|
+
<OptionsFontFamily />
|
|
28
|
+
|
|
29
|
+
<div class="w-full grid grid-cols-4 gap-2 my-3">
|
|
30
|
+
|
|
31
|
+
<OptionsFontSize />
|
|
32
|
+
<OptionsColor />
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div class="hidden w-full border-b border-areia my-8"></div>
|
|
36
|
+
|
|
37
|
+
<slot name="optionsBottom" />
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import useReaderSettings from '../../../composables/useReaderSettings'
|
|
3
|
+
const { textFont, fontsOptions, setTextFont } = useReaderSettings
|
|
4
|
+
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<div class="w-full grid grid-cols-4 gap-2 my-3">
|
|
9
|
+
<button v-for="font in fontsOptions" :key="font.name" @click.prevent="setTextFont(font.name)"
|
|
10
|
+
class="font-light text-lg tracking-wide border border-urucum w-full h-10 flex justify-center items-center"
|
|
11
|
+
:class="{
|
|
12
|
+
'bg-gray-100 text-gray-800 border-2 border-gray-500': textFont === font.name
|
|
13
|
+
}" :style="`font-family: ${font.name}`" :title="font.label ?? font.name">
|
|
14
|
+
Ff
|
|
15
|
+
</button>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import useReaderSettings from '../../../composables/useReaderSettings'
|
|
3
|
+
const { fontSize, setFontSize } = useReaderSettings
|
|
4
|
+
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<button @click.prevent="setFontSize(fontSize + 1)"
|
|
9
|
+
class="font-light text-base border border-urucum w-full h-10 flex justify-center items-center">
|
|
10
|
+
+ A
|
|
11
|
+
</button>
|
|
12
|
+
|
|
13
|
+
<button @click.prevent="setFontSize(fontSize - 1)"
|
|
14
|
+
class="font-light text-base border border-urucum w-full h-10 flex justify-center items-center">
|
|
15
|
+
- A
|
|
16
|
+
</button>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import { onClickOutside, onKeyStroke } from '@vueuse/core'
|
|
4
|
+
|
|
5
|
+
import IconHome from '../../icons/Home.vue'
|
|
6
|
+
import IconClose from '../../icons/Close.vue'
|
|
7
|
+
import IconSummary from '../../icons/Summary.vue'
|
|
8
|
+
import SummaryDropdown from './SummaryDropdown.vue'
|
|
9
|
+
|
|
10
|
+
const show = ref(false)
|
|
11
|
+
const button = ref(null)
|
|
12
|
+
|
|
13
|
+
const toggleSummary = () => show.value = !show.value
|
|
14
|
+
const hide = () => show.value = false
|
|
15
|
+
|
|
16
|
+
onClickOutside(button, () => hide())
|
|
17
|
+
onKeyStroke('Escape', () => hide())
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<div ref="button" class="relative">
|
|
22
|
+
<div class="flex">
|
|
23
|
+
<a href="/" title="Homepage"
|
|
24
|
+
class="hidden md:flex items-center border p-3 shadow mr-3
|
|
25
|
+
border-gray-800 text-gray-800 dark:border-white dark:text-white"
|
|
26
|
+
>
|
|
27
|
+
<IconHome class="w-6 h-6"/>
|
|
28
|
+
</a>
|
|
29
|
+
|
|
30
|
+
<button
|
|
31
|
+
@click.prevent="toggleSummary()"
|
|
32
|
+
class="border p-3 shadow flex items-center"
|
|
33
|
+
:class="{
|
|
34
|
+
'border-white text-white': invertBackground || show,
|
|
35
|
+
'border-gray-800 text-gray-800 dark:border-white dark:text-white': !invertBackground && !show,
|
|
36
|
+
'bg-terra': show
|
|
37
|
+
}"
|
|
38
|
+
id="summary-menu" aria-haspopup="true" :aria-expanded="show"
|
|
39
|
+
>
|
|
40
|
+
<IconSummary v-if="!show" />
|
|
41
|
+
<IconClose v-else class="w-6 h-6" />
|
|
42
|
+
|
|
43
|
+
<span class="block ml-3">
|
|
44
|
+
Sumário
|
|
45
|
+
</span>
|
|
46
|
+
</button>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<transition enter-active-class="transition ease-out duration-100 transform" enter-class="opacity-0 scale-95"
|
|
50
|
+
enter-to-class="opacity-100 scale-100" leave-active-class="transition ease-in duration-75 transform"
|
|
51
|
+
leave-class="opacity-100 scale-100" leave-to-class="opacity-0 scale-95"
|
|
52
|
+
>
|
|
53
|
+
<SummaryDropdown v-if="show">
|
|
54
|
+
<template #summaryTop>
|
|
55
|
+
<slot name="summaryTop"/>
|
|
56
|
+
</template>
|
|
57
|
+
|
|
58
|
+
<template #summaryBottom>
|
|
59
|
+
<slot name="summaryBottom"/>
|
|
60
|
+
</template>
|
|
61
|
+
</SummaryDropdown>
|
|
62
|
+
</transition>
|
|
63
|
+
</div>
|
|
64
|
+
</template>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import useTextContent from '../../../composables/useTextContent'
|
|
3
|
+
import usePagination from '../../../composables/usePagination'
|
|
4
|
+
const { summary } = useTextContent
|
|
5
|
+
|
|
6
|
+
function getChapter(item) {
|
|
7
|
+
useTextContent.getContent(item.file)
|
|
8
|
+
usePagination.set(1)
|
|
9
|
+
}
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<div class="absolute top-14 py-4 px-3 shadow-lg w-60 md:w-104
|
|
14
|
+
text-areia -left-2 md:left-16 z-10 bg-white" :role="Sumário" aria-orientation="vertical"
|
|
15
|
+
aria-labelledby="summary-menu">
|
|
16
|
+
<nav>
|
|
17
|
+
<slot name="summaryTop" />
|
|
18
|
+
|
|
19
|
+
<component v-for="item in summary" :is="item.link ? 'a' : 'button'" :key="item.link"
|
|
20
|
+
:href="item.link" :title="`Navegar para capítulo ${item.title}`"
|
|
21
|
+
class="w-full text-left block text-black py-2 px-3 hover:bg-gray-100 rounded mb-2"
|
|
22
|
+
@click="item.file ? getChapter(item) : null">
|
|
23
|
+
{{ item.title }}
|
|
24
|
+
</component>
|
|
25
|
+
|
|
26
|
+
<slot name="summaryBottom" />
|
|
27
|
+
</nav>
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|