Skip to content

Instantly share code, notes, and snippets.

@avimar
Created June 15, 2025 09:52
Show Gist options
  • Select an option

  • Save avimar/868c259c0476f59a93a1cbaa2b62ff1c to your computer and use it in GitHub Desktop.

Select an option

Save avimar/868c259c0476f59a93a1cbaa2b62ff1c to your computer and use it in GitHub Desktop.

Revisions

  1. avimar created this gist Jun 15, 2025.
    399 changes: 399 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,399 @@
    <!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">&nbsp;</div>
    <div class="english-space">&nbsp;</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>