Created
June 15, 2025 09:52
-
-
Save avimar/868c259c0476f59a93a1cbaa2b62ff1c to your computer and use it in GitHub Desktop.
Interlinear
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="he"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Hebrew Interlinear Viewer</title> | |
<style> | |
.interlinear-container { | |
display: flex; | |
flex-wrap: wrap; | |
gap: 4px; | |
line-height: 1.1; | |
margin: 10px 0; | |
direction: rtl; | |
} | |
.word-group { | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
min-width: fit-content; | |
direction: ltr; | |
} | |
.hebrew { | |
font-family: 'SBL Hebrew', 'David', 'Times New Roman', serif; | |
font-size: 18px; | |
font-weight: bold; | |
text-align: center; | |
padding: 3px 6px; | |
border-bottom: 0.5px solid #ccc; | |
margin-bottom: 2px; | |
direction: rtl; | |
} | |
.header-container { | |
display: flex; | |
flex-direction: row; | |
justify-content: center; | |
align-items: center; | |
width: 100%; | |
margin: 20px 0; | |
text-align: center; | |
gap: 15px; | |
} | |
.header-container .hebrew { | |
font-size: 32px; | |
border-bottom: none; | |
margin-bottom: 0; | |
} | |
.header-container .english { | |
font-size: 24px; | |
color: #000; | |
} | |
.hebrew.big { | |
font-size: 24px; | |
font-weight: 900; | |
} | |
.hebrew.small { | |
font-size: 14px; | |
} | |
.word-group.big .english { | |
font-size: 12px; | |
} | |
.word-group.small .english { | |
font-size: 8px; | |
} | |
.english { | |
font-family: Arial, sans-serif; | |
font-size: 10px; | |
color: #222; | |
text-align: center; | |
padding: 2px 4px; | |
word-break: keep-all; | |
white-space: nowrap; | |
overflow-wrap: break-word; | |
direction: ltr; | |
} | |
.sentence-break-alt { | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
direction: ltr; | |
width: 5px; | |
} | |
.sentence-break-alt .hebrew-space { | |
font-size: 18px; | |
padding: 3px 6px; | |
margin-bottom: 2px; | |
border-bottom: 0.5px solid transparent; | |
position: relative; | |
} | |
.sentence-break-alt .hebrew-space::after { | |
content: "◆"; | |
color: #0; | |
font-size: 10px; | |
position: absolute; | |
bottom: -1px; | |
left: 50%; | |
transform: translateX(-50%); | |
padding-left: 5px | |
} | |
.sentence-break-alt .english-space { | |
font-size: 12px; | |
padding: 2px 4px; | |
color: transparent; | |
} | |
.english.wrap-ok { | |
white-space: normal; | |
max-width: 100px; | |
} | |
.verse-break { | |
width: 100%; | |
height: 5px; | |
} | |
.paragraph-break { | |
width: 100%; | |
height: 25px; | |
} | |
body { | |
max-width: 900px; | |
margin: 0 auto; | |
padding: 20px; | |
font-family: Arial, sans-serif; | |
} | |
h1 { | |
text-align: center; | |
color: #333; | |
border-bottom: 2px solid #ddd; | |
padding-bottom: 10px; | |
} | |
#input-form { | |
margin: 20px 0; | |
padding: 20px; | |
border: 1px solid #ddd; | |
border-radius: 5px; | |
} | |
#input-form input[type="text"] { | |
width: 100%; | |
padding: 8px; | |
margin: 8px 0; | |
} | |
#input-form button { | |
padding: 8px 16px; | |
background: #4CAF50; | |
color: white; | |
border: none; | |
border-radius: 4px; | |
cursor: pointer; | |
} | |
#input-form button:hover { | |
background: #45a049; | |
} | |
.error { | |
color: red; | |
margin: 10px 0; | |
} | |
#back-button { | |
position: fixed; | |
top: 10px; | |
left: 10px; | |
padding: 8px 16px; | |
background: #666; | |
color: white; | |
border: none; | |
border-radius: 4px; | |
cursor: pointer; | |
display: none; | |
} | |
#back-button:hover { | |
background: #555; | |
} | |
@media print { | |
#back-button { | |
display: none !important; | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Hebrew Interlinear Viewer</h1> | |
<div id="input-form"> | |
<input type="text" id="sheets-url" placeholder="Enter Google Sheets URL"> | |
<button onclick="loadSheet()">Load Sheet</button> | |
<button onclick="loadDemo()" style="background: #666;">Demo: Hakaras HaTov</button> | |
<div id="error-message" class="error"></div> | |
</div> | |
<button id="back-button" onclick="resetView()">Back</button> | |
<div id="content" class="interlinear-container"></div> | |
<script> | |
// Load demo content | |
function loadDemo() { | |
document.getElementById('sheets-url').value = 'https://docs.google.com/spreadsheets/d/1TcL04FE0TU3diSOkRY7DZ25ab7DtaLdNkOnWOrIXqss'; | |
loadSheet(); | |
} | |
// Parse Google Sheets URL to get the ID | |
function getSheetId(url) { | |
// Handle regular Google Sheets URLs | |
let match = url.match(/spreadsheets\/d\/([a-zA-Z0-9-_]+)/); | |
if (match) return match[1]; | |
throw new Error('Invalid Google Sheets URL. Please use a standard Google Sheets URL.'); | |
} | |
// Create the interlinear display | |
function createInterlinear(data) { | |
const content = document.getElementById('content'); | |
content.innerHTML = ''; | |
let inParagraph = false; | |
data.forEach(row => { | |
if (row.type === 'break') { | |
if (inParagraph) { | |
content.innerHTML += `<div class="paragraph-break"></div>`; | |
inParagraph = false; | |
} | |
} else { | |
if (!inParagraph) { | |
inParagraph = true; | |
} | |
if (row.type === 'header') { | |
const headerContainer = document.createElement('div'); | |
headerContainer.className = 'header-container'; | |
const hebrew = document.createElement('div'); | |
hebrew.className = 'hebrew'; | |
hebrew.textContent = row.hebrew; | |
const english = document.createElement('div'); | |
english.className = 'english'; | |
english.textContent = row.english; | |
headerContainer.appendChild(hebrew); | |
const dash = document.createElement('div'); | |
dash.className = 'english'; | |
dash.style.fontSize = '24px'; | |
dash.textContent = ' - '; | |
headerContainer.appendChild(dash); | |
headerContainer.appendChild(english); | |
content.appendChild(headerContainer); | |
} else { | |
const wordGroup = document.createElement('div'); | |
wordGroup.className = 'word-group'; | |
if (['big', 'small'].includes(row.type)) { | |
wordGroup.classList.add(row.type); | |
} | |
const hebrew = document.createElement('div'); | |
hebrew.className = 'hebrew'; | |
if (['big', 'small'].includes(row.type)) { | |
hebrew.classList.add(row.type); | |
} | |
hebrew.textContent = row.hebrew; | |
const english = document.createElement('div'); | |
english.className = 'english'; | |
english.textContent = row.english; | |
wordGroup.appendChild(hebrew); | |
wordGroup.appendChild(english); | |
content.appendChild(wordGroup); | |
} | |
if (row.hebrew.endsWith('.') && row.english.endsWith('.')) { | |
const breakDiv = document.createElement('div'); | |
breakDiv.className = 'sentence-break-alt'; | |
breakDiv.innerHTML = ` | |
<div class="hebrew-space"> </div> | |
<div class="english-space"> </div> | |
`; | |
content.appendChild(breakDiv); | |
} | |
} | |
}); | |
} | |
// Reset the view to initial state | |
function resetView() { | |
document.getElementById('input-form').style.display = 'block'; | |
document.getElementById('back-button').style.display = 'none'; | |
document.querySelector('h1').style.display = 'block'; | |
document.getElementById('content').innerHTML = ''; | |
// Remove query parameter without reloading | |
window.history.pushState({}, '', window.location.pathname); | |
} | |
// Load sheet data | |
async function loadSheet() { | |
const urlInput = document.getElementById('sheets-url'); | |
const errorMsg = document.getElementById('error-message'); | |
const url = urlInput.value.trim(); | |
try { | |
const sheetId = getSheetId(url); | |
if (!sheetId) { | |
throw new Error('Invalid Google Sheets URL'); | |
} | |
// Try fetching directly from Google Sheets as TSV | |
const sheetUrl = `https://docs.google.com/spreadsheets/d/${sheetId}/export?format=tsv`; | |
console.log('Attempting direct fetch'); | |
let response, tsvData; | |
response = await fetch(sheetUrl); | |
if (!response.ok) throw new Error('Direct fetch failed'); | |
tsvData = await response.text(); | |
console.log('Raw TSV:', tsvData); | |
// Parse TSV data | |
const rows = tsvData.split(/\r?\n/).map(line => { | |
// Split by tabs and trim each field | |
return line.split('\t').map(field => field.trim()); | |
}); | |
// Remove header row and empty rows, transform to our format | |
const jsonData = rows.slice(1) | |
.map(row => ({ | |
type: row[0] || '', | |
english: row[1] || '', | |
hebrew: row[2] || '' | |
})) | |
.map(row => { | |
// Check for empty rows that should trigger paragraph breaks | |
if (!row.type && !row.english && !row.hebrew) { | |
return { type: 'break', english: '', hebrew: '' }; | |
} | |
return row; | |
}); | |
createInterlinear(jsonData); | |
errorMsg.textContent = ''; | |
// Hide input form and show back button | |
document.getElementById('input-form').style.display = 'none'; | |
document.getElementById('back-button').style.display = 'block'; | |
document.querySelector('h1').style.display = 'none'; | |
// Update URL with just the sheet ID | |
const newUrl = new URL(window.location); | |
newUrl.searchParams.set('id', sheetId); | |
window.history.pushState({}, '', newUrl); | |
} catch (error) { | |
errorMsg.innerHTML = `Error: ${error.message}<br><br> | |
Tips:<br> | |
1. Make sure your sheet is shared (File > Share > Anyone with the link)<br> | |
2. Use the regular Google Sheets URL<br> | |
3. Check that the sheet contains data with headers: type, english, hebrew<br><br> | |
<small>Check browser console (F12) for additional debug information</small>`; | |
console.error('Error:', error); | |
} | |
} | |
// Check for URL parameter on load | |
window.addEventListener('load', () => { | |
const params = new URLSearchParams(window.location.search); | |
const idParam = params.get('id'); | |
if (idParam) { | |
const urlInput = document.getElementById('sheets-url'); | |
urlInput.value = `https://docs.google.com/spreadsheets/d/${idParam}`; | |
loadSheet(); | |
} | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment