UNPKG

libreblog

Version:

An in-browser Static Site Generator focused on content-rich blogs and news websites

1,191 lines (1,036 loc) 168 kB
/** * Libreblog.org * Copyright (c) 2025 Thiago Gomes Quinalia * This code is licensed under the MIT License. * License text available at https://opensource.org/license/MIT */ 'use strict'; (function(){ /* Constants and default values */ const debug_mode = false; const thumbnail_column = "Thumbnail"; const supported_formats = ["JPG", "JPEG", "PNG", "APNG", "GIF", "SVG", "WEBP", "AVIF", "ICO"]; const supported_version_formats = ["JPG", "JPEG", "PNG", "WEBP"]; const jszip_version = "3.10.1"; const ace_builds_version = "1.41.0"; const default_footer_social_snippet = `<a href="PASTE YOUR NEWSLETTER URL HERE" id="subscribe-button" target="_blank"> Subscribe </a> <!-- Simply enter the URLs of your social media pages in the segments marked in capital letters. You can delete any unused platform sections, which are indicated by the comments: "[Platform]:start" and "[Platform]:end". For SVGs of other social media platforms, visit svgrepo.com . --> <div id="footer-social-icons"> <div id="first-icons-section"> <!-- Facebook:start --> <a href="PASTE YOUR FACEBOOK URL HERE" target="_blank" title="Follow us on Facebook"> <svg version="1.1" id="layer_facebook" class="social-icon-svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 54 54" xml:space="preserve"> <g> <rect class="social-icon-rect" x="0" y="0" width="54" height="54"/> <path d="M40,4.8c2.2,0,4.1,0.8,5.7,2.4c1.6,1.6,2.4,3.5,2.4,5.7v26.8c0,2.2-0.8,4.1-2.4,5.7c-1.6,1.6-3.5,2.4-5.7,2.4h-5.2V31.1 h5.6l0.8-6.5h-6.4v-4.1c0-1,0.2-1.8,0.7-2.3c0.4-0.5,1.3-0.8,2.6-0.8l3.4,0v-5.8c-1.2-0.2-2.8-0.3-5-0.3c-2.5,0-4.6,0.7-6.1,2.2 c-1.5,1.5-2.3,3.6-2.3,6.3v4.8h-5.6v6.5h5.6v16.6H13.3c-2.2,0-4.1-0.8-5.7-2.4c-1.6-1.6-2.4-3.5-2.4-5.7V12.9 c0-2.2,0.8-4.1,2.4-5.7c1.6-1.6,3.5-2.4,5.7-2.4H40z"/> </g> </svg> </a> <!-- Facebook:end --> <!-- X/Twitter:start --> <a href="PASTE YOUR X/TWITTER URL HERE" target="_blank" title="Follow us on X/Twitter"> <svg version="1.1" id="layer_x" class="social-icon-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <rect class="social-icon-rect" x="0" y="0" width="512" height="512"/> <path d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"/> </svg> </a> <!-- X/Twitter:end --> <!-- Instagram:start --> <a href="PASTE YOUR INSTAGRAM URL HERE" target="_blank" title="Follow us on Instagram"> <svg version="1.1" id="layer_instagram" class="social-icon-svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 20 20" xml:space="preserve"> <g> <rect class="social-icon-rect" x="0" y="0" width="20" height="20"/> <path d="M12.7 10c0-1.5-1.2-2.7-2.7-2.7S7.3 8.5 7.3 10s1.2 2.7 2.7 2.7c1.5 0 2.7-1.2 2.7-2.7zm1.4 0c0 2.3-1.8 4.1-4.1 4.1S5.9 12.3 5.9 10 7.7 5.9 10 5.9s4.1 1.8 4.1 4.1zm1.1-4.3c0 .6-.4 1-1 1s-1-.4-1-1 .4-1 1-1 1 .5 1 1zM10 3.4c-1.2 0-3.7-.1-4.7.3-.7.3-1.3.9-1.5 1.6-.4 1-.3 3.5-.3 4.7s-.1 3.7.3 4.7c.2.7.8 1.3 1.5 1.5 1 .4 3.6.3 4.7.3s3.7.1 4.7-.3c.7-.3 1.2-.8 1.5-1.5.4-1.1.3-3.6.3-4.7s.1-3.7-.3-4.7c-.2-.7-.8-1.3-1.5-1.5-1-.5-3.5-.4-4.7-.4zm8 6.6v3.3c0 1.2-.4 2.4-1.3 3.4-.9.9-2.1 1.3-3.4 1.3H6.7c-1.2 0-2.4-.4-3.4-1.3-.8-.9-1.3-2.1-1.3-3.4V10 6.7c0-1.3.5-2.5 1.3-3.4C4.3 2.5 5.5 2 6.7 2h6.6c1.2 0 2.4.4 3.4 1.3.8.9 1.3 2.1 1.3 3.4V10z"/> </g> </svg> </a> <!-- Instagram:end --> <!-- YouTube:start --> <a href="PASTE YOUR YOUTUBE URL HERE" target="_blank" title="Subscribe to our channel on YouTube"> <svg version="1.1" id="layer_youtube" class="social-icon-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600"> <rect class="social-icon-rect" x="0" y="0" width="600" height="600"/> <path transform="translate(12, 44)" d="M549.7 124.1c-6.3-23.7-24.8-42.3-48.3-48.6C458.8 64 288 64 288 64S117.2 64 74.6 75.5c-23.5 6.3-42 24.9-48.3 48.6-11.4 42.9-11.4 132.3-11.4 132.3s0 89.4 11.4 132.3c6.3 23.7 24.8 41.5 48.3 47.8C117.2 448 288 448 288 448s170.8 0 213.4-11.5c23.5-6.3 42-24.2 48.3-47.8 11.4-42.9 11.4-132.3 11.4-132.3s0-89.4-11.4-132.3zm-317.5 213.5V175.2l142.7 81.2-142.7 81.2z"/> </svg> </a> <!-- YouTube:end --> </div> <!-- "See more" button --> <details id="more-icons-section"> <summary><span id="more-icons-btn">+</span></summary> <div id="more-icons-div"> <!-- Keep below the icons that will be hidden and only displayed if the user clicks on the "See more" button --> <!-- LinkedIn:start --> <a href="PASTE YOUR LINKEDIN URL HERE" target="_blank" title="Connect with us on LinkedIn"> <svg version="1.1" id="layer_linkedin" class="social-icon-svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 54 54" xml:space="preserve"> <g> <rect class="social-icon-rect" x="0" y="0" width="54" height="54"/> <path d="M48.1,12.9v26.8c0,2.2-0.8,4.1-2.4,5.7c-1.6,1.6-3.5,2.4-5.7,2.4H13.3c-2.2,0-4.1-0.8-5.7-2.4c-1.6-1.6-2.4-3.5-2.4-5.7 V12.9c0-2.2,0.8-4.1,2.4-5.7c1.6-1.6,3.5-2.4,5.7-2.4H40c2.2,0,4.1,0.8,5.7,2.4C47.3,8.8,48.1,10.7,48.1,12.9z M18.7,15.4 c0-1-0.4-1.8-1-2.4C17,12.3,16.2,12,15.1,12c-1.1,0-2,0.3-2.6,0.9c-0.7,0.6-1,1.4-1,2.4c0,0.9,0.3,1.7,1,2.4c0.7,0.6,1.5,1,2.6,1h0 c1.1,0,2-0.3,2.7-1C18.4,17.1,18.7,16.3,18.7,15.4z M11.8,40.7h6.4V21.3h-6.4V40.7z M35,40.7h6.4V29.6c0-2.9-0.7-5-2-6.5 c-1.4-1.5-3.2-2.2-5.4-2.2c-2.5,0-4.5,1.1-5.8,3.3h0.1v-2.8h-6.4c0.1,1.2,0.1,7.7,0,19.4h6.4V29.9c0-0.7,0.1-1.2,0.2-1.6 c0.3-0.7,0.7-1.2,1.3-1.7c0.6-0.5,1.2-0.7,2.1-0.7c2.2,0,3.2,1.5,3.2,4.4V40.7z"/> </g> </svg> </a> <!-- LinkedIn:end --> <!-- Tiktok:start --> <a href="PASTE YOUR TIKTOK URL HERE" target="_blank" title="Follow us on Tiktok"> <svg version="1.1" id="layer_tiktok" class="social-icon-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <rect class="social-icon-rect" x="0" y="0" width="24" height="24"/> <path d="M21,2H3A1,1,0,0,0,2,3V21a1,1,0,0,0,1,1H21a1,1,0,0,0,1-1V3A1,1,0,0,0,21,2Zm-3.281,8.725h0c-.109.011-.219.016-.328.017A3.571,3.571,0,0,1,14.4,9.129v5.493a4.061,4.061,0,1,1-4.06-4.06c.085,0,.167.008.251.013v2a2.067,2.067,0,1,0-.251,4.119A2.123,2.123,0,0,0,12.5,14.649l.02-9.331h1.914A3.564,3.564,0,0,0,17.719,8.5Z"/> </svg> </a> <!-- Tiktok:end --> <!-- WhatsApp:start --> <a href="PASTE YOUR WHATSAPP URL HERE" target="_blank" title="Join us on WhatsApp"> <svg version="1.1" id="layer_whatsapp" class="social-icon-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54"> <rect class="social-icon-rect" x="0" y="0" width="54" height="54"/> <path d="M18.9,6.3c2.6-1.1,5.3-1.7,8.2-1.7c2.8,0,5.6,0.6,8.2,1.7c2.6,1.1,4.8,2.6,6.7,4.5c1.9,1.9,3.4,4.1,4.5,6.7 c1.1,2.6,1.7,5.3,1.7,8.2c0,2.8-0.6,5.6-1.7,8.2s-2.6,4.8-4.5,6.7c-1.9,1.9-4.1,3.4-6.7,4.5s-5.3,1.7-8.2,1.7 c-3.6,0-7-0.9-10.2-2.6L5.2,47.9L9,36.6c-2-3.3-3-6.9-3-10.9c0-2.8,0.6-5.6,1.7-8.2c1.1-2.6,2.6-4.8,4.5-6.7 C14,8.9,16.3,7.5,18.9,6.3z M27,43.2c2.4,0,4.6-0.5,6.8-1.4c2.2-0.9,4-2.2,5.6-3.7s2.8-3.4,3.7-5.6s1.4-4.4,1.4-6.8 s-0.5-4.6-1.4-6.8c-0.9-2.2-2.2-4-3.7-5.6s-3.4-2.8-5.6-3.7c-2.2-0.9-4.4-1.4-6.8-1.4c-2.4,0-4.6,0.5-6.8,1.4 c-2.2,0.9-4,2.2-5.6,3.7s-2.8,3.4-3.7,5.6c-0.9,2.2-1.4,4.4-1.4,6.8c0,3.8,1.1,7.2,3.3,10.3l-2.2,6.5l6.8-2.1 C20.3,42.3,23.6,43.2,27,43.2z M31.2,29.8c0.7-0.9,1.2-1.4,1.5-1.4c0.2,0,1.1,0.4,2.7,1.2c1.6,0.8,2.4,1.3,2.5,1.5 c0,0.1,0.1,0.2,0.1,0.4c0,0.6-0.2,1.3-0.5,2.1c-0.3,0.7-1,1.3-2,1.8c-1,0.5-2,0.7-2.8,0.7c-1.1,0-2.8-0.6-5.3-1.7 c-1.8-0.8-3.4-1.9-4.7-3.3c-1.3-1.4-2.7-3.1-4.1-5.2c-1.3-2-2-3.8-2-5.4v-0.2c0.1-1.7,0.7-3.2,2.1-4.4c0.4-0.4,0.9-0.6,1.5-0.6 c0.1,0,0.3,0,0.5,0c0.2,0,0.4,0,0.5,0c0.4,0,0.6,0.1,0.7,0.2c0.1,0.1,0.3,0.4,0.4,0.8c0.1,0.4,0.5,1.2,0.9,2.5 c0.5,1.3,0.7,2,0.7,2.1c0,0.4-0.3,0.9-1,1.6c-0.6,0.7-1,1.1-1,1.3c0,0.1,0,0.3,0.1,0.4c0.6,1.4,1.6,2.6,2.8,3.8 c1,1,2.4,1.9,4.2,2.8c0.2,0.1,0.4,0.2,0.6,0.2C30,31.1,30.5,30.7,31.2,29.8z"/> </svg> </a> <!-- WhatsApp:end --> <!-- Threads:start --> <a href="PASTE YOUR THREADS URL HERE" target="_blank" title="Follow us on Threads"> <svg version="1.1" id="layer_threads" class="social-icon-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 960"> <rect class="social-icon-rect" x="0" y="0" width="960" height="960"/> <path d="M404.63 392.13c-11.92-7.93-51.53-35.49-51.53-35.49 33.4-47.88 77.46-66.52 138.36-66.52 43.07 0 79.64 14.52 105.75 42 26.12 27.49 41.02 66.8 44.41 117.07 14.48 6.07 27.85 13.22 39.99 21.4 48.96 33 75.92 82.34 75.92 138.91 0 120.23-98.34 224.67-276.35 224.67-152.84 0-311.63-89.11-311.63-354.45 0-263.83 153.81-353.92 311.2-353.92 72.68 0 243.16 10.76 307.27 222.94l-60.12 15.63C678.33 213.2 574.4 189.14 479.11 189.14c-157.52 0-246.62 96.13-246.62 300.65 0 183.38 99.59 280.8 248.71 280.8 122.68 0 214.15-63.9 214.15-157.44 0-63.66-53.37-94.14-56.1-94.14-10.42 54.62-38.36 146.5-161.01 146.5-71.46 0-133.07-49.47-133.07-114.29 0-92.56 87.61-126.06 156.8-126.06 25.91 0 57.18 1.75 73.46 5.07 0-28.21-23.81-76.49-83.96-76.49-55.15-.01-69.14 17.92-86.84 38.39zm105.8 96.25c-90.13 0-101.79 38.51-101.79 62.7 0 38.86 46.07 51.74 70.65 51.74 45.06 0 91.35-12.52 98.63-107.31-22.85-5.14-39.88-7.13-67.49-7.13z"/> </svg> </a> <!-- Threads:end --> <!-- Telegram:start --> <a href="PASTE YOUR TELEGRAM URL HERE" target="_blank" title="Join us on Telegram"> <svg version="1.1" id="layer_telegram" class="social-icon-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 58 58"> <rect class="social-icon-rect" x="0" y="0" width="58" height="58"/> <path transform="translate(2, 2)" d="M49.7,16.5c1.3,3.1,2,6.3,2,9.7s-0.7,6.6-2,9.7c-1.3,3.1-3.1,5.7-5.3,8c-2.2,2.2-4.9,4-8,5.3c-3.1,1.3-6.3,2-9.7,2 c-3.4,0-6.6-0.7-9.7-2s-5.7-3.1-8-5.3c-2.2-2.2-4-4.9-5.3-8c-1.3-3.1-2-6.3-2-9.7s0.7-6.6,2-9.7c1.3-3.1,3.1-5.7,5.3-8 c2.2-2.2,4.9-4,8-5.3s6.3-2,9.7-2c3.4,0,6.6,0.7,9.7,2c3.1,1.3,5.7,3.1,8,5.3C46.6,10.8,48.3,13.5,49.7,16.5z M34.8,37.7l4.1-19.3 c0.2-0.8,0.1-1.4-0.3-1.8c-0.4-0.4-0.8-0.4-1.4-0.2l-24.1,9.3c-0.5,0.2-0.9,0.4-1.1,0.7c-0.2,0.3-0.2,0.5-0.1,0.7 c0.1,0.2,0.4,0.4,0.9,0.5l6.2,1.9l14.3-9c0.4-0.3,0.7-0.3,0.9-0.2c0.1,0.1,0.1,0.2-0.1,0.4L22.5,31.3L22,37.7 c0.4,0,0.8-0.2,1.3-0.6l3-2.9l6.2,4.6C33.8,39.5,34.5,39.1,34.8,37.7z"/> </svg> </a> <!-- Telegram:end --> <!-- Bluesky:start --> <a href="PASTE YOUR BLUESKY URL HERE" target="_blank" title="Follow us on Bluesky"> <svg version="1.1" id="layer_bluesky" class="social-icon-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <rect class="social-icon-rect" x="0" y="0" width="512" height="512"/> <path transform="translate(32, 0)" d="M64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l320 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64L64 32zM224 247.4c14.5-30 54-85.8 90.7-113.3c26.5-19.9 69.3-35.2 69.3 13.7c0 9.8-5.6 82.1-8.9 93.8c-11.4 40.8-53 51.2-90 44.9c64.7 11 81.2 47.5 45.6 84c-67.5 69.3-97-17.4-104.6-39.6c0 0 0 0 0 0l-.3-.9c-.9-2.6-1.4-4.1-1.8-4.1s-.9 1.5-1.8 4.1c-.1 .3-.2 .6-.3 .9c0 0 0 0 0 0c-7.6 22.2-37.1 108.8-104.6 39.6c-35.5-36.5-19.1-73 45.6-84c-37 6.3-78.6-4.1-90-44.9c-3.3-11.7-8.9-84-8.9-93.8c0-48.9 42.9-33.5 69.3-13.7c36.7 27.5 76.2 83.4 90.7 113.3z"/> </svg> </a> <!-- Bluesky:end --> <!-- Mastodon:start --> <a href="PASTE YOUR MASTODON URL HERE" target="_blank" title="Follow us on Mastodon"> <svg version="1.1" id="layer_mastodon" class="social-icon-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <rect class="social-icon-rect" x="0" y="0" width="24" height="24"/> <path d="M21.327 8.566c0-4.339-2.843-5.61-2.843-5.61-1.433-.658-3.894-.935-6.451-.956h-.063c-2.557.021-5.016.298-6.45.956 0 0-2.843 1.272-2.843 5.61 0 .993-.019 2.181.012 3.441.103 4.243.778 8.425 4.701 9.463 1.809.479 3.362.579 4.612.51 2.268-.126 3.541-.809 3.541-.809l-.075-1.646s-1.621.511-3.441.449c-1.804-.062-3.707-.194-3.999-2.409a4.523 4.523 0 0 1-.04-.621s1.77.433 4.014.536c1.372.063 2.658-.08 3.965-.236 2.506-.299 4.688-1.843 4.962-3.254.434-2.223.398-5.424.398-5.424zm-3.353 5.59h-2.081V9.057c0-1.075-.452-1.62-1.357-1.62-1 0-1.501.647-1.501 1.927v2.791h-2.069V9.364c0-1.28-.501-1.927-1.502-1.927-.905 0-1.357.546-1.357 1.62v5.099H6.026V8.903c0-1.074.273-1.927.823-2.558.566-.631 1.307-.955 2.228-.955 1.065 0 1.872.409 2.405 1.228l.518.869.519-.869c.533-.819 1.34-1.228 2.405-1.228.92 0 1.662.324 2.228.955.549.631.822 1.484.822 2.558v5.253z"/> </svg> </a> <!-- Mastodon:end --> <!-- Github:start --> <a href="PASTE YOUR GITHUB URL HERE" target="_blank" title="Star our repository on Github"> <svg id="layer_github" class="social-icon-svg" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <rect class="social-icon-rect" x="0" y="0" width="24" height="24"/> <path d="M12,2A10,10,0,0,0,8.84,21.5c.5.08.66-.23.66-.5V19.31C6.73,19.91,6.14,18,6.14,18A2.69,2.69,0,0,0,5,16.5c-.91-.62.07-.6.07-.6a2.1,2.1,0,0,1,1.53,1,2.15,2.15,0,0,0,2.91.83,2.16,2.16,0,0,1,.63-1.34C8,16.17,5.62,15.31,5.62,11.5a3.87,3.87,0,0,1,1-2.71,3.58,3.58,0,0,1,.1-2.64s.84-.27,2.75,1a9.63,9.63,0,0,1,5,0c1.91-1.29,2.75-1,2.75-1a3.58,3.58,0,0,1,.1,2.64,3.87,3.87,0,0,1,1,2.71c0,3.82-2.34,4.66-4.57,4.91a2.39,2.39,0,0,1,.69,1.85V21c0,.27.16.59.67.5A10,10,0,0,0,12,2Z"/> </svg> </a> <!-- Github:end --> </div> </details> </div>`; const default_main_js = `/** Libreblog's core features **/ const searchInput = document.getElementById("search-input"); const searchBoxWrapper = document.getElementById("search-box-wrapper"); const shareButton = document.getElementById("share-button"); const itemDates = document.getElementsByClassName("item-date"); const formatDate = function(dateStr, options) { const dateObj = new Date(dateStr); const lang = navigator && navigator.language ? navigator.language : "en"; let formattedDate = new Intl.DateTimeFormat(lang, options).format(dateObj); if (lang.startsWith("pt")) { formattedDate = formattedDate.replaceAll("de ", ""); } return formattedDate; } if (itemDates && itemDates.length > 0) { for (let i = 0; i < itemDates.length; i++) { const itemDate = itemDates[i]; if (itemDate.dateTime.split("T")[0] === new Date().toISOString().split("T")[0]) { itemDate.innerText = formatDate(itemDate.dateTime, { hour: 'numeric', minute: 'numeric' }) + " " + itemDate.attributes["timezone-abbreviation"].value; } else { itemDate.innerText = formatDate(itemDate.dateTime, { year: 'numeric', month: 'short', day: 'numeric' }); } } } if (shareButton) { const url = shareButton.attributes["data-url"].value; const title = shareButton.attributes["data-title"].value; const data = {url: url, title: title}; if (navigator.canShare && navigator.canShare(data)) { shareButton.addEventListener("click", async (event) => { try { await navigator.share(data); } catch (e) { console.log(e); } }); } else { shareButton.style.display = "none"; } } if (searchInput && searchBoxWrapper) searchInput.addEventListener("input", (event) => { let value = event.target.value; if (value) { value = value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase(); let innerHTML = "<section id='search-results-box'>"; const filtered = libreblogSearch.filter((item) => { //Simple search function (exact match) if (item.searchable && item.searchable.includes(value) && item['in_sitemap']) return true; return false; }); if (filtered.length === 0) { innerHTML = ""; } else { filtered.forEach((item) => { const p = item.published ? item.published : item.created; const d = new Date(p.replace(" ", "T")); const lang = navigator && navigator.language ? navigator.language : "en"; const published = new Intl.DateTimeFormat(lang, {year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric'}).format(d); innerHTML += "<article class='search-card'>" + "<a class='search-link' href='" + item.uri + "'>" + "<div class='search-title'>" + item.title + "</div>" + "<div class='search-published'>" + published + "</div>" + "<div class='search-subtitle'>" + item.subtitle + "</div>" + "</a>" + "</article>"; }); innerHTML += "</section>"; } searchBoxWrapper.innerHTML = innerHTML; } else { searchBoxWrapper.innerHTML = ""; } }); /** Insert custom code below **/ `; const default_htaccess = `DirectoryIndex index.html Options -Indexes <IfModule mod_deflate.c> AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript </IfModule> <IfModule mod_expires.c> ExpiresActive On ExpiresDefault "access plus 1 month" ExpiresByType text/html "access plus 1 hour" </IfModule> <IfModule mod_headers.c> Header set X-Content-Type-Options "nosniff" Header set X-XSS-Protection "1; mode=block" Header set X-Frame-Options "DENY" Header set X-Permitted-Cross-Domain-Policies "none" Header set Permissions-Policy "geolocation=(), camera=(), microphone=()" </IfModule> `; /* Funtions */ const generateJsonDb = function() { globalThis.lb.useSqlite(async (db) => { const jsonDb = { sections: await globalThis.lb.getAllResultRows("sections", db, null), series: await globalThis.lb.getAllResultRows("series", db, null), articles: await globalThis.lb.getAllResultRows("articles", db, null), relations: await globalThis.lb.getAllResultRows("relations", db, null), authors: await globalThis.lb.getAllResultRows("authors", db, null), navbar_items: await globalThis.lb.getAllResultRows("navbar_items", db, null), media: await globalThis.lb.getAllResultRows("media", db, null), sources: await globalThis.lb.getAllResultRows("sources", db, null), templates: await globalThis.lb.getAllResultRows("templates", db, null), settings: await globalThis.lb.getAllResultRows("settings", db, null) }; const jsonDbStr = JSON.stringify(jsonDb, null, 2); const blob = new Blob([jsonDbStr], {type: "text/plain;charset=utf-8"}); saveFile(blob, "mydb.json"); }); } const insertThemeFromObject = async function(db, theme, themeUri) { let templates = structuredClone(theme.templates); templates.push({ template_uri: "image", template_set: "", template_type: "", content_type: "cdata", contents: theme["image"] ? theme["image"] : "" }); templates.push({ template_uri: "config", template_set: "", template_type: "", content_type: "json", contents: JSON.stringify(theme["config"]) }); for (let i = 0; i < templates.length; i++) { const template = templates[i]; await db.exec({ sql: "INSERT INTO templates ( " + "theme_uri, " + "template_uri, " + "template_set, " + "template_type, " + "content_type, " + "contents, " + "created, " + "updated " + ") VALUES ('" + themeUri + "', ?, ?, ?, ?, ?, " + "STRFTIME('%Y-%m-%d %H:%M', DATETIME('now','localtime')), " + "STRFTIME('%Y-%m-%d %H:%M', DATETIME('now','localtime')))", bind: [ template['template_uri'], template['template_set'], template['template_type'], template['content_type'], template['contents'] ] }); } }; const createThemeObjectFromDb = async function(db, themeUri) { const theme = {theme_uri: themeUri, config: "{}", templates: [], image: ""}; const resultRows = await db.exec({ sql: "SELECT * FROM templates WHERE theme_uri = '" + themeUri + "'", rowMode: 'object' }); for (let i = 0; i < resultRows.length; i++) { if (resultRows[i]["template_uri"] === "image") { theme["image"] = resultRows[i]["contents"]; } else if (resultRows[i]["template_uri"] === "config") { try { theme["config"] = JSON.parse(resultRows[i]["contents"]); } catch (e) { globalThis.console.error("It was not possible to load this configuration. Error: ", e); } } else { theme["templates"].push({ template_uri: resultRows[i]["template_uri"], template_set: resultRows[i]["template_set"], template_type: resultRows[i]["template_type"], content_type: resultRows[i]["content_type"], contents: resultRows[i]["contents"] }); } } return theme; } const addDisabledOption = function(selectFilter) { let option = globalThis.document.createElement("option"); option.innerText = "Filter by..."; option.value = "none"; selectFilter.appendChild(option); } const generateHeaderStr = function(id, args) { let header = "<tr style='display:run-in' class='checkbox-td'><th><input id='th-checkbox' type='checkbox'></th>"; for (let i = 0; i < args.length; i++) { const column = args[i][1]; header += "<th class='" + getTdClass(id, column)[0] + "'><div>" + column + (column !== thumbnail_column ? " <span><img class='data-table-icon' src='images/chevron-down.svg' /></span>" : "") + "</div></th>"; } header += "</tr>"; return header; } const generateHeader = function(id, args) { let selectFilter = globalThis.document.getElementById('select-filter'); while (selectFilter.firstChild) { selectFilter.removeChild(selectFilter.lastChild); } addDisabledOption(selectFilter); const header = generateHeaderStr(id, args); let columns = []; for (let i = 0; i < args.length; i++) { columns.push(args[i][0]); if (args[i][1] !== thumbnail_column) { let option = globalThis.document.createElement("option"); option.innerText = args[i][1]; option.value = args[i][1]; selectFilter.appendChild(option); } } return [header, columns]; } const titleIfBig = function(text, size) { if (text && text.length > size) return text; return ""; } const mobileScreen = function() { return window.innerWidth <= 1024; } const smallScreen = function() { return window.innerWidth < 1360; } const bigScreen = function() { return window.innerWidth > 1530; } const veryBigScreen = function() { return window.innerWidth > 1900; } const bigTd = function(tableId, columnName) { let big = false; switch (tableId) { case "sources-table": if (["Title"].includes(columnName)) { big = true; } else if (["Author"].includes(columnName) && bigScreen()) { big = true; } break; case "authors-table": if (["Email", "Name"].includes(columnName) && bigScreen()) big = true; break; case "media-table": if (["Alt Text"].includes(columnName) && bigScreen()) big = true; break; case "navbar-table": if (["Label", "ID or URL"].includes(columnName) && bigScreen()) big = true; break; case "series-table": if (["Title"].includes(columnName) && bigScreen()) big = true; break; case "sections-table": if (["Title"].includes(columnName) && bigScreen()) big = true; break; case "articles-table": if (["Title"].includes(columnName) && veryBigScreen()) big = true; break; default: } return big; } const smallTd = function(tableId, columnName) { let small = false; switch (tableId) { case "modal-references-table": if (["Year", "Type"].includes(columnName)) small = true; break; case "articles-table": if (["Type", "Status"].includes(columnName)) { small = true; } else if (["Section", "Series", "Author(s)"].includes(columnName) && !bigScreen()) { small = true; } break; case "series-table": if (["Status"].includes(columnName)) small = true; break; case "relations-table": if (["Type"].includes(columnName)) small = true; break; case "sources-table": if (["Type", "Year"].includes(columnName)) small = true; break; case "media-table": if (["File Ext."].includes(columnName)) small = true; break; default: } return small; } const modestTd = function(tableId, columnName) { let modest = false; switch (tableId) { case "articles-table": if (["Title"].includes(columnName) && !bigScreen()) { modest = true; } else if (["Section", "Series", "Author(s)"].includes(columnName) && !veryBigScreen()) { modest = true; } break; case "series-table": if (["Section"].includes(columnName) && !veryBigScreen()) modest = true; break; case "relations-table": if (["Article 1", "Article 2"].includes(columnName) && !veryBigScreen()) modest = true; break; case "media-table": if (["Size (bytes)"].includes(columnName)) modest = true; break; default: if (["ID"].includes(columnName) && !veryBigScreen()) modest = true; } return modest; } const escapeHTML = function(text) { if (typeof text !== "string") return text; return text.replace( /[&<>'"]/g, tag => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', "'": '&#39;', '"': '&quot;' }[tag] || tag) ); } const makeALink = function(table, id, uri, text) { if(!["navbar_items", "sources"].includes(table)) { return "<a href='/" + getEditPageFromTable(table) + ".html?uri=" + uri + "'><div>" + escapeHTML(text) + "</div></a>"; } else if (id === "sources-table") { return "<a source-uri='" + uri + "' href='#source-modal'><div source-uri='" + uri + "'>" + escapeHTML(text) + "</div></a>" } return "<div>" + escapeHTML(text) + "</div>"; } const getTdClass = function(id, column) { let tdClass = ""; const lines = 2; let titleSize = 16 * lines; if (mobileScreen() && id === 'modal-references-table') { tdClass = "mobile-td-in-modal"; } else if (mobileScreen()) { tdClass = "mobile-td"; } else if (smallTd(id, column)) { tdClass = "small-td"; titleSize = 10 * lines; } else if (modestTd(id, column)) { tdClass = "modest-td"; titleSize = 13 * lines; } else if (bigTd(id, column)) { tdClass = "big-td"; titleSize = 33 * lines; } else { tdClass = "normal-td"; } if (["Title", "Author", "Label", "Alt Text", "Name", "Location"].includes(column)) { tdClass += " hyphen-td"; } return [tdClass, titleSize]; } const generateTrStr = function(id, table, row, uri, columns, counter, args){ let tr = "<tr id='tr-" + counter + "' style='display:run-in'><td class='checkbox-td'><input type='checkbox' id='checkbox-" + counter + "' value='" + uri + "'></td>"; for (let i = 0; i < args.length; i++) { const [tdClass, titleSize] = getTdClass(id, args[i][1]); if (["media-table"].includes(id) && args[i][1] === thumbnail_column) { tr += "<td id='thumbnail-" + i + "' uri='" + uri + "' class='thumbnail-td' name='" + args[i][1] + "'><div>&nbsp;</div></td>"; } else { tr += "<td class='" + tdClass + "' name='" + args[i][1] + "' title='" + titleIfBig(row[columns[i]], titleSize) + "'>" + makeALink(table, id, uri, row[columns[i]]) + "</td>"; } } tr += "</tr>"; return tr; } const setCheckboxHandler = function(){ const thCheckbox = globalThis.document.getElementById("th-checkbox"); if (thCheckbox) { thCheckbox.addEventListener("change", thCheckboxHandler); } } const addOrderByIcons = function(dataTable, db, id, table, ...args) { let ths = dataTable.getElementsByTagName("th"); ths = [].slice.call(ths).slice(1); for (let i = 0; i < ths.length; i++) { if (args[i][1] === thumbnail_column) continue; const div = ths[i].getElementsByTagName("div")[0]; div.innerHTML = args[i][1] + " <span id='th-" + i + "'><img class='data-table-icon' src='images/chevron-down.svg' /></span>"; const span = globalThis.document.getElementById("th-" + i); span.addEventListener("click", async (event) => { let newSpan; const src = event.target.attributes["src"]; if (src && src.value && src.value.includes('chevron-down.svg')) { await fillDOM(db, id, table, args[i][0] + " ASC", ...args); newSpan = globalThis.document.getElementById("th-" + i); newSpan.innerHTML = "<img class='data-table-icon' src='images/chevron-up.svg' />"; } else { await fillDOM(db, id, table, args[i][0] + " DESC", ...args); newSpan = globalThis.document.getElementById("th-" + i); newSpan.innerHTML = "<img class='data-table-icon' src='images/chevron-down.svg' />"; } if (newSpan) newSpan.style = "filter: brightness(0) saturate(100%) invert(7%) sepia(95%) saturate(6909%) hue-rotate(247deg) brightness(128%) contrast(146%);"; if (["media-table"].includes(id)) addThumbnails(); }); } } const preFormatThead = function() { let page = thisPage(); let header; let body; let id; switch (page) { case "index": break; case "articles": id = "articles-table"; if (smallScreen()) { header = generateHeaderStr(id, [["", "ID"], ["", "Type"], ["", "Status"], ["", "Title"], ["", "Author(s)"], ["", "Section"], ["", "Series"], ["", "Created"]]); body = "<td><div>&nbsp;</div></td>".repeat(9); } else { header = generateHeaderStr(id, [["", "ID"], ["", "Type"], ["", "Status"], ["", "Title"], ["", "Author(s)"], ["", "Section"], ["", "Series"], ["", "Created"], ["", "Updated"]]); } break; case "series": id = "series-table"; header = generateHeaderStr(id, [["", "ID"], ["", "Status"], ["", "Section"], ["", "Title"], ["", "Author(s)"], ["", "Created"], ["", "Updated"]]); break; case "sections": id = "sections-table"; header = generateHeaderStr(id, [["", "ID"], ["", "Title"], ["", "Author(s)"], ["", "Created"], ["", "Updated"]]); break; case "relations": id = "relations-table"; header = generateHeaderStr(id, [["", "ID"], ["", "Article 1"], ["", "Type"], ["", "Article 2"], ["", "Place"], ["", "Author(s)"], ["", "Created"], ["", "Updated"]]); break; case "sources": id = "sources-table"; header = generateHeaderStr(id, [["", "ID"], ["", "Type"], ["", "Author"], ["", "Year"], ["", "Title"]]); break; case "article-edit": id = "modal-references-table"; header = generateHeaderStr(id, [["", "ID"], ["", "Author"], ["", "Title"], ["", "Type"], ["", "Year"]]); break; case "navbar": id = "navbar-table"; header = generateHeaderStr(id, [["", "ID"], ["", "Label"], ["", "Location"], ["", "Type of Ref."], ["", "ID or URL"], ["", "Created"]]); break; case "media": id = "media-table"; header = generateHeaderStr(id, [["", "ID"], ["", "File Ext."], ["", "Alt Text"], ["", "Size (bytes)"], ["", "Created"], ["", "Updated"], ["", thumbnail_column]]); break; case "authors": id = "authors-table"; header = generateHeaderStr(id, [["", "ID"], ["", "Name"], ["", "Email"], ["", "Location"], ["", "Created"], ["", "Updated"]]); break; default: } if (id) { const dataTable = globalThis.document.getElementById(id); const thead = dataTable.getElementsByTagName('thead')[0]; thead.innerHTML = header; if (body) { const tbody = dataTable.getElementsByTagName('tbody')[0]; tbody.innerHTML = body; const tfoot = dataTable.getElementsByTagName('tfoot')[0]; tfoot.innerHTML = tfoot.innerHTML.replace("colspan=\"5\" id=\"tfoot-right-td\"", "colspan=\"4\" id=\"tfoot-right-td\""); } } } const fillDOM = async function(db, id, table, orderBy, ...args){ let [header, columns] = generateHeader(id, args); const dataTable = globalThis.document.getElementById(id); const thead = dataTable.getElementsByTagName('thead')[0]; const tbody = dataTable.getElementsByTagName('tbody')[0]; thead.innerHTML = header; const uriField = table === 'sources' ? 'ris_id' : 'uri'; const completeCols = columns.slice(0); if (!completeCols.includes(uriField)) completeCols.push(uriField); let resultRows = await db.exec({ sql: "SELECT " + completeCols.join(',') + " FROM " + table + (orderBy ? " ORDER BY " + orderBy : ""), rowMode: 'object' }); tbody.innerHTML = ""; for (let i = 0, counter = 0; i < resultRows.length; i++) { let row = resultRows[i]; tbody.innerHTML += generateTrStr(id, table, row, row[uriField], columns, counter++, args); } setCheckboxHandler(); addOrderByIcons(thead, db, id, table, ...args); if (tbody.rows.length === 0) { addEmptyTr(); } setEntriesPerPage(); } const fetchData = function(db){ let page = thisPage(); if (mobileScreen()) { switch (page) { case "index": break; case "articles": fillDOM(db, "articles-table", "articles", "created ASC", ["uri", "ID"], ["title", "Title"]); break; case "series": fillDOM(db, "series-table", "series", "created ASC", ["uri", "ID"], ["title", "Title"]); break; case "sections": fillDOM(db, "sections-table", "sections", "created ASC", ["uri", "ID"], ["title", "Title"]); break; case "relations": fillDOM(db, "relations-table", "relations", "created ASC", ["article1", "Article 1"], ["article2", "Article 2"]); break; case "sources": fillDOM(db, "sources-table", "sources", "", ["IIF(ris_a1 = '', ris_au, ris_a1)", "Author"], ["IIF(ris_t1 = '', IIF(ris_ti = '', ris_st, ris_ti), ris_t1)", "Title"]); break; case "article-edit": fillDOM(db, "modal-references-table", "sources", "", ["IIF(ris_a1 = '', ris_au, ris_a1)", "Author"], ["IIF(ris_t1 = '', IIF(ris_ti = '', ris_st, ris_ti), ris_t1)", "Title"]); break; case "navbar": fillDOM(db, "navbar-table", "navbar_items", "created ASC", ["uri", "ID"], ["label", "Label"]); break; case "media": fillDOM(db, "media-table", "media", "created ASC", ["uri", "ID"], ["''", thumbnail_column]) .then(addThumbnails); break; case "authors": fillDOM(db, "authors-table", "authors", "created ASC", ["uri", "ID"], ["name", "Name"]); break; default: } } else { switch (page) { case "index": break; case "articles": if (smallScreen()) { fillDOM(db, "articles-table", "articles", "created ASC", ["uri", "ID"], ["type", "Type"], ["status", "Status"], ["title", "Title"], ["authors_ids", "Author(s)"], ["section_uri", "Section"], ["series_uri", "Series"], ["created", "Created"]); } else { fillDOM(db, "articles-table", "articles", "created ASC", ["uri", "ID"], ["type", "Type"], ["status", "Status"], ["title", "Title"], ["authors_ids", "Author(s)"], ["section_uri", "Section"], ["series_uri", "Series"], ["created", "Created"], ["updated", "Updated"]); } break; case "series": fillDOM(db, "series-table", "series", "created ASC", ["uri", "ID"], ["status", "Status"], ["section_uri", "Section"], ["title", "Title"], ["authors_ids", "Author(s)"], ["created", "Created"], ["updated", "Updated"]); break; case "sections": fillDOM(db, "sections-table", "sections", "created ASC", ["uri", "ID"], ["title", "Title"], ["authors_ids", "Author(s)"], ["created", "Created"], ["updated", "Updated"]); break; case "relations": fillDOM(db, "relations-table", "relations", "created ASC", ["uri", "ID"], ["article1", "Article 1"], ["type", "Type"], ["article2", "Article 2"], ["place", "Place"], ["authors_ids", "Author(s)"], ["created", "Created"], ["updated", "Updated"]); break; case "sources": fillDOM(db, "sources-table", "sources", "", ["ris_id", "ID"], ["ris_ty", "Type"], ["IIF(ris_a1 = '', ris_au, ris_a1)", "Author"], ["IIF(ris_y1 = '', ris_py, ris_y1)", "Year"], ["IIF(ris_t1 = '', IIF(ris_ti = '', ris_st, ris_ti), ris_t1)", "Title"]); break; case "article-edit": fillDOM(db, "modal-references-table", "sources", "", ["ris_id", "ID"], ["IIF(ris_a1 = '', ris_au, ris_a1)", "Author"], ["IIF(ris_t1 = '', IIF(ris_ti = '', ris_st, ris_ti), ris_t1)", "Title"], ["ris_ty", "Type"], ["IIF(ris_y1 = '', ris_py, ris_y1)", "Year"]); break; case "navbar": fillDOM(db, "navbar-table", "navbar_items", "created ASC", ["uri", "ID"], ["label", "Label"], ["location", "Location"], ["type", "Type of Ref."], ["reference", "ID or URL"], ["created", "Created"]); break; case "media": fillDOM(db, "media-table", "media", "created ASC", ["uri", "ID"], ["file_extension", "File Ext."], ["alt_text", "Alt Text"], ["size", "Size (bytes)"], ["created", "Created"], ["updated", "Updated"], ["''", thumbnail_column]) .then(addThumbnails); break; case "authors": fillDOM(db, "authors-table", "authors", "created ASC", ["uri", "ID"], ["name", "Name"], ["email", "Email"], ["location", "Location"], ["created", "Created"], ["updated", "Updated"]); break; default: } } } const addThumbnails = async function() { if (!globalThis.mediaUrls) { globalThis.mediaUrls = {}; const response = await globalThis.lb.listMediaFiles(); if (response.type !== "error") { response.result.forEach((item) => { globalThis.mediaUrls[item['name']] = item['url']; }); } } const tdsCollection = globalThis.document.getElementsByClassName("thumbnail-td"); const tds = Array.from(tdsCollection); tds.forEach((td) => { td.innerHTML = "<img class='thumbnail-img' src='" + globalThis.mediaUrls[td.attributes['uri'].value] + "' />"; }); } const reloadPage = function() { let href = globalThis.location.href; const hashtagPos = href.indexOf("#"); if (hashtagPos !== -1) href = href.substring(0, hashtagPos); globalThis.location.href = href; } const deleteItems = function(items, sql) { let itemsStr = "'" + items.join("','") + "'"; globalThis.lb.useSqlite(async (db) => { await db.exec({ sql: sql(itemsStr) }); reloadPage(); }); } const deleteArticles = function(items) { deleteItems(items, (a) => "DELETE FROM articles WHERE uri IN (" + a + ");" + "DELETE FROM relations WHERE article1 IN (" + a + ") OR article2 IN (" + a + ");"); } const deleteSeries = function(items){ deleteItems(items, (a) => "DELETE FROM series WHERE uri IN (" + a + ");" + "UPDATE articles SET series_uri='' WHERE series_uri IN (" + a + ");"); } const deleteSections = function(items){ deleteItems(items, (a) => "DELETE FROM sections WHERE uri IN (" + a + ");" + "UPDATE articles SET section_uri='' WHERE section_uri IN (" + a + ");"); } const deleteRelations = function(items){ deleteItems(items, (a) => "DELETE FROM relations WHERE uri IN (" + a + ");"); } const deleteSources = function(items){ deleteItems(items, (a) => "DELETE FROM sources WHERE ris_id IN (" + a + ");"); } const deleteNavbarItems = function(items){ deleteItems(items, (a) => "DELETE FROM navbar_items WHERE uri IN (" + a + ");"); } const deleteMediaFiles = function(items) { let itemsStr = "'" + items.join("','") + "'"; globalThis.lb.useSqlite(async (db) => { await db.exec({ sql: "DELETE FROM media WHERE uri IN (" + itemsStr + ");" }); for (let i = 0; i < items.length; i++) { await deleteMediaFileAndVersions(items[i]); } reloadPage(); }); } const deleteAuthors = function(items){ deleteItems(items, (a) => "DELETE FROM authors WHERE uri IN (" + a + ");"); } const changeStatus = function(table, items, status){ let itemsStr = "'" + items.join("','") + "'"; globalThis.lb.useSqlite(async (db) => { await db.exec({ sql: "UPDATE " + table + " SET status='" + status + "' WHERE uri IN (" + itemsStr + ");" }); reloadPage(); }); } const thisTable = function(){ switch (thisPage()) { case "articles": return "articles"; case "series": return "series"; case "sections": return "sections"; case "relations": return "relations"; case "sources": return "sources"; case "navbar": return "navbar_items"; case "media": return "media"; case "authors": return "authors"; default: return ""; } } const actionFormHandler = function(event) { event.preventDefault(); const selectAction = globalThis.document.getElementById("select-action"); const action = selectAction.value; if (action === "none"){ event.preventDefault(); return; } let selectedItems = []; const checkboxes = getAllVisibleCheckboxes(); for (let checkbox in checkboxes) { if (checkboxes[checkbox].checked) { selectedItems.push(checkboxes[checkbox].value); } } if (selectedItems.length === 0){ event.preventDefault(); return; } if (action === "delete"){ switch (thisPage()) { case "articles": deleteArticles(selectedItems); break; case "series": deleteSeries(selectedItems); break; case "sections": deleteSections(selectedItems); break; case "relations": deleteRelations(selectedItems); break; case "sources": deleteSources(selectedItems); break; case "navbar": deleteNavbarItems(selectedItems); break; case "media": deleteMediaFiles(selectedItems); break; case "authors": deleteAuthors(selectedItems); break; default: } } else if (action === "publish" || action === "unpublish") { changeStatus(thisTable(), selectedItems, action === "publish" ? "Published" : "Unpublished"); } } const strFromSelectValues = function(select) { let result = ""; for (let i = 0, len = select.options.length; i < len; i++) { const option = select.options[i]; if (option.selected) { result += option.value + ","; } } if (result.length > 0) return result.slice(0, -1); return result; } const insertOrUpdateRecord = async function(db, table, columns, fieldValues) { const columnsStr = columns.join(", "); //Using UPSERT in order not to update the field 'created' let sqlStr = "INSERT INTO " + table + "(" + columnsStr + ", created, updated) VALUES " + "(" + "?, ".repeat(fieldValues.length) + "STRFTIME('%Y-%m-%d %H:%M', DATETIME('now','localtime')), " + "STRFTIME('%Y-%m-%d %H:%M', DATETIME('now','localtime'))) ON CONFLICT(uri) DO UPDATE SET " + columns.join("=?, ") + "=?, updated=STRFTIME('%Y-%m-%d %H:%M', DATETIME('now','localtime'));"; await db.exec({ sql: sqlStr, bind: [...fieldValues, ...fieldValues] }, {if_error_do: (e) => globalThis.window.alert("Error: " + e)}); } const formHandler = function(table, fields){ const fieldValues = []; let columns = []; let uri = ""; for (let i = 0; i < fields.length; i++) { columns[i] = fields[i][1]; switch (fields[i][2]) { case "select": const select = globalThis.document.getElementById(fields[i][0]); fieldValues[i] = select.options[select.selectedIndex].value; break; case "fix-datetime-format": let value = globalThis.document.getElementById(fields[i][0]).value; if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(value)) value.replace("T", " "); fieldValues[i] = value; break; case "checkbox": fieldValues[i] = globalThis.document.getElementById(fields[i][0]).checked ? 1 : 0; break; case "select-multiple": fieldValues[i] = strFromSelectValues(globalThis.document.getElementById(fields[i][0])); break; default: fieldValues[i] = globalThis.document.getElementById(fields[i][0]).value; } if (fields[i][1] === 'uri') uri = fieldValues[i]; } const textarea = globalThis.document.getElementById("editor"); if (textarea) { const contents = globalThis.thisEditor.getValue(); fieldValues.push(contents); columns.push("contents"); } globalThis.lb.useSqlite(async (db) => { await insertOrUpdateRecord(db, table, columns, fieldValues); if (table === "relations") { uri = await getRelationURI(fields, fieldValues, db); } else if (table === "media") { const versions = getVersions(columns, fieldValues); await saveFileInMediaFolder(versions); } if (findParameter(globalThis.location.href, "uri") === uri || table === "navbar_items" || table === "templates") { reloadPage(); } else { addURItoURL(uri); } }); } const getVersions = function(columns, fieldValues) { let versions = []; for (let i = 0; i < columns.length; i++) { if (columns[i] === 'versions') { versions = fieldValues[i].split(","); } } return versions; } const addURItoURL = function(uri) { globalThis.location.href = globalThis.location.href.split("?uri=")[0] + "?uri=" + uri; } const getRelationURI = async function(fields, fieldValues, db) { let article1, article2, type = ""; for (let i = 0; i < fields.length; i++) { if (fields[i][1] === 'article1') { article1 = fieldValues[i]; } else if(fields[i][1] === 'article2') { article2 = fieldValues[i]; } else if(fields[i][1] === 'type') { type = fieldValues[i]; } } let resultRows = await db.exec({ sql: "SELECT uri FROM relations WHERE article1 = '" + article1 + "' AND article2 = '" + article2 + "' AND type = '" + type + "'", rowMode: 'object' }); if (resultRows && resultRows[0]) return resultRows[0]["uri"]; return ""; } const articleFormHandler = function(event) { event.preventDefault(); formHandler("articles", [["uri", "uri"], ["type", "type", "select"], ["section-uri", "section_uri", "select"], ["series-uri", "series_uri", "select"], ["highlight-mainpage", "highlight_mainpage", "checkbox"], ["highlight-section", "highlight_section", "checkbox"], ["in-sitemap", "in_sitemap", "checkbox"], ["title", "title"], ["notes", "notes"], ["summary", "summary"], ["geolocation", "geolocation"], ["authors", "authors_ids", "select-multiple"], ["subtitle", "subtitle"], ["label", "label"], ["keywords", "keywords"], ["photo", "photo"], ["photo-info", "photo_info"], ["language", "language"], ["enable-comments", "enable_comments", "checkbox"], ["video", "video"], ["video-info", "video_info"], ["audio", "audio"], ["audio-info", "audio_info"], ["editorial-notes", "editorial_notes"], ["published", "published", "fix-datetime-format"], ["status", "status", "select"]]); } const seriesFormHandler = function(event) { event.preventDefault(); formHandler("series", [["uri", "uri"], ["type", "type", "select"], ["highlight-mainpage", "highlight_mainpage", "checkbox"], ["highlight-section", "highlight_section", "checkbox"], ["in-sitemap", "in_sitemap", "checkbox"], ["title", "title"], ["authors", "authors_ids", "select-multiple"], ["subtitle", "subtitle"], ["photo", "photo"], ["photo-info", "photo_info"], ["keywords", "keywords"], ["published", "published", "fix-datetime-format"], ["status", "status", "select"], ["section-uri", "section_uri", "select"]]); } const sectionFormHandler = function(event) { event.preventDefault(); formHandler("sections", [["uri", "uri"], ["type", "type", "select"], ["title", "title"], ["description", "description"], ["keywords", "keywords"], ["authors", "authors_ids", "select-multiple"]]); } const relationFormHandler = function(event) { event.preventDefault(); const uriInput = globalThis.document.getElementById("uri"); if (!uriInput.value) uriInput.value = globalThis.lb.generateRandomId(); formHandler("relations", [["uri", "uri"], ["article1", "article1", "select"], ["article2", "article2", "select"], ["relation-type", "type", "select"], ["place", "place", "fix-datetime-format"], ["title", "title"], ["photo", "photo"], ["photo-info", "photo_info"], ["authors", "authors_ids", "select-multiple"]]); } const authorFormHandler = function(event) { event.preventDefault(); formHandler("authors", [["uri", "uri"], ["type", "type", "select"], ["name", "name"], ["contact", "contact"], ["location", "l