|
@@ -11,101 +11,42 @@
|
|
</div>
|
|
</div>
|
|
<div class="select-menu__driver" />
|
|
<div class="select-menu__driver" />
|
|
<div class="select-menu-toolbar">
|
|
<div class="select-menu-toolbar">
|
|
- <div @click="removeHighlight">
|
|
|
|
- <svg-icon
|
|
|
|
- name="icon_delete"
|
|
|
|
- width="24px"
|
|
|
|
- height="24px"
|
|
|
|
- color="var(--el-color-primary)"
|
|
|
|
- />删除标记
|
|
|
|
- </div>
|
|
|
|
- <div @click="copyText">
|
|
|
|
- <svg-icon
|
|
|
|
- name="icon_copy"
|
|
|
|
- width="24px"
|
|
|
|
- height="24px"
|
|
|
|
- color="var(--el-color-primary)"
|
|
|
|
- />复制
|
|
|
|
- </div>
|
|
|
|
- <div @click="handleSearch">
|
|
|
|
- <svg-icon
|
|
|
|
- name="icon_search"
|
|
|
|
- width="24px"
|
|
|
|
- height="24px"
|
|
|
|
- color="var(--el-color-primary)"
|
|
|
|
- />搜索
|
|
|
|
- </div>
|
|
|
|
|
|
+ <p @click="removeHighlight">删除</p>
|
|
|
|
+ <p @click="copyText">复制</p>
|
|
|
|
+ <p @click="handleSearch">搜索</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Teleport>
|
|
</Teleport>
|
|
</template>
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
-import { onMounted, ref } from "vue";
|
|
|
|
|
|
+import { ref } from "vue";
|
|
import useClipboard from "vue-clipboard3";
|
|
import useClipboard from "vue-clipboard3";
|
|
import { showNotify } from "vant";
|
|
import { showNotify } from "vant";
|
|
|
|
+import "vant/es/notify/style/index";
|
|
import { useEpubStore, useDetailStore } from "@/stores";
|
|
import { useEpubStore, useDetailStore } from "@/stores";
|
|
import { COLOR_MAP } from "../constants";
|
|
import { COLOR_MAP } from "../constants";
|
|
|
|
|
|
|
|
+const props = defineProps({
|
|
|
|
+ selectMenuStyle: {
|
|
|
|
+ type: Object,
|
|
|
|
+ default: undefined,
|
|
|
|
+ },
|
|
|
|
+ selectedCfi: {
|
|
|
|
+ type: String,
|
|
|
|
+ default: "",
|
|
|
|
+ },
|
|
|
|
+});
|
|
|
|
+const emits = defineEmits(["close"]);
|
|
|
|
+
|
|
const epubStore = useEpubStore();
|
|
const epubStore = useEpubStore();
|
|
const detailStore = useDetailStore();
|
|
const detailStore = useDetailStore();
|
|
const { toClipboard } = useClipboard();
|
|
const { toClipboard } = useClipboard();
|
|
-const selectMenuStyle = ref({});
|
|
|
|
-const selectedCfi = ref("");
|
|
|
|
const selectedCfiStack = ref([]);
|
|
const selectedCfiStack = ref([]);
|
|
|
|
|
|
-onMounted(() => {
|
|
|
|
- epubStore.rendition.hooks.render.register((v) => {
|
|
|
|
- v.document.body.addEventListener("touchend", (e) => {
|
|
|
|
- const selection = v.window.getSelection();
|
|
|
|
-
|
|
|
|
- console.log(selection);
|
|
|
|
- setTimeout(() => {
|
|
|
|
- if (!selection.isCollapsed) selected(e, v);
|
|
|
|
- else hideSelectMenu();
|
|
|
|
- }, 50);
|
|
|
|
- });
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- epubStore.rendition.on("selected", (v) => {
|
|
|
|
- selectedCfi.value = v;
|
|
|
|
- });
|
|
|
|
-});
|
|
|
|
-
|
|
|
|
-const hideSelectMenu = () => {
|
|
|
|
- selectMenuStyle.value = { visibility: "hidden" };
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-const selected = (e, v) => {
|
|
|
|
- const sty = {
|
|
|
|
- visibility: "visible",
|
|
|
|
- };
|
|
|
|
- const x = e.clientX;
|
|
|
|
- const y = e.clientY;
|
|
|
|
- const menuWidth = 506;
|
|
|
|
- const menuHeight = 58;
|
|
|
|
- const screenWidth = v.document.body.clientWidth;
|
|
|
|
- const screenHeight = v.document.body.clientHeight;
|
|
|
|
- const offset = Math.floor(x / screenWidth) + 1;
|
|
|
|
- const top = y + Math.round(menuHeight / 2) + 25;
|
|
|
|
-
|
|
|
|
- if (x + menuWidth > screenWidth * offset) {
|
|
|
|
- sty.left = screenWidth * offset - menuWidth - 11 + "px";
|
|
|
|
- } else {
|
|
|
|
- sty.left = x + "px";
|
|
|
|
- }
|
|
|
|
- if (top + menuHeight > screenHeight) {
|
|
|
|
- sty.top = screenHeight - menuHeight - 11 + "px";
|
|
|
|
- } else {
|
|
|
|
- sty.top = top + "px";
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- selectMenuStyle.value = sty;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
const setHighlight = (color) => {
|
|
const setHighlight = (color) => {
|
|
epubStore.rendition.annotations.highlight(
|
|
epubStore.rendition.annotations.highlight(
|
|
- selectedCfi.value,
|
|
|
|
|
|
+ props.selectedCfi,
|
|
{},
|
|
{},
|
|
() => {},
|
|
() => {},
|
|
"",
|
|
"",
|
|
@@ -114,32 +55,32 @@ const setHighlight = (color) => {
|
|
}
|
|
}
|
|
);
|
|
);
|
|
|
|
|
|
- hideSelectMenu();
|
|
|
|
- selectedCfiStack.value.push(selectedCfi.value);
|
|
|
|
|
|
+ emits("close");
|
|
|
|
+ selectedCfiStack.value.push(props.selectedCfi);
|
|
};
|
|
};
|
|
|
|
|
|
const removeHighlight = () => {
|
|
const removeHighlight = () => {
|
|
- epubStore.rendition.annotations.remove(selectedCfi.value, "highlight");
|
|
|
|
- hideSelectMenu();
|
|
|
|
|
|
+ epubStore.rendition.annotations.remove(props.selectedCfi, "highlight");
|
|
|
|
+ emits("close");
|
|
};
|
|
};
|
|
|
|
|
|
const copyText = async () => {
|
|
const copyText = async () => {
|
|
try {
|
|
try {
|
|
- const range = epubStore.rendition.getRange(selectedCfi.value);
|
|
|
|
|
|
+ const range = epubStore.rendition.getRange(props.selectedCfi);
|
|
const selectedText = range.toString();
|
|
const selectedText = range.toString();
|
|
await toClipboard(selectedText);
|
|
await toClipboard(selectedText);
|
|
showNotify({ type: "success", message: "复制成功" });
|
|
showNotify({ type: "success", message: "复制成功" });
|
|
} catch (err) {
|
|
} catch (err) {
|
|
showNotify({ type: "danger", message: "复制失败" });
|
|
showNotify({ type: "danger", message: "复制失败" });
|
|
}
|
|
}
|
|
- hideSelectMenu();
|
|
|
|
|
|
+ emits("close");
|
|
};
|
|
};
|
|
|
|
|
|
const handleSearch = () => {
|
|
const handleSearch = () => {
|
|
- const range = epubStore.rendition.getRange(selectedCfi.value);
|
|
|
|
|
|
+ const range = epubStore.rendition.getRange(props.selectedCfi);
|
|
const selectedText = range.toString();
|
|
const selectedText = range.toString();
|
|
detailStore.openSearchDrawer(selectedText);
|
|
detailStore.openSearchDrawer(selectedText);
|
|
- hideSelectMenu();
|
|
|
|
|
|
+ emits("close");
|
|
};
|
|
};
|
|
</script>
|
|
</script>
|
|
|
|
|
|
@@ -148,44 +89,41 @@ const handleSearch = () => {
|
|
position: absolute;
|
|
position: absolute;
|
|
display: flex;
|
|
display: flex;
|
|
align-items: center;
|
|
align-items: center;
|
|
- gap: 15px;
|
|
|
|
- padding: 15px;
|
|
|
|
|
|
+ gap: 20px;
|
|
|
|
+ padding: 20px;
|
|
background: var(--el-bg-color);
|
|
background: var(--el-bg-color);
|
|
- border-radius: 5px;
|
|
|
|
|
|
+ border-radius: 10px;
|
|
visibility: hidden;
|
|
visibility: hidden;
|
|
white-space: nowrap;
|
|
white-space: nowrap;
|
|
- box-shadow: 0px 0px 11px 0px rgba(0, 0, 0, 0.25);
|
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
+ background: var(--van-white);
|
|
|
|
+ box-shadow: 0px 0px 22px 0px rgba(0, 0, 0, 0.25);
|
|
|
|
+ z-index: 999;
|
|
|
|
|
|
&-colors {
|
|
&-colors {
|
|
display: flex;
|
|
display: flex;
|
|
align-items: center;
|
|
align-items: center;
|
|
- gap: 8px;
|
|
|
|
|
|
+ gap: 20px;
|
|
|
|
|
|
div {
|
|
div {
|
|
- width: 24px;
|
|
|
|
- height: 24px;
|
|
|
|
|
|
+ width: 50px;
|
|
|
|
+ height: 50px;
|
|
border-radius: 50%;
|
|
border-radius: 50%;
|
|
- cursor: pointer;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
&__driver {
|
|
&__driver {
|
|
- width: 1px;
|
|
|
|
- height: 28px;
|
|
|
|
- background: var(--text-color);
|
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
+ width: 2px;
|
|
|
|
+ height: 56px;
|
|
|
|
+ background: var(--van-border-color);
|
|
}
|
|
}
|
|
&-toolbar {
|
|
&-toolbar {
|
|
display: flex;
|
|
display: flex;
|
|
align-items: center;
|
|
align-items: center;
|
|
- gap: 15px;
|
|
|
|
- font-size: 18px;
|
|
|
|
- color: var(--el-color-primary);
|
|
|
|
-
|
|
|
|
- div {
|
|
|
|
- display: flex;
|
|
|
|
- align-items: center;
|
|
|
|
- gap: 5px;
|
|
|
|
- cursor: pointer;
|
|
|
|
- }
|
|
|
|
|
|
+ gap: 30px;
|
|
|
|
+ font-size: 27px;
|
|
|
|
+ line-height: 50px;
|
|
|
|
+ color: var(--van-primary-color);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|
|
</style>
|