{"id":2779,"date":"2025-01-28T06:05:43","date_gmt":"2025-01-28T06:05:43","guid":{"rendered":"https:\/\/textsnapper.com\/?page_id=2779"},"modified":"2025-06-06T05:04:59","modified_gmt":"2025-06-06T05:04:59","slug":"library","status":"publish","type":"page","link":"https:\/\/textsnapper.com\/en\/library\/","title":{"rendered":"Library"},"content":{"rendered":"<div data-elementor-type=\"wp-page\" data-elementor-id=\"2779\" class=\"elementor elementor-2779\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-d2d6aab e-flex e-con-boxed e-con e-parent\" data-id=\"d2d6aab\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-99d2011 elementor-widget elementor-widget-heading\" data-id=\"99d2011\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">My archive:<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-5da6f31 elementor-widget elementor-widget-html\" data-id=\"5da6f31\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<link rel=\"stylesheet\" href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/font-awesome\/6.5.1\/css\/all.min.css\"\/>\r\n<!-- TranslatePress Helper (unsichtbar) -->\r\n<div style=\"display:none;\">\r\n  <span id=\"tp-file-format-error\">File format {ext} not allowed<\/span>\r\n  <span id=\"tp-confirm-delete\">Really delete?<\/span>\r\n  <span id=\"tp-confirm-bookmark-delete\">Are you sure you want to delete this bookmark?<\/span>\r\n  <span id=\"tp-file-deleted\">File deleted<\/span>\r\n  <span id=\"tp-bookmark-deleted\">Bookmark deleted<\/span>\r\n  <span id=\"tp-upload-success\">Upload successful!<\/span>\r\n  <span id=\"tp-upload-error\">Upload error:<\/span>\r\n  <span id=\"tp-select-file\">Please select a file<\/span>\r\n  <span id=\"tp-loading-docs\">Loading documents...<\/span>\r\n  <span id=\"tp-loading-audio\">Loading audios...<\/span>\r\n  <span id=\"tp-loading-error\">Error loading<\/span>\r\n  <span id=\"tp-no-docs\">No documents found<\/span>\r\n  <span id=\"tp-no-audio\">No audio files found<\/span>\r\n  <span id=\"tp-no-bookmarks\">No bookmarks<\/span>\r\n  <span id=\"tp-error\">Error:<\/span>\r\n<\/div>\r\n\r\n<!-- ========== LIBRARY ========== -->\r\n<div id=\"ts-library-page\" style=\"font-family:Arial,sans-serif; background:#f9f9f9; border:1px solid #ccc; padding:20px; border-radius:6px; box-shadow:0 2px 8px rgba(0,0,0,0.1)\">\r\n  <style>\r\n#ts-library-page h2,\r\n#ts-library-page h3 {\r\n  color: #2F5591;\r\n  margin-top: 0;\r\n}\r\n\r\n#ts-upload-form {\r\n  display: flex;\r\n  align-items: center;\r\n  gap: 10px;\r\n  margin-bottom: 15px;\r\n}\r\n\r\n\/* Hide default file input *\/\r\n#ts-upload-form input[type=\"file\"] {\r\n  display: none;\r\n}\r\n\r\n\/* Button-Styling angepasst wie \"In mein Archiv speichern\" *\/\r\n.custom-file-upload {\r\n  padding: 7px 12px;\r\n  border: none;\r\n  border-radius: 4px;\r\n  cursor: pointer;\r\n  background: #2F5591;\r\n  color: #fff;\r\n  transition: background 0.3s ease;\r\n  display: inline-block;\r\n  text-align: center;\r\n  font-family: Arial, sans-serif;\r\n}\r\n\r\n.custom-file-upload:hover {\r\n  background: #1b3d73;\r\n}\r\n\r\n\/* Datei-Name-Styling anpassen *\/\r\n#file-name {\r\n  font-size: 0.95em;\r\n  color: #555;\r\n  font-style: italic;\r\n  margin-left: 10px;\r\n  vertical-align: middle;\r\n}\r\n\r\n\r\n\/* Upload submit button *\/\r\n#ts-upload-form button[type=\"submit\"] {\r\n  margin-left: auto;\r\n  padding: 8px 14px;\r\n  border: none;\r\n  border-radius: 4px;\r\n  cursor: pointer;\r\n  background: #2F5591;\r\n  color: #fff;\r\n  transition: opacity 0.3s ease;\r\n  \r\n}\r\n\r\n#ts-upload-form button[type=\"submit\"]:hover {\r\n  opacity: 0.8;\r\n}\r\n#ts-document-list .fa-solid {\r\n  font-size: 24px; \/* Gr\u00f6\u00dfe des Icons anpassen *\/\r\n  color: #2F5591;  \/* Farbe passend zur Webseite *\/\r\n  margin-right: 8px; \/* Abstand zwischen Icon und Dateiname *\/\r\n  vertical-align: middle;\r\n}\r\n\r\n\r\n\r\n#ts-document-list li,\r\n#ts-audio-list li,\r\n#ts-bookmark-list li {\r\n  list-style: none;\r\n  margin-bottom: 8px;\r\n  border: 1px solid #ccc;\r\n  border-radius: 4px;\r\n  padding: 10px;\r\n  background: #fff;\r\n  display: flex;\r\n  align-items: center;\r\n  justify-content: space-between;\r\n}\r\n\r\n.doc-info { flex: 1; }\r\n\r\n.doc-actions button,\r\n.bm-actions button {\r\n  display: inline-flex;\r\n  align-items: center;\r\n  justify-content: center;\r\n  width: 36px;\r\n  height: 36px;\r\n  padding: 0;\r\n  font-size: 18px;\r\n  border: none;\r\n  border-radius: 4px;\r\n  cursor: pointer;\r\n  color: #fff;\r\n  margin-right: 6px;\r\n  flex-shrink: 0; \/* Buttons schrumpfen nicht *\/\r\n}\r\n\r\n.doc-actions button:last-child,\r\n.bm-actions button:last-child {\r\n  margin-right: 0;\r\n}\r\n\r\n.doc-actions button:hover,\r\n.bm-actions button:hover {\r\n  opacity: 0.8;\r\n}\r\n\r\n\/* Breitere Buttons: Vorlesen, Abspielen, Weiterlesen *\/\r\n.btn-play,\r\n.btn-audio-play,\r\n.btn-bookmark-continue {\r\n  flex: 1; \/* Nutzt gesamte restliche Breite *\/\r\n  max-width: 100%; \/* Sicherstellen, dass der Button nicht \u00fcberl\u00e4uft *\/\r\n  width: auto; \/* Automatische Breite basierend auf flex *\/\r\n}\r\n\r\n\/* Farbdefinitionen - Desktop und Mobil einheitlich *\/\r\n.btn-download { background-color: #007BFF; } \/* Blau *\/\r\n.btn-delete { background-color: #DC3545; } \/* Rot *\/\r\n.btn-play { background-color: #FFC107; } \/* Gelb *\/\r\n.btn-audio-play { background-color: #17A2B8; } \/* T\u00fcrkis *\/\r\n.btn-bookmark-continue { background-color: #28A745; } \/* Gr\u00fcn *\/\r\n\r\n#ts-document-list li.doc-item {\r\n  display: flex;\r\n  align-items: center;\r\n  justify-content: space-between;\r\n  gap: 15px; \/* Abstand zwischen Thumbnail und Text *\/\r\n  padding: 10px;\r\n}\r\n\r\n#ts-document-list li.doc-item img {\r\n  width: 50px;\r\n  height: 60px;\r\n  object-fit: cover;\r\n  border-radius: 4px;\r\n  flex-shrink: 0;\r\n}\r\n\r\n#ts-document-list .doc-info {\r\n  flex: 1;\r\n  display: flex;\r\n  flex-direction: column;\r\n  justify-content: center;\r\n}\r\n\r\n#ts-document-list .doc-info small {\r\n  color: #666;\r\n  margin-top: 4px;\r\n}\r\n\r\n#ts-document-list .doc-actions {\r\n  display: flex;\r\n  flex-direction: row; \/* Buttons nebeneinander *\/\r\n  gap: 5px; \/* kleiner Abstand zwischen Buttons *\/\r\n}\r\n\r\n#ts-document-list .doc-actions button {\r\n  height: 50px; \/* Fast volle H\u00f6he der Spalte *\/\r\n  width: 50px; \/* Buttons quadratisch *\/\r\n  display: flex;\r\n  align-items: center;\r\n  justify-content: center;\r\n  font-size: 20px;\r\n}\r\n#ts-document-list .doc-actions .btn-play {\r\n  width: auto; \/* breiterer Button zum Vorlesen *\/\r\n  padding: 0 15px;\r\n}\r\n\r\n#ts-document-list .doc-actions button:last-child {\r\n  margin-bottom: 0;\r\n}\r\n.ts-upload-box {\r\n  background: #fff;\r\n  border: 2px solid #2F5591;\r\n  border-radius: 10px;\r\n  box-shadow: 0 2px 16px rgba(47, 85, 145, 0.08);\r\n  padding: 24px 18px 18px 18px;\r\n  margin-bottom: 30px;\r\n  width: 100%;\r\n  box-sizing: border-box;\r\n  transition: box-shadow 0.2s;\r\n}\r\n\r\n.ts-upload-box:hover {\r\n  box-shadow: 0 4px 24px rgba(47, 85, 145, 0.14);\r\n}\r\n\r\n.ts-upload-info {\r\n  margin-bottom: 15px;\r\n  color: #2F5591;\r\n  font-weight: bold;\r\n  font-size: 1em;\r\n}\r\n\r\n.ts-upload-info i {\r\n  margin-right: 7px;\r\n}\r\n\r\n\r\n\/* Responsive Anpassungen *\/\r\n@media (max-width: 835px) {\r\n  #ts-upload-form,\r\n  #ts-library-page form,\r\n  #ts-library-page li {\r\n    flex-direction: column;\r\n    align-items: stretch;\r\n  }\r\n\r\n  .doc-actions,\r\n  .bm-actions {\r\n    display: flex;\r\n    flex-direction: row !important;\r\n    justify-content: flex-start;\r\n    margin-top: 10px;\r\n  }\r\n\r\n  .doc-actions button,\r\n  .bm-actions button {\r\n    margin-right: 6px !important;\r\n    margin-bottom: 0 !important;\r\n  }\r\n  #ts-document-list li.doc-item {\r\n    display: grid;\r\n    grid-template-columns: auto 1fr; \/* Thumbnail und Text nebeneinander *\/\r\n    gap: 10px;\r\n    align-items: center;\r\n    padding: 10px;\r\n  }\r\n\r\n  #ts-document-list li.doc-item img {\r\n    grid-row: span 2; \/* Thumbnail nimmt 2 Reihen (Titel und Datum) ein *\/\r\n    width: 50px;\r\n    height: 60px;\r\n    object-fit: cover;\r\n    border-radius: 4px;\r\n  }\r\n\r\n  #ts-document-list .doc-info {\r\n    display: flex;\r\n    flex-direction: column;\r\n    justify-content: center;\r\n  }\r\n\r\n  #ts-document-list .doc-info strong {\r\n    font-size: 15px;\r\n  }\r\n\r\n  #ts-document-list .doc-info small {\r\n    color: #666;\r\n    margin-top: 2px;\r\n    font-size: 13px;\r\n  }\r\n\r\n  #ts-document-list .doc-actions {\r\n    grid-column: 1 \/ -1; \/* Buttons unterhalb in voller Breite *\/\r\n    display: flex;\r\n    width: 100%;\r\n    margin-top: 8px;\r\n  }\r\n\r\n  #ts-document-list .doc-actions button {\r\n    flex-grow: 0;\r\n    flex-shrink: 0;\r\n    flex-basis: 50px;\r\n    height: 40px;\r\n    font-size: 18px;\r\n  }\r\n\r\n  #ts-document-list .doc-actions .btn-play {\r\n    flex-grow: 1;\r\n    flex-basis: auto;\r\n  }\r\n  #ts-upload-form button[type=\"submit\"] {\r\n    margin-left: 0;\r\n    margin-right: 0;\r\n    align-self: center;\r\n    width: 100%; \/* Optional: Button volle Breite mobil *\/\r\n    max-width: 200px; \/* Optional: Maximalbreite f\u00fcr besseres Aussehen *\/\r\n    text-align: center;\r\n  }\r\n\r\n}\r\n\r\n  <\/style>\r\n\r\n\r\n<h3>My Documents<\/h3>\r\n  <!-- ======== DOKUMENTE-Liste ======== -->\r\n  <ul id=\"ts-document-list\">\r\n    <li>Loading documents...<\/li>\r\n  <\/ul>\r\n\r\n  <hr style=\"margin:20px 0;\">\r\n  <!-- ======== AUDIOS-Liste ======== -->\r\n  <h3>My audio files<\/h3>\r\n  <ul id=\"ts-audio-list\">\r\n    <li>Loading audios...<\/li>\r\n  <\/ul>\r\n\r\n  <hr style=\"margin:20px 0;\">\r\n  <h3>My Bookmarks<\/h3>\r\n  <ul id=\"ts-bookmark-list\">\r\n    <li>No bookmarks yet<\/li>\r\n  <\/ul>\r\n  \r\n<div class=\"ts-upload-box\" style=\"margin-top:80px; \">\r\n  <h3>Datei hochladen<\/h3>\r\n  <p class=\"ts-upload-info\">\r\n    <i class=\"fa-solid fa-circle-info\"><\/i>\r\n    Folgende Dateiformate k\u00f6nnen hochgeladen werden:\r\n    <b>PDF, DOCX, XLS\/XLSX, TXT, EPUB, MP3, WAV<\/b>.<br>\r\n    Hinweis: Bei E-Books bitte darauf achten, dass sie <b>ohne Kopierschutz (kein DRM)<\/b> sind.\r\n    B\u00fccher mit Wasserzeichen sind problemlos m\u00f6glich.\r\n  <\/p>\r\n  <form id=\"ts-upload-form\" enctype=\"multipart\/form-data\" action=\"\">\r\n    <label for=\"file-upload\" class=\"custom-file-upload\">\r\n      \ud83d\udcc1 Select file\r\n    <\/label>\r\n    <input id=\"file-upload\" type=\"file\" name=\"file\" required style=\"display:none;\">\r\n    <span id=\"file-name\">No file selected<\/span>\r\n    <button type=\"submit\">Upload<\/button>\r\n  <input type=\"hidden\" name=\"trp-form-language\" value=\"en\"\/><\/form>\r\n  <div id=\"ts-upload-status\" style=\"margin-bottom:10px; color:#333;\">\r\n  <span id=\"upload-progress\"><\/span>\r\n  <span id=\"upload-success-msg\" style=\"display:none; color:green;\"><\/span>\r\n<\/div>\r\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/jszip\/3.10.1\/jszip.min.js\"><\/script>\r\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/epubjs@0.3.88\/dist\/epub.min.js\"><\/script>\r\n\r\n  <script>\r\n  (function(){\r\n    \"use strict\";\r\n\r\n\/\/ Hilfsfunktion: Dateityp-Icon bestimmen\r\nfunction getFileIcon(fileType){\r\n  switch(fileType.toLowerCase()){\r\n    case 'pdf': return '<i class=\"fa-solid fa-file-pdf\"><\/i>';\r\n    case 'docx': return '<i class=\"fa-solid fa-file-word\"><\/i>';\r\n    case 'xlsx':\r\n    case 'xls': return '<i class=\"fa-solid fa-file-excel\"><\/i>';\r\n    case 'txt': return '<i class=\"fa-solid fa-file-lines\"><\/i>';\r\n    case 'epub': return '<i class=\"fa-solid fa-book-open\"><\/i>';\r\n    default: return '<i class=\"fa-solid fa-file\"><\/i>';\r\n  }\r\n}\r\n\r\nasync function extractEpubCover(file) {\r\n  return new Promise((resolve, reject) => {\r\n    const reader = new FileReader();\r\n    reader.onload = async function(e) {\r\n      const book = ePub(e.target.result);\r\n\r\n      try {\r\n        const coverUrl = await book.coverUrl();\r\n        if (!coverUrl) {\r\n          resolve(null);\r\n          return;\r\n        }\r\n\r\n        const img = new Image();\r\n        img.crossOrigin = 'anonymous';\r\n        img.onload = function() {\r\n          const canvas = document.createElement('canvas');\r\n          canvas.width = img.width;\r\n          canvas.height = img.height;\r\n\r\n          const ctx = canvas.getContext('2d');\r\n          ctx.drawImage(img, 0, 0);\r\n\r\n          canvas.toBlob((blob) => {\r\n            resolve(blob);\r\n          }, 'image\/jpeg', 0.9);\r\n        };\r\n        img.onerror = () => resolve(null);\r\n        img.src = coverUrl;\r\n\r\n      } catch (error) {\r\n        console.error('Cover extraction error:', error);\r\n        resolve(null);\r\n      }\r\n    };\r\n    reader.onerror = reject;\r\n    reader.readAsArrayBuffer(file);\r\n  });\r\n}\r\n\r\n\r\n\r\n    \/\/ Hilfsfunktion f\u00fcr TranslatePress\r\nfunction getTranslation(id, fallback) {\r\n  const el = document.getElementById(id);\r\n  return el ? el.textContent : fallback;\r\n}\r\n\r\n\r\n    const ajaxUrl = (window.ajaxurl || \"\/wp-admin\/admin-ajax.php\");\r\n    const uploadForm     = document.getElementById(\"ts-upload-form\");\r\n    const uploadStatusEl = document.getElementById(\"ts-upload-status\");\r\n\r\n    const documentList   = document.getElementById(\"ts-document-list\");\r\n    const audioList      = document.getElementById(\"ts-audio-list\");\r\n    const bookmarkList   = document.getElementById(\"ts-bookmark-list\");\r\n\r\n    \/\/ Hilfsfunktion: Problematische Zeichen f\u00fcr JS-String escapen\r\n    function escapeJsString(str){\r\n      return String(str)\r\n        .replace(\/\\\\\/g, \"\\\\\\\\\")\r\n        .replace(\/'\/g, \"\\\\'\")\r\n        .replace(\/\"\/g, \"\\\\\\\"\")\r\n        .replace(\/\\n\/g, \"\\\\n\")\r\n        .replace(\/\\r\/g, \"\\\\r\");\r\n    }\r\n\r\n    \/\/ Bookmark-Funktion: Weiterlesen => \/viewer\/?doc_id=XX&pos=...\r\n    window.continueReading = function(docId, pos){\r\n  \/\/ 1) pos war noch url-encodet => decode:\r\n  let decoded = decodeURIComponent(pos);\r\n\r\n  \/\/ 2) Jetzt haben wir z.B. `{\"cfi\":\"epubcfi(\/6\/38[item17]!\/4\/2...)\"}` oder `{\"page\":2,\"offset\":1200}`\r\n  \/\/    => wir wollen das unver\u00e4ndert als Query param `?pos=...`\r\n  \/\/    => also wieder encodieren, aber nur f\u00fcr die URL:\r\n  let finalParam = encodeURIComponent(decoded);\r\n\r\n  window.location.href = \"\/viewer\/?doc_id=\"+ docId +\"&pos=\"+ finalParam;\r\n};\r\n\r\n\r\n    document.addEventListener(\"DOMContentLoaded\", ()=>{\r\n      loadDocuments();\r\n      loadBookmarks();\r\n\r\n      \/\/ Upload\r\nuploadForm.addEventListener(\"submit\", async (e) => {\r\n  e.preventDefault();\r\n\r\n  const fileInput = uploadForm.querySelector('input[type=\"file\"]');\r\n  const uploadProgress = document.getElementById('upload-progress');\r\n  const uploadSuccessMsg = document.getElementById('upload-success-msg');\r\n\r\n  uploadProgress.textContent = '';\r\n  uploadSuccessMsg.style.display = 'none';\r\n\r\n  if (!fileInput.files.length) {\r\n    uploadProgress.textContent = getTranslation(\"tp-select-file\", \"Bitte w\u00e4hle eine Datei aus\");\r\n    return;\r\n  }\r\n\r\n  const file = fileInput.files[0];\r\n  const fData = new FormData();\r\n  fData.append('action', 'ts_upload');\r\n  fData.append('file', file);\r\n\r\n  if (file.name.toLowerCase().endsWith('.epub')) {\r\n    const coverBlob = await extractEpubCover(file);\r\n    if (coverBlob) {\r\n      fData.append('cover', coverBlob, 'cover.jpg');\r\n    }\r\n  }\r\n\r\n  const xhr = new XMLHttpRequest();\r\n  xhr.open('POST', ajaxUrl, true);\r\n  xhr.withCredentials = true;\r\n\r\n  xhr.upload.addEventListener('progress', (e) => {\r\n    if (e.lengthComputable) {\r\n      const percentComplete = Math.round((e.loaded \/ e.total) * 100);\r\n      uploadProgress.textContent = `Upload: ${percentComplete}%`;\r\n    }\r\n  });\r\n\r\n  xhr.onload = function () {\r\n    if (xhr.status === 200) {\r\n      const res = JSON.parse(xhr.responseText);\r\n      if (res.success) {\r\n        uploadProgress.textContent = '';\r\n        uploadSuccessMsg.textContent = getTranslation(\"tp-upload-success\", \"Upload erfolgreich!\");\r\n        uploadSuccessMsg.style.display = 'inline';\r\n        loadDocuments();\r\n      } else {\r\n        uploadProgress.textContent = getTranslation(\"tp-upload-error\", \"Upload-Fehler:\") + res.data.message;\r\n      }\r\n    } else {\r\n      uploadProgress.textContent = getTranslation(\"tp-upload-error\", \"Upload-Fehler: HTTP Status \") + xhr.status;\r\n    }\r\n  };\r\n\r\n  xhr.onerror = function () {\r\n    uploadProgress.textContent = getTranslation(\"tp-upload-error\", \"Upload-Fehler: Netzwerkfehler\");\r\n  };\r\n\r\n  xhr.send(fData);\r\n});\r\n\r\n\r\n\r\n});\r\n\r\n\r\n\/\/ ============ Dokumente laden (mit Thumbnail) ============\r\nfunction loadDocuments() {\r\n  documentList.innerHTML = \"<li>Lade Dokumente...<\/li>\";\r\n  audioList.innerHTML = \"<li>Lade Audios...<\/li>\";\r\n\r\n  fetch(ajaxUrl + \"?action=ts_fetch_docs\", {credentials:'include'})\r\n  .then(r => r.json())\r\n  .then(res => {\r\n    if(!res.success){\r\n      documentList.innerHTML = \"<li>Fehler beim Laden<\/li>\";\r\n      audioList.innerHTML = \"<li>Fehler beim Laden<\/li>\";\r\n      return;\r\n    }\r\n\r\n    const docs = res.data.documents || [];\r\n    if(docs.length===0){\r\n      documentList.innerHTML = \"<li>Keine Dokumente gefunden<\/li>\";\r\n      audioList.innerHTML = \"<li>Keine Audiodateien gefunden<\/li>\";\r\n      return;\r\n    }\r\n\r\n    const docFiles = docs.filter(d => d.file_type !== 'mp3' && d.file_type !== 'wav');\r\n    const audioFiles = docs.filter(d => d.file_type === 'mp3' || d.file_type === 'wav');\r\n\r\n    \/\/ Dokumente (mit Thumbnail!)\r\n    if(docFiles.length === 0){\r\n      documentList.innerHTML = \"<li>Keine Dokumente<\/li>\";\r\n    } else {\r\n      let docHtml = \"\";\r\n      docFiles.forEach(doc => {\r\n  let thumbnail = doc.thumbnail_path \r\n  ? `<img decoding=\"async\" src=\"\/${doc.thumbnail_path}\" alt=\"Thumbnail\" style=\"width:50px;height:60px;border-radius:4px;object-fit:cover;flex-shrink:0;\">` \r\n  : '';\r\n\r\ndocHtml += `\r\n<li class=\"doc-item\">\r\n  ${thumbnail}\r\n  <div class=\"doc-info\">\r\n    <strong>${getFileIcon(doc.file_type)} ${doc.original_name}<\/strong>\r\n    <small>Upload: ${doc.created_at}<\/small>\r\n  <\/div>\r\n  <div class=\"doc-actions\">\r\n    <button class=\"btn-download\" onclick=\"downloadDoc(${doc.id})\" title=\"Download\">\u2b07\ufe0f<\/button>\r\n    <button class=\"btn-delete\" onclick=\"deleteDoc(${doc.id})\" title=\"L\u00f6schen\">\ud83d\uddd1\ufe0f<\/button>\r\n    <button class=\"btn-play\" onclick=\"openViewer(${doc.id})\" title=\"Vorlesen\">\ud83d\udcd6<\/button>\r\n  <\/div>\r\n<\/li>`;\r\n});\r\n      documentList.innerHTML = docHtml;\r\n    }\r\n\r\n    \/\/ Audios unver\u00e4ndert\r\n    if(audioFiles.length === 0){\r\n      audioList.innerHTML = \"<li>Keine Audiodateien<\/li>\";\r\n    } else {\r\n      let audioHtml = \"\";\r\n      audioFiles.forEach(doc => {\r\n        audioHtml += `\r\n        <li>\r\n          <div class=\"doc-info\">\r\n            <strong>${doc.original_name}<\/strong> (${doc.file_type})\r\n            <br><small>Upload: ${doc.created_at}<\/small>\r\n          <\/div>\r\n          <div class=\"doc-actions\">\r\n            <button class=\"btn-download\" onclick=\"downloadDoc(${doc.id})\" title=\"Download\">\u2b07\ufe0f<\/button>\r\n            <button class=\"btn-delete\" onclick=\"deleteDoc(${doc.id})\" title=\"L\u00f6schen\">\ud83d\uddd1\ufe0f<\/button>\r\n            <button class=\"btn-audio-play\" onclick=\"playAudioInViewer(${doc.id})\" title=\"Abspielen\">\u25b6\ufe0f<\/button>\r\n          <\/div>\r\n        <\/li>`;\r\n      });\r\n      audioList.innerHTML = audioHtml;\r\n    }\r\n  })\r\n  .catch(err => {\r\n    console.error(err);\r\n    documentList.innerHTML = \"<li>Ajax-Fehler<\/li>\";\r\n    audioList.innerHTML = \"<li>Ajax-Fehler<\/li>\";\r\n  });\r\n}\r\n\r\nwindow.loadDocuments = loadDocuments;\r\n\r\n\r\n    \/\/ Download\r\n    window.downloadDoc= function(docId){\r\n      window.open(ajaxUrl+\"?action=ts_download&doc_id=\"+docId,\"_blank\");\r\n    };\r\n\r\n    \/\/ L\u00f6schen\r\n    window.deleteDoc= function(docId){\r\n      if(!confirm(getTranslation(\"tp-confirm-delete\", \"Wirklich l\u00f6schen?\"))) return;\r\n\r\n      let f= new FormData();\r\n      f.append('action','ts_delete_doc');\r\n      f.append('doc_id',docId);\r\n\r\n      fetch(ajaxUrl,{method:'POST', body:f, credentials:'include'})\r\n      .then(r=> r.json())\r\n      .then(res=>{\r\n        if(res.success){\r\n          alert(getTranslation(\"tp-file-deleted\", \"Datei gel\u00f6scht\"));\r\n\r\n          loadDocuments();\r\n        } else {\r\n          alert(\"Fehler: \"+ (res.data.message||''));\r\n        }\r\n      })\r\n      .catch(err=> alert(\"Fehler deleteDoc:\"+ err));\r\n    };\r\n\r\n    \/\/ Vorlesen\r\n    window.openViewer= function(docId){\r\n      window.location.href= \"\/viewer\/?doc_id=\"+ docId;\r\n    };\r\n\r\n    \/\/ Neu: Audio abspielen im Viewer\r\n    window.playAudioInViewer= function(docId){\r\n      window.location.href= \"\/viewer\/?doc_id=\"+ docId +\"&audio_play=1\";\r\n    };\r\n\r\n    \/\/ ============ Bookmarks ============\r\n\/\/ ============ Bookmarks laden (ohne Kapitel und Wortposition) ============\r\nfunction loadBookmarks() {\r\n  const bookmarkList = document.getElementById(\"ts-bookmark-list\");\r\n  const fileInput = document.getElementById('file-upload');\r\nconst fileNameDisplay = document.getElementById('file-name');\r\n\r\nfileInput.addEventListener('change', () => {\r\n  if(fileInput.files.length > 0) {\r\n    fileNameDisplay.textContent = fileInput.files[0].name;\r\n  } else {\r\n    fileNameDisplay.textContent = getTranslation(\"tp-select-file\", \"Keine Datei ausgew\u00e4hlt\");\r\n  }\r\n});\r\n  bookmarkList.innerHTML = \"<li>Lade Bookmarks...<\/li>\";\r\n\r\n  const f = new FormData();\r\n  f.append(\"action\", \"ts_fetch_bookmarks\");\r\n\r\n  fetch(ajaxUrl, { method: \"POST\", body: f, credentials: \"include\" })\r\n    .then(r => r.json())\r\n    .then(res => {\r\n      if (!res.success) {\r\n        bookmarkList.innerHTML = \"<li>Fehler beim Laden<\/li>\";\r\n        return;\r\n      }\r\n\r\n      const bms = res.data.bookmarks || [];\r\n      if (bms.length === 0) {\r\n        bookmarkList.innerHTML = \"<li>Keine Bookmarks<\/li>\";\r\n        return;\r\n      }\r\n\r\n      let html = \"\";\r\n      bms.forEach(bm => {\r\n        let note = bm.note ? ` - Notiz: ${bm.note}` : \"\";\r\n        let documentTitle = bm.document_title || `Dokument ${bm.document_id}`;\r\n\r\n        html += `\r\n          <li>\r\n            <div>\r\n              <strong>${documentTitle}<\/strong>${note}\r\n            <\/div>\r\n            <div class=\"bm-actions\">\r\n              <button class=\"btn-delete\" onclick=\"deleteBookmark(${bm.id})\" title=\"L\u00f6schen\">\ud83d\uddd1\ufe0f<\/button>\r\n              <button class=\"btn-bookmark-continue\" onclick=\"continueReading(${bm.document_id}, '${encodeURIComponent(bm.position)}')\" title=\"Weiterlesen\">\ud83d\udcd6<\/button>\r\n            <\/div>\r\n          <\/li>`;\r\n      });\r\n\r\n      bookmarkList.innerHTML = html;\r\n    })\r\n    .catch(err => {\r\n      console.error(err);\r\n      bookmarkList.innerHTML = \"<li>Ajax-Fehler<\/li>\";\r\n    });\r\n}\r\n\r\nwindow.loadBookmarks = loadBookmarks;\r\n\r\n\r\n\r\n\r\n\r\n\/\/ ============ Bookmark l\u00f6schen ============\r\nwindow.deleteBookmark = function(bmId){\r\n  if(!confirm(getTranslation(\"tp-confirm-bookmark-delete\", \"Sicher, dass du dieses Bookmark l\u00f6schen willst?\"))) return;\r\n\r\n  let f = new FormData();\r\n  f.append(\"action\",\"ts_delete_bookmark\");\r\n  f.append(\"bm_id\", bmId);\r\n\r\n  fetch(ajaxUrl,{method:\"POST\", body:f, credentials:\"include\"})\r\n  .then(r=> r.json())\r\n  .then(res=>{\r\n    if(res.success){\r\n      alert(getTranslation(\"tp-bookmark-deleted\", \"Bookmark gel\u00f6scht\"));\r\n\r\n      loadBookmarks();\r\n    } else {\r\n      alert(\"Fehler: \"+ (res.data.message || \"\"));\r\n    }\r\n  })\r\n  .catch(err=>{\r\n    alert(\"Fehler deleteBookmark:\"+ err);\r\n  });\r\n};\r\n\r\n\/\/ ============ Weiterlesen =============\r\n\/\/ decodeURIComponent, dann wieder encodeURIComponent\r\nwindow.continueReading = function(docId, pos){\r\n  \/\/ 1) decode => JSON\/cfi\r\n  let decoded = decodeURIComponent(pos);\r\n  \/\/ 2) f\u00fcr die URL param ?pos= ...\r\n  let finalParam = encodeURIComponent(decoded);\r\n\r\n  window.location.href = \"\/viewer\/?doc_id=\" + docId + \"&pos=\" + finalParam;\r\n};\r\n\r\n  \r\n\r\n\r\n\r\n  })();\r\n  <\/script>\r\n<\/div>\r\n<!-- ========== END LIBRARY ========== -->\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>","protected":false},"excerpt":{"rendered":"<p>Mein Archiv: Dateiformat {ext} nicht erlaubt Wirklich l\u00f6schen? Sicher, dass du dieses Bookmark l\u00f6schen willst? Datei gel\u00f6scht Bookmark gel\u00f6scht Upload erfolgreich! Upload-Fehler: Bitte w\u00e4hle eine Datei aus Lade Dokumente&#8230; Lade Audios&#8230; Fehler beim Laden Keine Dokumente gefunden Keine Audiodateien gefunden Keine Bookmarks Fehler: Meine Dokumente Lade Dokumente&#8230; Meine Audiodateien Lade Audios&#8230; Meine Bookmarks Noch keine&hellip;&nbsp;<a href=\"https:\/\/textsnapper.com\/en\/library\/\" rel=\"bookmark\">Read More \u00bb<span class=\"screen-reader-text\">Library<\/span><\/a><\/p>","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"page-templates\/template-pagebuilder-full-width.php","meta":{"neve_meta_sidebar":"","neve_meta_container":"","neve_meta_enable_content_width":"","neve_meta_content_width":0,"neve_meta_title_alignment":"","neve_meta_author_avatar":"","neve_post_elements_order":"","neve_meta_disable_header":"","neve_meta_disable_footer":"","neve_meta_disable_title":"","footnotes":""},"class_list":["post-2779","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Library - TextSnapper<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/textsnapper.com\/en\/library\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Library - TextSnapper\" \/>\n<meta property=\"og:description\" content=\"Mein Archiv: Dateiformat {ext} nicht erlaubt Wirklich l\u00f6schen? Sicher, dass du dieses Bookmark l\u00f6schen willst? Datei gel\u00f6scht Bookmark gel\u00f6scht Upload erfolgreich! Upload-Fehler: Bitte w\u00e4hle eine Datei aus Lade Dokumente&#8230; Lade Audios&#8230; Fehler beim Laden Keine Dokumente gefunden Keine Audiodateien gefunden Keine Bookmarks Fehler: Meine Dokumente Lade Dokumente&#8230; Meine Audiodateien Lade Audios&#8230; Meine Bookmarks Noch keine&hellip;&nbsp;Read More &raquo;Library\" \/>\n<meta property=\"og:url\" content=\"https:\/\/textsnapper.com\/en\/library\/\" \/>\n<meta property=\"og:site_name\" content=\"TextSnapper\" \/>\n<meta property=\"article:modified_time\" content=\"2025-06-06T05:04:59+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/textsnapper.com\\\/library\\\/\",\"url\":\"https:\\\/\\\/textsnapper.com\\\/library\\\/\",\"name\":\"Library - TextSnapper\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/textsnapper.com\\\/#website\"},\"datePublished\":\"2025-01-28T06:05:43+00:00\",\"dateModified\":\"2025-06-06T05:04:59+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/textsnapper.com\\\/library\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/textsnapper.com\\\/library\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/textsnapper.com\\\/library\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/textsnapper.com\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Library\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/textsnapper.com\\\/#website\",\"url\":\"https:\\\/\\\/textsnapper.com\\\/\",\"name\":\"TextSnapper\",\"description\":\"Texte aus Bildern vorlesen, \u00fcbersetzen und einfach h\u00f6ren.\",\"publisher\":{\"@id\":\"https:\\\/\\\/textsnapper.com\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/textsnapper.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/textsnapper.com\\\/#organization\",\"name\":\"Textsnapper\",\"url\":\"https:\\\/\\\/textsnapper.com\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/textsnapper.com\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/textsnapper.com\\\/wp-content\\\/uploads\\\/2024\\\/12\\\/cropped-cropped-cropped-textsnapper.png\",\"contentUrl\":\"https:\\\/\\\/textsnapper.com\\\/wp-content\\\/uploads\\\/2024\\\/12\\\/cropped-cropped-cropped-textsnapper.png\",\"width\":200,\"height\":200,\"caption\":\"Textsnapper\"},\"image\":{\"@id\":\"https:\\\/\\\/textsnapper.com\\\/#\\\/schema\\\/logo\\\/image\\\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Library - TextSnapper","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/textsnapper.com\/en\/library\/","og_locale":"en_US","og_type":"article","og_title":"Library - TextSnapper","og_description":"Mein Archiv: Dateiformat {ext} nicht erlaubt Wirklich l\u00f6schen? Sicher, dass du dieses Bookmark l\u00f6schen willst? Datei gel\u00f6scht Bookmark gel\u00f6scht Upload erfolgreich! Upload-Fehler: Bitte w\u00e4hle eine Datei aus Lade Dokumente&#8230; Lade Audios&#8230; Fehler beim Laden Keine Dokumente gefunden Keine Audiodateien gefunden Keine Bookmarks Fehler: Meine Dokumente Lade Dokumente&#8230; Meine Audiodateien Lade Audios&#8230; Meine Bookmarks Noch keine&hellip;&nbsp;Read More &raquo;Library","og_url":"https:\/\/textsnapper.com\/en\/library\/","og_site_name":"TextSnapper","article_modified_time":"2025-06-06T05:04:59+00:00","twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/textsnapper.com\/library\/","url":"https:\/\/textsnapper.com\/library\/","name":"Library - TextSnapper","isPartOf":{"@id":"https:\/\/textsnapper.com\/#website"},"datePublished":"2025-01-28T06:05:43+00:00","dateModified":"2025-06-06T05:04:59+00:00","breadcrumb":{"@id":"https:\/\/textsnapper.com\/library\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/textsnapper.com\/library\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/textsnapper.com\/library\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/textsnapper.com\/"},{"@type":"ListItem","position":2,"name":"Library"}]},{"@type":"WebSite","@id":"https:\/\/textsnapper.com\/#website","url":"https:\/\/textsnapper.com\/","name":"TextSnapper","description":"Texte aus Bildern vorlesen, \u00fcbersetzen und einfach h\u00f6ren.","publisher":{"@id":"https:\/\/textsnapper.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/textsnapper.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/textsnapper.com\/#organization","name":"Textsnapper","url":"https:\/\/textsnapper.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/textsnapper.com\/#\/schema\/logo\/image\/","url":"https:\/\/textsnapper.com\/wp-content\/uploads\/2024\/12\/cropped-cropped-cropped-textsnapper.png","contentUrl":"https:\/\/textsnapper.com\/wp-content\/uploads\/2024\/12\/cropped-cropped-cropped-textsnapper.png","width":200,"height":200,"caption":"Textsnapper"},"image":{"@id":"https:\/\/textsnapper.com\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/textsnapper.com\/en\/wp-json\/wp\/v2\/pages\/2779","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/textsnapper.com\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/textsnapper.com\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/textsnapper.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/textsnapper.com\/en\/wp-json\/wp\/v2\/comments?post=2779"}],"version-history":[{"count":247,"href":"https:\/\/textsnapper.com\/en\/wp-json\/wp\/v2\/pages\/2779\/revisions"}],"predecessor-version":[{"id":10474,"href":"https:\/\/textsnapper.com\/en\/wp-json\/wp\/v2\/pages\/2779\/revisions\/10474"}],"wp:attachment":[{"href":"https:\/\/textsnapper.com\/en\/wp-json\/wp\/v2\/media?parent=2779"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}