Compact reader controls and margin layout

This commit is contained in:
2026-07-12 10:44:01 -05:00
parent 162fb06870
commit 123cb8dd98
7 changed files with 425 additions and 100 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "libre-study",
"version": "0.1.16",
"version": "0.1.17",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "libre-study",
"version": "0.1.16",
"version": "0.1.17",
"dependencies": {
"@tauri-apps/api": "^2.11.1",
"react": "^19.2.7",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "libre-study",
"private": true,
"version": "0.1.16",
"version": "0.1.17",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
+1 -1
View File
@@ -77,7 +77,7 @@ checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
[[package]]
name = "app"
version = "0.1.16"
version = "0.1.17"
dependencies = [
"log",
"rusqlite",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "app"
version = "0.1.16"
version = "0.1.17"
description = "A Tauri App"
authors = ["you"]
license = ""
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "Libre Study",
"version": "0.1.16",
"version": "0.1.17",
"identifier": "org.librestudy.desktop",
"build": {
"frontendDist": "../dist",
+155 -59
View File
@@ -11,6 +11,10 @@
filter: blur(1.5px) brightness(0.62) saturate(0.72);
}
.app-shell.notes-pinned .reader {
padding-right: min(500px, 30vw);
}
.tool-rail {
display: flex;
flex-direction: column;
@@ -155,14 +159,13 @@
gap: 10px;
min-width: 0;
height: 100vh;
padding: 18px 42px 18px 18px;
padding: 10px 48px 12px 12px;
box-sizing: border-box;
transition: filter 160ms ease;
}
.top-bar {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: 6px;
align-items: stretch;
min-width: 0;
@@ -170,15 +173,18 @@
.bible-top-tools {
display: grid;
gap: 8px;
grid-template-columns: minmax(0, 1.42fr) minmax(160px, 1fr);
gap: 6px;
align-items: end;
min-width: 0;
}
.study-top-tools {
display: flex;
align-items: end;
align-items: center;
justify-content: flex-start;
min-width: 0;
padding: 0 12px 3px;
padding: 4px 8px;
border: 1px solid #d4cdbd;
border-radius: 8px;
background: #fbf8ef;
@@ -188,6 +194,18 @@
text-transform: uppercase;
}
.study-top-tools button {
min-height: 28px;
padding: 5px 9px;
background: #2f6f5e;
color: #fffef8;
font-size: 12px;
}
.split-spacer {
min-width: 0;
}
.study-margin h2,
.notes-workspace h2,
.strong-focus h4 {
@@ -206,7 +224,7 @@
.search-box {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 8px;
gap: 5px;
}
input,
@@ -216,11 +234,11 @@ textarea {
min-width: 0;
border: 1px solid #bdb8aa;
border-radius: 6px;
padding: 10px 12px;
padding: 5px 8px;
background: #fffef9;
color: #202124;
font: inherit;
font-size: 14px;
font-size: 12px;
box-sizing: border-box;
}
@@ -233,11 +251,11 @@ textarea {
button {
border: 1px solid transparent;
border-radius: 6px;
padding: 10px 12px;
padding: 6px 9px;
background: #d6a84f;
color: #141510;
font: inherit;
font-size: 14px;
font-size: 12px;
font-weight: 700;
cursor: pointer;
}
@@ -252,17 +270,17 @@ textarea:focus-visible {
.page-controls {
display: grid;
grid-template-columns: minmax(92px, 1fr) minmax(118px, 1.24fr) minmax(72px, 0.74fr) minmax(68px, 0.66fr) minmax(96px, 0.9fr);
gap: 8px;
grid-template-columns: minmax(62px, 0.8fr) minmax(92px, 1.25fr) minmax(54px, 0.55fr) minmax(50px, 0.5fr) minmax(80px, 0.82fr);
gap: 5px;
align-items: end;
max-width: none;
}
.page-controls label {
display: grid;
gap: 4px;
gap: 2px;
color: #657061;
font-size: 11px;
font-size: 9px;
font-weight: 800;
letter-spacing: 0;
text-transform: uppercase;
@@ -270,8 +288,7 @@ textarea:focus-visible {
.study-desk {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: 6px;
gap: 0;
min-height: 0;
}
@@ -282,6 +299,19 @@ textarea:focus-visible {
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}
.split-resizer {
width: 6px;
min-width: 6px;
cursor: col-resize;
background: linear-gradient(90deg, transparent, #cfc6b4 45%, #cfc6b4 55%, transparent);
}
.split-resizer:hover,
.split-resizer:focus-visible {
background: #d6a84f;
outline: none;
}
.page-system,
.notes-workspace,
.status-panel {
@@ -310,7 +340,7 @@ textarea:focus-visible {
grid-column: 1;
grid-template-rows: auto minmax(0, 1fr);
min-height: 0;
padding: 26px 30px;
padding: 22px 26px;
}
.bible-page header,
@@ -327,7 +357,9 @@ textarea:focus-visible {
.page-turner {
grid-column: 1;
padding: 14px 30px;
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
padding: 8px 26px 6px;
border-top: 1px solid #e8e2d4;
background: #fffef8;
text-transform: none;
@@ -335,10 +367,50 @@ textarea:focus-visible {
.page-turner span {
color: #6b675d;
font-size: 13px;
font-size: 12px;
font-weight: 700;
}
.page-turner button {
align-self: start;
min-height: 26px;
padding: 4px 9px;
}
.page-position {
display: grid;
gap: 4px;
justify-items: center;
min-width: 0;
}
.chapter-tabs {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 3px;
max-height: 38px;
overflow: hidden;
}
.chapter-tabs button {
min-width: 22px;
min-height: 20px;
padding: 2px 5px;
border-color: #cfc4ad;
background: #f2ead8;
color: #4c422f;
font-size: 10px;
line-height: 1;
}
.chapter-tabs button.active,
.chapter-tabs button:hover,
.chapter-tabs button:focus-visible {
background: #d6a84f;
color: #151510;
}
.page-columns {
column-count: 2;
column-gap: 34px;
@@ -419,7 +491,7 @@ textarea:focus-visible {
.notes-workspace {
min-height: 0;
overflow: auto;
padding: 16px;
padding: 10px;
}
.study-margin {
@@ -435,13 +507,13 @@ textarea:focus-visible {
justify-content: space-between;
gap: 12px;
align-items: center;
padding-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #e4e0d6;
}
.panel-header h2,
.notes-workspace h2 {
font-size: 18px;
font-size: 16px;
}
.panel-header span,
@@ -453,16 +525,23 @@ textarea:focus-visible {
}
.margin-reference {
margin: 12px 0;
margin: 8px 0;
font-weight: 800;
}
.margin-resource-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
align-items: start;
}
.original-language-verse {
display: grid;
gap: 8px;
gap: 5px;
min-width: 0;
margin: 0 0 14px;
padding: 12px;
margin: 0;
padding: 8px;
overflow: hidden;
border: 1px solid #dfd6c2;
border-radius: 6px;
@@ -471,11 +550,11 @@ textarea:focus-visible {
.margin-section-header {
display: flex;
gap: 8px;
gap: 6px;
align-items: center;
justify-content: space-between;
margin: -6px -6px 2px;
padding: 7px 8px;
margin: -5px -5px 1px;
padding: 4px 6px;
border-radius: 5px;
background: #f5efe1;
cursor: pointer;
@@ -492,7 +571,7 @@ textarea:focus-visible {
border: 0;
background: transparent;
color: #657061;
font-size: 11px;
font-size: 10px;
font-weight: 800;
letter-spacing: 0;
text-transform: uppercase;
@@ -505,16 +584,10 @@ textarea:focus-visible {
cursor: pointer;
}
.collapse-button::before {
margin-right: 5px;
color: #735c0f;
content: 'v';
}
.mini-settings {
width: auto;
height: auto;
padding: 3px 6px;
padding: 2px 5px;
border: 1px solid #d8cfbf;
border-radius: 4px;
background: #fbf8ef;
@@ -541,7 +614,7 @@ textarea:focus-visible {
color: #1f211e;
font-family: Georgia, 'Times New Roman', serif;
font-size: 20px;
line-height: 1.7;
line-height: 1.45;
white-space: normal;
word-break: normal;
}
@@ -573,9 +646,9 @@ textarea:focus-visible {
.english-strong-line {
display: grid;
gap: 8px;
margin: 0 0 14px;
padding: 12px;
gap: 5px;
margin: 0;
padding: 8px;
border: 1px solid #dfd6c2;
border-radius: 6px;
background: #fffdf6;
@@ -584,7 +657,7 @@ textarea:focus-visible {
.linked-line-text {
margin: 0;
color: #202124;
line-height: 1.65;
line-height: 1.45;
overflow-wrap: anywhere;
}
@@ -609,8 +682,8 @@ textarea:focus-visible {
.strong-focus {
display: grid;
gap: 9px;
padding: 16px;
gap: 6px;
padding: 8px;
border: 1px solid #b9cabf;
border-radius: 8px;
background: #eef6f1;
@@ -632,14 +705,14 @@ textarea:focus-visible {
.strong-focus h4 {
color: #1f211e;
font-size: 22px;
font-size: 18px;
}
.strong-focus p {
margin: 0;
color: #3f443d;
font-size: 14px;
line-height: 1.5;
font-size: 12px;
line-height: 1.38;
}
.strong-focus button {
@@ -651,31 +724,36 @@ textarea:focus-visible {
.resource-card {
display: grid;
gap: 9px;
margin-top: 14px;
padding: 12px;
gap: 6px;
margin-top: 0;
padding: 8px;
border: 1px solid #dfd6c2;
border-radius: 6px;
background: #fffdf6;
}
.commentary-card,
.dictionary-card {
grid-column: 1 / -1;
}
.commentary-list,
.dictionary-list {
display: grid;
gap: 12px;
gap: 9px;
}
.commentary-list section,
.dictionary-list section {
display: grid;
gap: 5px;
gap: 4px;
}
.commentary-list h3,
.dictionary-list h3 {
margin: 0;
color: #252821;
font-size: 14px;
font-size: 13px;
letter-spacing: 0;
}
@@ -683,8 +761,8 @@ textarea:focus-visible {
.dictionary-list p {
margin: 0;
color: #3f443d;
font-size: 13px;
line-height: 1.48;
font-size: 12px;
line-height: 1.38;
}
.commentary-list small {
@@ -713,15 +791,15 @@ textarea:focus-visible {
right: 0;
z-index: 28;
display: grid;
grid-template-columns: 30px minmax(360px, 30vw);
width: min(540px, calc(100vw - 92px));
grid-template-columns: 42px minmax(380px, 30vw);
width: min(560px, calc(100vw - 92px));
height: 100vh;
transform: translateX(calc(100% - 30px));
transform: translateX(calc(100% - 42px));
transition: transform 190ms ease, box-shadow 190ms ease;
}
.notes-drawer.open,
.notes-drawer:hover,
.notes-drawer.pinned,
.notes-drawer:focus-within {
transform: translateX(0);
box-shadow: -18px 0 42px rgba(17, 20, 17, 0.24);
@@ -740,6 +818,24 @@ textarea:focus-visible {
font-weight: 900;
letter-spacing: 0;
cursor: pointer;
width: 42px;
height: 100%;
padding: 0;
border-top: 0;
border-bottom: 0;
border-radius: 0;
}
.notes-workspace > header {
display: flex;
align-items: start;
justify-content: space-between;
gap: 10px;
}
.pin-notes {
background: #2f6f5e;
color: #fffef8;
}
.notes-drawer .notes-workspace {
+259 -30
View File
@@ -6,7 +6,7 @@ import {
getLibraryState,
searchVerses,
} from './studyClient'
import type { DragEvent, ReactNode } from 'react'
import type { DragEvent, MouseEvent as ReactMouseEvent, ReactNode } from 'react'
import type { CommentaryEntry, DictionaryEntry, Verse } from './types'
import './App.css'
@@ -219,6 +219,7 @@ function App() {
const [timelineModalOpen, setTimelineModalOpen] = useState(false)
const [settingsModalOpen, setSettingsModalOpen] = useState(false)
const [notesDrawerOpen, setNotesDrawerOpen] = useState(false)
const [notesPinned, setNotesPinned] = useState(false)
const [pageMotion, setPageMotion] = useState<PageMotion>('idle')
const [viewMode, setViewMode] = useState<ViewMode>('notes')
const [query, setQuery] = useState('')
@@ -245,6 +246,12 @@ function App() {
const [definitionCollapsed, setDefinitionCollapsed] = useState(false)
const [commentaryCollapsed, setCommentaryCollapsed] = useState(false)
const [dictionaryCollapsed, setDictionaryCollapsed] = useState(false)
const [showOriginalLine, setShowOriginalLine] = useState(true)
const [showLinkedLine, setShowLinkedLine] = useState(true)
const [showDefinitionPanel, setShowDefinitionPanel] = useState(true)
const [showCommentaryPanel, setShowCommentaryPanel] = useState(true)
const [showDictionaryPanel, setShowDictionaryPanel] = useState(true)
const [splitPercent, setSplitPercent] = useState(50)
const [commentaryEntries, setCommentaryEntries] = useState<CommentaryEntry[]>([])
const [dictionaryEntries, setDictionaryEntries] = useState<DictionaryEntry[]>([])
const [marginResourcesLoading, setMarginResourcesLoading] = useState(false)
@@ -252,6 +259,8 @@ function App() {
const [error, setError] = useState<string | null>(null)
const restoredSessionId = useRef('')
const pageMotionTimeout = useRef<number | null>(null)
const notesDragOpenTimeout = useRef<number | null>(null)
const readerRef = useRef<HTMLElement | null>(null)
async function loadLibrary() {
setError(null)
@@ -389,12 +398,30 @@ function App() {
originalCollapsed: boolean
linkedCollapsed: boolean
definitionCollapsed: boolean
commentaryCollapsed: boolean
dictionaryCollapsed: boolean
showOriginalLine: boolean
showLinkedLine: boolean
showDefinitionPanel: boolean
showCommentaryPanel: boolean
showDictionaryPanel: boolean
splitPercent: number
notesPinned: boolean
}>
setLemmaFontSize(parsed.lemmaFontSize ?? 17)
setLinkedFontSize(parsed.linkedFontSize ?? 15)
setOriginalCollapsed(parsed.originalCollapsed ?? false)
setLinkedCollapsed(parsed.linkedCollapsed ?? false)
setDefinitionCollapsed(parsed.definitionCollapsed ?? false)
setCommentaryCollapsed(parsed.commentaryCollapsed ?? false)
setDictionaryCollapsed(parsed.dictionaryCollapsed ?? false)
setShowOriginalLine(parsed.showOriginalLine ?? true)
setShowLinkedLine(parsed.showLinkedLine ?? true)
setShowDefinitionPanel(parsed.showDefinitionPanel ?? true)
setShowCommentaryPanel(parsed.showCommentaryPanel ?? true)
setShowDictionaryPanel(parsed.showDictionaryPanel ?? true)
setSplitPercent(parsed.splitPercent ?? 50)
setNotesPinned(parsed.notesPinned ?? false)
} catch {
localStorage.removeItem(studySettingsStorageKey)
}
@@ -457,9 +484,33 @@ function App() {
originalCollapsed,
linkedCollapsed,
definitionCollapsed,
commentaryCollapsed,
dictionaryCollapsed,
showOriginalLine,
showLinkedLine,
showDefinitionPanel,
showCommentaryPanel,
showDictionaryPanel,
splitPercent,
notesPinned,
}),
)
}, [definitionCollapsed, lemmaFontSize, linkedCollapsed, linkedFontSize, originalCollapsed])
}, [
commentaryCollapsed,
definitionCollapsed,
dictionaryCollapsed,
lemmaFontSize,
linkedCollapsed,
linkedFontSize,
notesPinned,
originalCollapsed,
showCommentaryPanel,
showDefinitionPanel,
showDictionaryPanel,
showLinkedLine,
showOriginalLine,
splitPercent,
])
useEffect(() => {
if (restoredSessionId.current === activeSessionId) return
@@ -724,6 +775,34 @@ function App() {
)
}
function recordDisplaySetting(label: string) {
recordTimeline({
type: 'setting',
label,
})
}
function beginSplitResize(event: ReactMouseEvent<HTMLDivElement>) {
event.preventDefault()
const reader = readerRef.current
if (!reader) return
const resize = (moveEvent: MouseEvent) => {
const rect = reader.getBoundingClientRect()
const next = ((moveEvent.clientX - rect.left) / rect.width) * 100
setSplitPercent(Math.min(68, Math.max(36, next)))
}
const stop = () => {
window.removeEventListener('mousemove', resize)
window.removeEventListener('mouseup', stop)
recordDisplaySetting('Adjusted Bible and study margin split')
}
window.addEventListener('mousemove', resize)
window.addEventListener('mouseup', stop, { once: true })
}
function updateNoteDraft(value: string) {
setNoteDraft(value)
patchActiveSession({ noteDraft: value })
@@ -739,7 +818,20 @@ function App() {
function startTextDrag(event: DragEvent<HTMLElement>, value: string) {
event.dataTransfer.setData('text/plain', value)
event.dataTransfer.effectAllowed = 'copy'
}
function scheduleNotesDragOpen() {
if (notesPinned || notesDragOpenTimeout.current) return
notesDragOpenTimeout.current = window.setTimeout(() => {
setNotesDrawerOpen(true)
notesDragOpenTimeout.current = null
}, 550)
}
function clearNotesDragOpen() {
if (!notesDragOpenTimeout.current) return
window.clearTimeout(notesDragOpenTimeout.current)
notesDragOpenTimeout.current = null
}
function renameActiveSession(title: string) {
@@ -881,7 +973,7 @@ function App() {
}
function renderOriginalLanguageLine() {
if (!selectedVerse?.strongs.length) return null
if (!showOriginalLine || !selectedVerse?.strongs.length) return null
const isHebrew = selectedVerse.strongs.some((entry) => entry.strong.startsWith('H'))
const direction = isHebrew ? 'rtl' : 'ltr'
@@ -891,11 +983,15 @@ function App() {
className="margin-section-header"
role="button"
tabIndex={0}
onClick={() => setOriginalCollapsed((current) => !current)}
onClick={() => {
setOriginalCollapsed((current) => !current)
recordDisplaySetting('Toggled original language accordion')
}}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault()
setOriginalCollapsed((current) => !current)
recordDisplaySetting('Toggled original language accordion')
}
}}
>
@@ -943,7 +1039,7 @@ function App() {
}
function renderLinkedEnglishLine() {
if (!selectedVerse?.strongs.length) return null
if (!showLinkedLine || !selectedVerse?.strongs.length) return null
return (
<section className="english-strong-line" aria-label="English Strong's line">
@@ -951,11 +1047,15 @@ function App() {
className="margin-section-header"
role="button"
tabIndex={0}
onClick={() => setLinkedCollapsed((current) => !current)}
onClick={() => {
setLinkedCollapsed((current) => !current)
recordDisplaySetting('Toggled English linked text accordion')
}}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault()
setLinkedCollapsed((current) => !current)
recordDisplaySetting('Toggled English linked text accordion')
}
}}
>
@@ -1003,6 +1103,8 @@ function App() {
}
function renderCommentarySection() {
if (!showCommentaryPanel) return null
if (activeSession?.commentaryEnabled === false) {
return (
<article className="resource-card">
@@ -1021,11 +1123,15 @@ function App() {
className="margin-section-header"
role="button"
tabIndex={0}
onClick={() => setCommentaryCollapsed((current) => !current)}
onClick={() => {
setCommentaryCollapsed((current) => !current)
recordDisplaySetting('Toggled commentary accordion')
}}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault()
setCommentaryCollapsed((current) => !current)
recordDisplaySetting('Toggled commentary accordion')
}
}}
>
@@ -1060,6 +1166,8 @@ function App() {
}
function renderDictionarySection() {
if (!showDictionaryPanel) return null
if (activeSession?.dictionaryEnabled === false) {
return (
<article className="resource-card">
@@ -1078,11 +1186,15 @@ function App() {
className="margin-section-header"
role="button"
tabIndex={0}
onClick={() => setDictionaryCollapsed((current) => !current)}
onClick={() => {
setDictionaryCollapsed((current) => !current)
recordDisplaySetting('Toggled dictionary accordion')
}}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault()
setDictionaryCollapsed((current) => !current)
recordDisplaySetting('Toggled dictionary accordion')
}
}}
>
@@ -1158,7 +1270,21 @@ function App() {
<button type="button" onClick={() => shiftPage(-1)}>
Previous page
</button>
<span>King James Version - {pageRange}</span>
<div className="page-position">
<span>{pageRange}</span>
<div className="chapter-tabs" aria-label="Chapter selector">
{chapters.map((chapter) => (
<button
key={chapter}
type="button"
className={chapter === selectedVerse?.chapter ? 'active' : ''}
onClick={() => selectedVerse && jumpTo(selectedVerse.book, chapter, 1)}
>
{chapter}
</button>
))}
</div>
</div>
<button type="button" onClick={() => shiftPage(1)}>
Next page
</button>
@@ -1182,20 +1308,24 @@ function App() {
{activeSession?.strongsEnabled === false ? (
<p className="resource-disabled">Strong's links are disabled for this session.</p>
) : (
<>
<div className="margin-resource-grid">
{renderOriginalLanguageLine()}
{renderLinkedEnglishLine()}
{selectedStrongEntry ? (
{showDefinitionPanel && selectedStrongEntry ? (
<article className="strong-focus" aria-label="Strong's definition">
<header
className="margin-section-header"
role="button"
tabIndex={0}
onClick={() => setDefinitionCollapsed((current) => !current)}
onClick={() => {
setDefinitionCollapsed((current) => !current)
recordDisplaySetting('Toggled Strong definition accordion')
}}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault()
setDefinitionCollapsed((current) => !current)
recordDisplaySetting('Toggled Strong definition accordion')
}
}}
>
@@ -1223,10 +1353,12 @@ function App() {
) : null}
</article>
) : null}
</>
</div>
)}
<div className="margin-resource-grid">
{renderCommentarySection()}
{renderDictionarySection()}
</div>
</aside>
)
}
@@ -1617,8 +1749,15 @@ function App() {
<label className="checkbox-row">
<input
type="checkbox"
checked={!originalCollapsed}
onChange={(event) => setOriginalCollapsed(!event.target.checked)}
checked={showOriginalLine}
onChange={(event) => {
setShowOriginalLine(event.target.checked)
recordDisplaySetting(
event.target.checked
? 'Show original language line'
: 'Hide original language line',
)
}}
/>
Show original language line
</label>
@@ -1640,8 +1779,13 @@ function App() {
<label className="checkbox-row">
<input
type="checkbox"
checked={!linkedCollapsed}
onChange={(event) => setLinkedCollapsed(!event.target.checked)}
checked={showLinkedLine}
onChange={(event) => {
setShowLinkedLine(event.target.checked)
recordDisplaySetting(
event.target.checked ? 'Show English linked text' : 'Hide English linked text',
)
}}
/>
Show English linked text
</label>
@@ -1652,12 +1796,47 @@ function App() {
<label className="checkbox-row">
<input
type="checkbox"
checked={!definitionCollapsed}
onChange={(event) => setDefinitionCollapsed(!event.target.checked)}
checked={showDefinitionPanel}
onChange={(event) => {
setShowDefinitionPanel(event.target.checked)
recordDisplaySetting(
event.target.checked ? 'Show Strong definition' : 'Hide Strong definition',
)
}}
/>
Show expanded definition
</label>
</section>
<section className="modal-section">
<h3>Reference Panels</h3>
<label className="checkbox-row">
<input
type="checkbox"
checked={showCommentaryPanel}
onChange={(event) => {
setShowCommentaryPanel(event.target.checked)
recordDisplaySetting(
event.target.checked ? 'Show commentary panel' : 'Hide commentary panel',
)
}}
/>
Show commentary
</label>
<label className="checkbox-row">
<input
type="checkbox"
checked={showDictionaryPanel}
onChange={(event) => {
setShowDictionaryPanel(event.target.checked)
recordDisplaySetting(
event.target.checked ? 'Show dictionary panel' : 'Hide dictionary panel',
)
}}
/>
Show dictionary
</label>
</section>
</div>
</section>
</div>
@@ -1669,32 +1848,58 @@ function App() {
return (
<aside
className={notesDrawerOpen ? 'notes-drawer open' : 'notes-drawer'}
className={[
'notes-drawer',
notesDrawerOpen || notesPinned ? 'open' : '',
notesPinned ? 'pinned' : '',
].filter(Boolean).join(' ')}
aria-label="Notes drawer"
onMouseEnter={() => setNotesDrawerOpen(true)}
onMouseLeave={() => setNotesDrawerOpen(false)}
onDragEnter={(event) => {
event.preventDefault()
setNotesDrawerOpen(true)
scheduleNotesDragOpen()
}}
onDragLeave={clearNotesDragOpen}
onDragOver={(event) => event.preventDefault()}
onDrop={(event) => {
event.preventDefault()
clearNotesDragOpen()
appendNoteClip(event.dataTransfer.getData('text/plain'))
setNotesDrawerOpen(true)
}}
>
<div className="notes-drawer-grip" aria-hidden="true">
<button
type="button"
className="notes-drawer-grip"
aria-label={notesDrawerOpen || notesPinned ? 'Close notes drawer' : 'Open notes drawer'}
onClick={() => {
if (notesPinned) return
setNotesDrawerOpen((current) => !current)
}}
>
<span>N</span>
<span>O</span>
<span>T</span>
<span>E</span>
<span>S</span>
</div>
</button>
<div className="notes-workspace">
<header>
<div>
<span className="eyebrow">Notes</span>
<h2>Study Notes</h2>
</div>
<button
type="button"
className="pin-notes"
onClick={() => {
const nextPinned = !notesPinned
setNotesPinned(nextPinned)
setNotesDrawerOpen(nextPinned)
recordDisplaySetting(nextPinned ? 'Pinned notes drawer' : 'Unpinned notes drawer')
}}
>
{notesPinned ? 'Unpin' : 'Pin'}
</button>
</header>
<textarea
value={noteDraft}
@@ -1751,8 +1956,16 @@ function App() {
)
}
const splitTemplate = `${splitPercent}fr 6px ${100 - splitPercent}fr`
return (
<main className={railOpen ? 'app-shell menu-open' : 'app-shell'}>
<main
className={[
'app-shell',
railOpen ? 'menu-open' : '',
notesPinned ? 'notes-pinned' : '',
].filter(Boolean).join(' ')}
>
<aside
className="tool-rail"
aria-label="Main tools"
@@ -1792,14 +2005,17 @@ function App() {
</button>
</aside>
<section className="reader">
<section className="reader" ref={readerRef}>
{loading ? (
<div className="status-panel">Loading local library...</div>
) : error ? (
<div className="status-panel error">{error}</div>
) : selectedVerse ? (
<>
<header className="top-bar">
<header
className="top-bar"
style={{ gridTemplateColumns: splitTemplate }}
>
<div className="bible-top-tools">
<div className="page-controls" aria-label="Page selectors">
<label>
@@ -1885,12 +2101,18 @@ function App() {
<button type="submit">Go</button>
</form>
</div>
<div className="split-spacer" aria-hidden="true" />
<div className="study-top-tools" aria-label="Study margin tools">
<span>Study margin tools</span>
<button type="button" onClick={() => setSettingsModalOpen(true)}>
Margin settings
</button>
</div>
</header>
<div className={`study-desk ${viewMode}`}>
<div
className={`study-desk ${viewMode}`}
style={{ gridTemplateColumns: splitTemplate }}
>
{viewMode === 'compare' ? (
<div className="page-system compare-system" aria-label="Compare translations">
<div className="compare-pages">
@@ -1918,6 +2140,13 @@ function App() {
{renderPageTurner()}
{renderBookTabs()}
</div>
<div
className="split-resizer"
role="separator"
aria-label="Resize Bible and study margin"
aria-orientation="vertical"
onMouseDown={beginSplitResize}
/>
{renderStudyMargin()}
</>
)}