Add Strong display modes and book page boundaries

This commit is contained in:
2026-07-12 08:53:45 -05:00
parent c0774c8e52
commit 7674510952
8 changed files with 72 additions and 27 deletions
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## 0.1.6 - 2026-07-12
- Made session page anchors book-aware so pages do not silently cross from one book into another.
- Added a visible Strong's display selector with off, highlight, and full-number modes.
- Changed inline Strong's numbers from pill badges to superscript markers.
## 0.1.5 - 2026-07-12
- Fixed page selection so clicking a verse highlights it in place instead of rebuilding the page around it.
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "libre-study",
"version": "0.1.5",
"version": "0.1.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "libre-study",
"version": "0.1.5",
"version": "0.1.6",
"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.5",
"version": "0.1.6",
"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.5"
version = "0.1.6"
dependencies = [
"log",
"rusqlite",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "app"
version = "0.1.5"
version = "0.1.6"
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.5",
"version": "0.1.6",
"identifier": "org.librestudy.desktop",
"build": {
"frontendDist": "../dist",
+9 -12
View File
@@ -125,7 +125,7 @@ textarea:focus-visible {
.page-controls {
display: grid;
grid-template-columns: minmax(130px, 1.1fr) minmax(130px, 1.3fr) minmax(86px, 0.7fr) minmax(78px, 0.6fr);
grid-template-columns: minmax(130px, 1.1fr) minmax(130px, 1.3fr) minmax(86px, 0.7fr) minmax(78px, 0.6fr) minmax(120px, 0.9fr);
gap: 8px;
align-items: end;
}
@@ -250,9 +250,13 @@ textarea:focus-visible {
.strong-token {
display: inline;
position: relative;
cursor: pointer;
}
.strong-token.highlight,
.strong-token.numbers {
border-radius: 3px;
box-shadow: inset 0 -0.45em 0 rgba(214, 168, 79, 0.22);
cursor: pointer;
}
.strong-token:hover,
@@ -266,23 +270,16 @@ textarea:focus-visible {
}
.strong-code {
display: inline-block;
margin: 0 2px 0 3px;
transform: translateY(-0.12em);
border: 1px solid rgba(138, 109, 46, 0.34);
border-radius: 4px;
padding: 0 3px;
background: #fff8e6;
margin-left: 2px;
color: #735c0f;
font-family: ui-sans-serif, system-ui, sans-serif;
font-size: 9px;
font-weight: 800;
line-height: 1.35;
line-height: 0;
vertical-align: super;
}
.strong-token.active .strong-code {
border-color: rgba(47, 111, 94, 0.42);
background: #e7f0eb;
color: #225446;
}
+50 -8
View File
@@ -7,6 +7,7 @@ import './App.css'
const tagColors = ['#2f6f5e', '#735c0f', '#8a3ffc', '#b42318', '#245ba7']
const pageSize = 34
type ViewMode = 'page' | 'study' | 'notes' | 'compare'
type StrongDisplayMode = 'off' | 'highlight' | 'numbers'
function reference(verse: Verse) {
return `${verse.book} ${verse.chapter}:${verse.verse}`
@@ -20,6 +21,7 @@ function App() {
const [selectedId, setSelectedId] = useState<number | null>(null)
const [pageStartId, setPageStartId] = useState<number | null>(null)
const [selectedStrong, setSelectedStrong] = useState<string | null>(null)
const [strongDisplay, setStrongDisplay] = useState<StrongDisplayMode>('highlight')
const [tagInput, setTagInput] = useState('')
const [noteDraft, setNoteDraft] = useState('')
const [loading, setLoading] = useState(true)
@@ -85,10 +87,20 @@ function App() {
const pageAnchors = useMemo(() => {
const source = libraryVerses.length ? libraryVerses : verses
const anchors: Verse[] = []
let index = 0
for (let index = 0; index < source.length; index += pageSize) {
while (index < source.length) {
const book = source[index].book
let bookVerseIndex = 0
while (index < source.length && source[index].book === book) {
if (bookVerseIndex % pageSize === 0) {
anchors.push(source[index])
}
bookVerseIndex += 1
index += 1
}
}
return anchors
}, [libraryVerses, verses])
@@ -98,7 +110,10 @@ function App() {
const pageStartIndex = source.findIndex((verse) => verse.id === pageStartId)
const selectedIndex = source.findIndex((verse) => verse.id === selectedVerse.id)
const start = Math.max(0, pageStartIndex >= 0 ? pageStartIndex : selectedIndex)
return source.slice(start, start + pageSize)
const book = source[start]?.book
return source
.slice(start, start + pageSize)
.filter((verse) => verse.book === book)
}, [libraryVerses, pageStartId, selectedVerse, verses])
const pageRange = pageVerses.length
? `${reference(pageVerses[0])} - ${reference(pageVerses[pageVerses.length - 1])}`
@@ -169,8 +184,17 @@ function App() {
const source = libraryVerses.length ? libraryVerses : verses
const index = source.findIndex((entry) => entry.id === verse.id)
if (index < 0) return pageStartId ?? verse.id
const pageIndex = Math.floor(index / pageSize) * pageSize
return source[pageIndex]?.id ?? verse.id
let anchor = source[index]
for (const pageAnchor of pageAnchors) {
const anchorIndex = source.findIndex((entry) => entry.id === pageAnchor.id)
if (anchorIndex > index) break
if (pageAnchor.book === verse.book) {
anchor = pageAnchor
}
}
return anchor.id
}
function selectVerse(verse: Verse) {
@@ -184,7 +208,7 @@ function App() {
}
function renderVerseText(verse: Verse) {
if (!verse.strongs.length) return verse.text
if (!verse.strongs.length || strongDisplay === 'off') return verse.text
const parts: ReactNode[] = []
let cursor = 0
@@ -203,10 +227,15 @@ function App() {
const linkedText = verse.text.slice(index, index + surface.length)
const isActive = entry.strong === selectedStrong
const showCode = strongDisplay === 'numbers' || isActive
parts.push(
<span
key={`${verse.id}-${entry.strong}-${entry.position}`}
className={isActive ? 'strong-token active' : 'strong-token'}
className={[
'strong-token',
strongDisplay,
isActive ? 'active' : '',
].filter(Boolean).join(' ')}
role="button"
tabIndex={0}
title={`${entry.surface}: ${entry.strong}`}
@@ -222,8 +251,8 @@ function App() {
}}
>
<span className="strong-surface">{linkedText}</span>
{(verse.id === selectedId || isActive) ? (
<small className="strong-code">{entry.strong}</small>
{showCode ? (
<sup className="strong-code">{entry.strong}</sup>
) : null}
</span>,
)
@@ -388,6 +417,19 @@ function App() {
))}
</select>
</label>
<label>
Strong's
<select
value={strongDisplay}
onChange={(event) =>
setStrongDisplay(event.target.value as StrongDisplayMode)
}
>
<option value="off">Off</option>
<option value="highlight">Highlight</option>
<option value="numbers">Numbers</option>
</select>
</label>
</div>
<form
className="search-box"