chore: 版本升级到0.4.0并添加右键颜色设置菜单
- 新增右键快捷菜单,支持调整字体和背景颜色 - 移除页面点击翻页默认事件,新增自定义右键菜单逻辑 - 新增颜色选择器相关样式与工具函数 - 优化弹窗关闭逻辑,支持ESC键隐藏菜单
This commit is contained in:
parent
ee4d7df7d0
commit
1458397398
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "stealthreader",
|
"name": "stealthreader",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.3.2",
|
"version": "0.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="glass-panel" :style="readerStyle" data-tauri-drag-region @mousedown="onWindowMouseDown">
|
<div
|
||||||
|
class="glass-panel"
|
||||||
|
:style="readerStyle"
|
||||||
|
data-tauri-drag-region
|
||||||
|
@mousedown="onWindowMouseDown"
|
||||||
|
@contextmenu.prevent="onContextMenu"
|
||||||
|
>
|
||||||
<div class="reading-wrap">
|
<div class="reading-wrap">
|
||||||
<div class="chapter-name" :class="{ 'chapter-name--hidden': currentPage !== 0 }">
|
<div class="chapter-name" :class="{ 'chapter-name--hidden': currentPage !== 0 }">
|
||||||
{{ chapter?.title || "加载中..." }}
|
{{ chapter?.title || "加载中..." }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ref="pageEl" class="page-body" @click="onPageClick">
|
<div ref="pageEl" class="page-body">
|
||||||
<div class="page-text">{{ currentText }}</div>
|
<div class="page-text">{{ currentText }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -26,6 +32,67 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<n-popover
|
||||||
|
v-model:show="menuShow"
|
||||||
|
trigger="manual"
|
||||||
|
:x="menuX"
|
||||||
|
:y="menuY"
|
||||||
|
placement="bottom-start"
|
||||||
|
:show-arrow="false"
|
||||||
|
@clickoutside="menuShow = false"
|
||||||
|
>
|
||||||
|
<div class="ctx-menu" @contextmenu.prevent>
|
||||||
|
<div class="ctx-item">
|
||||||
|
<span class="ctx-label">字体颜色</span>
|
||||||
|
<div class="color-picker-control">
|
||||||
|
<n-color-picker v-model:value="menuFontColor" show-alpha :modes="['hex']" placement="bottom-end">
|
||||||
|
<template #trigger="{ value, onClick, ref: triggerRef }">
|
||||||
|
<div
|
||||||
|
:ref="triggerRef"
|
||||||
|
class="color-swatch"
|
||||||
|
:style="{ backgroundColor: value || '#000000' }"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
aria-label="选择字体颜色"
|
||||||
|
@click="onClick"
|
||||||
|
@keydown.enter="onClick"
|
||||||
|
@keydown.space.prevent="onClick"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</n-color-picker>
|
||||||
|
<div class="color-values">
|
||||||
|
<span>{{ getColorDetails(menuFontColor).color }}</span>
|
||||||
|
<small>透明度 {{ getColorDetails(menuFontColor).opacity }}%</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ctx-item">
|
||||||
|
<span class="ctx-label">背景颜色</span>
|
||||||
|
<div class="color-picker-control">
|
||||||
|
<n-color-picker v-model:value="menuBackgroundColor" show-alpha :modes="['hex']" placement="bottom-end">
|
||||||
|
<template #trigger="{ value, onClick, ref: triggerRef }">
|
||||||
|
<div
|
||||||
|
:ref="triggerRef"
|
||||||
|
class="color-swatch"
|
||||||
|
:style="{ backgroundColor: value || '#ffffff' }"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
aria-label="选择背景颜色"
|
||||||
|
@click="onClick"
|
||||||
|
@keydown.enter="onClick"
|
||||||
|
@keydown.space.prevent="onClick"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</n-color-picker>
|
||||||
|
<div class="color-values">
|
||||||
|
<span>{{ getColorDetails(menuBackgroundColor).color }}</span>
|
||||||
|
<small>透明度 {{ getColorDetails(menuBackgroundColor).opacity }}%</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-popover>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -123,20 +190,56 @@ const nextPage = () => {
|
||||||
if (currentPage.value < pages.value.length - 1) currentPage.value++
|
if (currentPage.value < pages.value.length - 1) currentPage.value++
|
||||||
}
|
}
|
||||||
|
|
||||||
const onPageClick = (e: MouseEvent) => {
|
// 右键快捷设置菜单(字体颜色 / 背景颜色)
|
||||||
const el = e.currentTarget as HTMLElement
|
const menuShow = ref(false)
|
||||||
const rect = el.getBoundingClientRect()
|
const menuX = ref(0)
|
||||||
if (e.clientX - rect.left < rect.width / 2) prevPage()
|
const menuY = ref(0)
|
||||||
else nextPage()
|
const menuFontColor = ref(settingStore.reader.fontColor)
|
||||||
|
const menuBackgroundColor = ref(settingStore.reader.backgroundColor)
|
||||||
|
|
||||||
|
const getColorDetails = (value: string | null) => {
|
||||||
|
const color = value || "#000000"
|
||||||
|
const match =
|
||||||
|
color.match(/^#([\da-f]{6})([\da-f]{2})$/i) ||
|
||||||
|
color.match(/^#([\da-f]{3})([\da-f])$/i)
|
||||||
|
const alphaMatch = color.match(/^rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*([\d.]+)\s*\)$/i)
|
||||||
|
const alpha = match
|
||||||
|
? Math.round((parseInt(match[2].length === 1 ? match[2] + match[2] : match[2], 16) / 255) * 100)
|
||||||
|
: alphaMatch
|
||||||
|
? Math.round(Number(alphaMatch[1]) * 100)
|
||||||
|
: 100
|
||||||
|
|
||||||
|
return {
|
||||||
|
color: match ? `#${match[1].toUpperCase()}` : color,
|
||||||
|
opacity: Math.min(100, Math.max(0, alpha)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onContextMenu = (e: MouseEvent) => {
|
||||||
|
menuX.value = e.clientX
|
||||||
|
menuY.value = e.clientY
|
||||||
|
menuShow.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(menuFontColor, (v) => {
|
||||||
|
if (v) settingStore.reader.fontColor = v
|
||||||
|
})
|
||||||
|
watch(menuBackgroundColor, (v) => {
|
||||||
|
if (v) settingStore.reader.backgroundColor = v
|
||||||
|
})
|
||||||
|
|
||||||
const onWindowMouseDown = (e: MouseEvent) => {
|
const onWindowMouseDown = (e: MouseEvent) => {
|
||||||
|
if (e.button !== 0) return
|
||||||
const target = e.target as HTMLElement | null
|
const target = e.target as HTMLElement | null
|
||||||
if (target?.closest("button, a, input, textarea, select, [role='button']")) return
|
if (target?.closest("button, a, input, textarea, select, [role='button']")) return
|
||||||
void appWindow.startDragging()
|
void appWindow.startDragging()
|
||||||
}
|
}
|
||||||
|
|
||||||
const onKeydown = (e: KeyboardEvent) => {
|
const onKeydown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
menuShow.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
if (e.key === "ArrowLeft") {
|
if (e.key === "ArrowLeft") {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
switchChapter(-1)
|
switchChapter(-1)
|
||||||
|
|
@ -266,7 +369,6 @@ onUnmounted(() => {
|
||||||
.page-body {
|
.page-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-text {
|
.page-text {
|
||||||
|
|
@ -327,4 +429,62 @@ onUnmounted(() => {
|
||||||
.nav-back {
|
.nav-back {
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ctx-menu {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
min-width: 176px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctx-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctx-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-picker-control {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-swatch {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border: 2px solid #ffffff;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
background-position: 0 0, 0 4px, 4px -4px, -4px 0;
|
||||||
|
background-size: 8px 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-swatch:focus-visible {
|
||||||
|
outline: 2px solid var(--color-accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-values {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1px;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-values small {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue