Add Strong display modes and book page boundaries
This commit is contained in:
@@ -1,5 +1,11 @@
|
|||||||
# Changelog
|
# 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
|
## 0.1.5 - 2026-07-12
|
||||||
|
|
||||||
- Fixed page selection so clicking a verse highlights it in place instead of rebuilding the page around it.
|
- Fixed page selection so clicking a verse highlights it in place instead of rebuilding the page around it.
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "libre-study",
|
"name": "libre-study",
|
||||||
"version": "0.1.5",
|
"version": "0.1.6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "libre-study",
|
"name": "libre-study",
|
||||||
"version": "0.1.5",
|
"version": "0.1.6",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^2.11.1",
|
"@tauri-apps/api": "^2.11.1",
|
||||||
"react": "^19.2.7",
|
"react": "^19.2.7",
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "libre-study",
|
"name": "libre-study",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.5",
|
"version": "0.1.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host 127.0.0.1",
|
"dev": "vite --host 127.0.0.1",
|
||||||
|
|||||||
Generated
+1
-1
@@ -77,7 +77,7 @@ checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "app"
|
name = "app"
|
||||||
version = "0.1.5"
|
version = "0.1.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "app"
|
name = "app"
|
||||||
version = "0.1.5"
|
version = "0.1.6"
|
||||||
description = "A Tauri App"
|
description = "A Tauri App"
|
||||||
authors = ["you"]
|
authors = ["you"]
|
||||||
license = ""
|
license = ""
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"productName": "Libre Study",
|
"productName": "Libre Study",
|
||||||
"version": "0.1.5",
|
"version": "0.1.6",
|
||||||
"identifier": "org.librestudy.desktop",
|
"identifier": "org.librestudy.desktop",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../dist",
|
"frontendDist": "../dist",
|
||||||
|
|||||||
+9
-12
@@ -125,7 +125,7 @@ textarea:focus-visible {
|
|||||||
|
|
||||||
.page-controls {
|
.page-controls {
|
||||||
display: grid;
|
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;
|
gap: 8px;
|
||||||
align-items: end;
|
align-items: end;
|
||||||
}
|
}
|
||||||
@@ -250,9 +250,13 @@ textarea:focus-visible {
|
|||||||
.strong-token {
|
.strong-token {
|
||||||
display: inline;
|
display: inline;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.strong-token.highlight,
|
||||||
|
.strong-token.numbers {
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
box-shadow: inset 0 -0.45em 0 rgba(214, 168, 79, 0.22);
|
box-shadow: inset 0 -0.45em 0 rgba(214, 168, 79, 0.22);
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.strong-token:hover,
|
.strong-token:hover,
|
||||||
@@ -266,23 +270,16 @@ textarea:focus-visible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.strong-code {
|
.strong-code {
|
||||||
display: inline-block;
|
margin-left: 2px;
|
||||||
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;
|
|
||||||
color: #735c0f;
|
color: #735c0f;
|
||||||
font-family: ui-sans-serif, system-ui, sans-serif;
|
font-family: ui-sans-serif, system-ui, sans-serif;
|
||||||
font-size: 9px;
|
font-size: 9px;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
line-height: 1.35;
|
line-height: 0;
|
||||||
|
vertical-align: super;
|
||||||
}
|
}
|
||||||
|
|
||||||
.strong-token.active .strong-code {
|
.strong-token.active .strong-code {
|
||||||
border-color: rgba(47, 111, 94, 0.42);
|
|
||||||
background: #e7f0eb;
|
|
||||||
color: #225446;
|
color: #225446;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+51
-9
@@ -7,6 +7,7 @@ import './App.css'
|
|||||||
const tagColors = ['#2f6f5e', '#735c0f', '#8a3ffc', '#b42318', '#245ba7']
|
const tagColors = ['#2f6f5e', '#735c0f', '#8a3ffc', '#b42318', '#245ba7']
|
||||||
const pageSize = 34
|
const pageSize = 34
|
||||||
type ViewMode = 'page' | 'study' | 'notes' | 'compare'
|
type ViewMode = 'page' | 'study' | 'notes' | 'compare'
|
||||||
|
type StrongDisplayMode = 'off' | 'highlight' | 'numbers'
|
||||||
|
|
||||||
function reference(verse: Verse) {
|
function reference(verse: Verse) {
|
||||||
return `${verse.book} ${verse.chapter}:${verse.verse}`
|
return `${verse.book} ${verse.chapter}:${verse.verse}`
|
||||||
@@ -20,6 +21,7 @@ function App() {
|
|||||||
const [selectedId, setSelectedId] = useState<number | null>(null)
|
const [selectedId, setSelectedId] = useState<number | null>(null)
|
||||||
const [pageStartId, setPageStartId] = useState<number | null>(null)
|
const [pageStartId, setPageStartId] = useState<number | null>(null)
|
||||||
const [selectedStrong, setSelectedStrong] = useState<string | null>(null)
|
const [selectedStrong, setSelectedStrong] = useState<string | null>(null)
|
||||||
|
const [strongDisplay, setStrongDisplay] = useState<StrongDisplayMode>('highlight')
|
||||||
const [tagInput, setTagInput] = useState('')
|
const [tagInput, setTagInput] = useState('')
|
||||||
const [noteDraft, setNoteDraft] = useState('')
|
const [noteDraft, setNoteDraft] = useState('')
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
@@ -85,9 +87,19 @@ function App() {
|
|||||||
const pageAnchors = useMemo(() => {
|
const pageAnchors = useMemo(() => {
|
||||||
const source = libraryVerses.length ? libraryVerses : verses
|
const source = libraryVerses.length ? libraryVerses : verses
|
||||||
const anchors: Verse[] = []
|
const anchors: Verse[] = []
|
||||||
|
let index = 0
|
||||||
|
|
||||||
for (let index = 0; index < source.length; index += pageSize) {
|
while (index < source.length) {
|
||||||
anchors.push(source[index])
|
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
|
return anchors
|
||||||
@@ -98,7 +110,10 @@ function App() {
|
|||||||
const pageStartIndex = source.findIndex((verse) => verse.id === pageStartId)
|
const pageStartIndex = source.findIndex((verse) => verse.id === pageStartId)
|
||||||
const selectedIndex = source.findIndex((verse) => verse.id === selectedVerse.id)
|
const selectedIndex = source.findIndex((verse) => verse.id === selectedVerse.id)
|
||||||
const start = Math.max(0, pageStartIndex >= 0 ? pageStartIndex : selectedIndex)
|
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])
|
}, [libraryVerses, pageStartId, selectedVerse, verses])
|
||||||
const pageRange = pageVerses.length
|
const pageRange = pageVerses.length
|
||||||
? `${reference(pageVerses[0])} - ${reference(pageVerses[pageVerses.length - 1])}`
|
? `${reference(pageVerses[0])} - ${reference(pageVerses[pageVerses.length - 1])}`
|
||||||
@@ -169,8 +184,17 @@ function App() {
|
|||||||
const source = libraryVerses.length ? libraryVerses : verses
|
const source = libraryVerses.length ? libraryVerses : verses
|
||||||
const index = source.findIndex((entry) => entry.id === verse.id)
|
const index = source.findIndex((entry) => entry.id === verse.id)
|
||||||
if (index < 0) return pageStartId ?? 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) {
|
function selectVerse(verse: Verse) {
|
||||||
@@ -184,7 +208,7 @@ function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderVerseText(verse: Verse) {
|
function renderVerseText(verse: Verse) {
|
||||||
if (!verse.strongs.length) return verse.text
|
if (!verse.strongs.length || strongDisplay === 'off') return verse.text
|
||||||
|
|
||||||
const parts: ReactNode[] = []
|
const parts: ReactNode[] = []
|
||||||
let cursor = 0
|
let cursor = 0
|
||||||
@@ -203,10 +227,15 @@ function App() {
|
|||||||
|
|
||||||
const linkedText = verse.text.slice(index, index + surface.length)
|
const linkedText = verse.text.slice(index, index + surface.length)
|
||||||
const isActive = entry.strong === selectedStrong
|
const isActive = entry.strong === selectedStrong
|
||||||
|
const showCode = strongDisplay === 'numbers' || isActive
|
||||||
parts.push(
|
parts.push(
|
||||||
<span
|
<span
|
||||||
key={`${verse.id}-${entry.strong}-${entry.position}`}
|
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"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
title={`${entry.surface}: ${entry.strong}`}
|
title={`${entry.surface}: ${entry.strong}`}
|
||||||
@@ -222,8 +251,8 @@ function App() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="strong-surface">{linkedText}</span>
|
<span className="strong-surface">{linkedText}</span>
|
||||||
{(verse.id === selectedId || isActive) ? (
|
{showCode ? (
|
||||||
<small className="strong-code">{entry.strong}</small>
|
<sup className="strong-code">{entry.strong}</sup>
|
||||||
) : null}
|
) : null}
|
||||||
</span>,
|
</span>,
|
||||||
)
|
)
|
||||||
@@ -388,6 +417,19 @@ function App() {
|
|||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</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>
|
</div>
|
||||||
<form
|
<form
|
||||||
className="search-box"
|
className="search-box"
|
||||||
|
|||||||
Reference in New Issue
Block a user