forked from akai/readest
feat(annotator): support rendering vertical annotation bubbles (#2801)
This commit is contained in:
@@ -39,17 +39,21 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
const { appService } = useEnv();
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
const { isTrafficLightVisible } = useTrafficLight();
|
||||
const { trafficLightInFullscreen, setTrafficLightVisibility } = useTrafficLightStore();
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
const { bookKeys, hoveredBookKey, setHoveredBookKey } = useReaderStore();
|
||||
const { bookKeys, hoveredBookKey, getView, setHoveredBookKey } = useReaderStore();
|
||||
const { systemUIVisible, statusBarHeight } = useThemeStore();
|
||||
const { isSideBarVisible } = useSidebarStore();
|
||||
const iconSize16 = useResponsiveSize(16);
|
||||
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
const view = getView(bookKey);
|
||||
const iconSize16 = useResponsiveSize(16);
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
const windowButtonVisible = appService?.hasWindowBar && !isTrafficLightVisible;
|
||||
|
||||
const docs = view?.renderer.getContents() ?? [];
|
||||
const pointerInDoc = docs.some(({ doc }) => doc.body.style.cursor === 'pointer');
|
||||
|
||||
const handleToggleDropdown = (isOpen: boolean) => {
|
||||
setIsDropdownOpen(isOpen);
|
||||
if (!isOpen) setHoveredBookKey('');
|
||||
@@ -90,7 +94,7 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
|
||||
>
|
||||
<div
|
||||
role='none'
|
||||
className={clsx('absolute top-0 z-10 h-11 w-full')}
|
||||
className={clsx('absolute top-0 z-10 h-11 w-full', pointerInDoc && 'pointer-events-none')}
|
||||
onClick={() => setHoveredBookKey(bookKey)}
|
||||
onMouseEnter={() => !appService?.isMobile && setHoveredBookKey(bookKey)}
|
||||
onTouchStart={() => !appService?.isMobile && setHoveredBookKey(bookKey)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { BookNote } from '@/types/book';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useBookDataStore } from '@/store/bookDataStore';
|
||||
@@ -31,7 +31,11 @@ const AnnotationNotes: React.FC<AnnotationNotesProps> = ({
|
||||
const { setHoveredBookKey } = useReaderStore();
|
||||
const { setSideBarVisible } = useSidebarStore();
|
||||
const config = getConfig(bookKey);
|
||||
const maxSize = useResponsiveSize(300);
|
||||
const maxSize = useResponsiveSize(150);
|
||||
|
||||
const sortedNotes = useMemo(() => {
|
||||
return [...notes].sort((a, b) => b.updatedAt - a.updatedAt);
|
||||
}, [notes]);
|
||||
|
||||
const handleShowAnnotation = (note: BookNote) => {
|
||||
if (!note.id) return;
|
||||
@@ -51,10 +55,7 @@ const AnnotationNotes: React.FC<AnnotationNotesProps> = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'annotation-notes absolute flex overflow-y-auto rounded-lg text-white shadow-lg',
|
||||
'flex-col',
|
||||
)}
|
||||
className={clsx('annotation-notes absolute flex rounded-lg text-white')}
|
||||
style={{
|
||||
...(isVertical
|
||||
? {
|
||||
@@ -62,35 +63,61 @@ const AnnotationNotes: React.FC<AnnotationNotesProps> = ({
|
||||
left: `${triangleDir === 'right' ? `${popupWidth + 16}px` : undefined}`,
|
||||
height: `${popupHeight}px`,
|
||||
maxWidth: `${maxSize}px`,
|
||||
overflowX: 'auto',
|
||||
}
|
||||
: {
|
||||
top: triangleDir === 'up' ? undefined : `${popupHeight + 16}px`,
|
||||
bottom: triangleDir === 'up' ? `${popupHeight + 16}px` : undefined,
|
||||
width: `${popupWidth}px`,
|
||||
maxHeight: `${maxSize}px`,
|
||||
overflowY: 'auto',
|
||||
}),
|
||||
scrollbarWidth: 'thin',
|
||||
}}
|
||||
>
|
||||
<div className={clsx('flex gap-4', isVertical ? 'h-full flex-row' : 'flex-col')}>
|
||||
{notes.map((note, index) => (
|
||||
<div
|
||||
className={clsx('flex gap-4', isVertical ? 'h-full flex-row' : 'flex-col')}
|
||||
style={
|
||||
isVertical
|
||||
? {
|
||||
display: 'grid',
|
||||
gridAutoFlow: 'column',
|
||||
gridAutoColumns: 'max-content',
|
||||
minWidth: 'min-content',
|
||||
height: `${popupHeight}px`,
|
||||
maxHeight: `${popupHeight}px`,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
{sortedNotes.map((note, index) => (
|
||||
<div
|
||||
role='none'
|
||||
key={note.id || index}
|
||||
onClick={() => handleShowAnnotation?.(note)}
|
||||
className='cursor-pointer rounded-lg bg-gray-600 p-4 shadow-md transition-colors'
|
||||
className={clsx('cursor-pointer rounded-lg bg-gray-600 shadow-lg transition-colors')}
|
||||
style={
|
||||
isVertical
|
||||
? {
|
||||
minWidth: 'max-content',
|
||||
height: `${popupHeight}px`,
|
||||
maxHeight: `${popupHeight}px`,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
{note.note && (
|
||||
<div
|
||||
dir='auto'
|
||||
className={clsx(
|
||||
'hyphens-auto text-justify font-sans text-sm text-white',
|
||||
'm-4 hyphens-auto text-justify font-sans text-sm text-white',
|
||||
isVertical && 'writing-vertical-rl',
|
||||
)}
|
||||
style={
|
||||
isVertical
|
||||
? {
|
||||
fontFeatureSettings: "'vrt2' 1, 'vert' 1",
|
||||
minWidth: 'max-content',
|
||||
}
|
||||
: {}
|
||||
}
|
||||
|
||||
@@ -245,7 +245,13 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const customColors = settings.globalReadSettings.customHighlightColors;
|
||||
const hexColor =
|
||||
color && customColors ? customColors[color] : color ? HIGHLIGHT_COLOR_HEX[color] : color;
|
||||
if (style === 'highlight') {
|
||||
if (annotation.note) {
|
||||
const { defaultView } = doc;
|
||||
const node = range.startContainer;
|
||||
const el = node.nodeType === 1 ? node : node.parentElement;
|
||||
const { writingMode } = defaultView.getComputedStyle(el);
|
||||
draw(Overlayer.bubble, { writingMode });
|
||||
} else if (style === 'highlight') {
|
||||
draw(Overlayer.highlight, { color: hexColor });
|
||||
} else if (['underline', 'squiggly'].includes(style as string)) {
|
||||
const { defaultView } = doc;
|
||||
|
||||
@@ -41,6 +41,9 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
const actionTab = hoveredBookKey === bookKey ? userSelectedTab : '';
|
||||
const isVisible = hoveredBookKey === bookKey;
|
||||
|
||||
const docs = view?.renderer.getContents() ?? [];
|
||||
const pointerInDoc = docs.some(({ doc }) => doc.body.style.cursor === 'pointer');
|
||||
|
||||
const progressInfo = useMemo(
|
||||
() => (bookFormat === 'PDF' ? section : pageinfo),
|
||||
[bookFormat, section, pageinfo],
|
||||
@@ -224,7 +227,7 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
className={clsx(
|
||||
'absolute bottom-0 left-0 z-10 flex h-[52px] w-full',
|
||||
needHorizontalScroll && 'sm:!bottom-3 sm:!h-7',
|
||||
isMobile ? 'pointer-events-none' : '',
|
||||
isMobile || pointerInDoc ? 'pointer-events-none' : '',
|
||||
)}
|
||||
onMouseEnter={() => !isMobile && setHoveredBookKey(bookKey)}
|
||||
onTouchStart={() => !isMobile && setHoveredBookKey(bookKey)}
|
||||
|
||||
+1
-1
Submodule packages/foliate-js updated: 873b545933...bab0bccbe9
Reference in New Issue
Block a user