cc/index.html
2023-07-14 02:10:38 +05:30

5426 lines
326 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Content Collaboration</title>
<script src="scripts/components.js" defer></script>
<link rel="stylesheet" href="css/main.min.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Calistoga&family=Roboto:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap"
rel="stylesheet">
<script src="scripts/purify.min.js" defer></script>
<script src="https://unpkg.com/uhtml@3.0.1/es.js"></script>
<script src="https://cdn.jsdelivr.net/npm/fuse.js@6.4.6" defer></script>
<script id="floGlobals">
/* Constants for FLO blockchain operations !!Make sure to add this at begining!! */
const floGlobals = {
blockchain: "FLO",
adminID: "FC18nZVcHifauCsRMMeH4gkGxexuhSMFgs",
application: "CC",
}
</script>
</head>
<body class="hidden" onload="onLoadStartUp()">
<sm-notifications id="notification_drawer"></sm-notifications>
<sm-popup id="confirmation_popup">
<h4 id="confirm_title"></h4>
<p id="confirm_message"></p>
<div class="flex align-center">
<sm-button variant="no-outline" class="cancel-btn">Cancel</sm-button>
<sm-button variant="no-outline" class="submit-btn">OK</sm-button>
</div>
</sm-popup>
<article id="landing" class="page page-layout hidden">
<header class="flex space-between">
<div class="logo">
<svg class="main-logo" viewBox="0 0 27.25 32">
<title>RanchiMall</title>
<path
d="M27.14,30.86c-.74-2.48-3-4.36-8.25-6.94a20,20,0,0,1-4.2-2.49,6,6,0,0,1-1.25-1.67,4,4,0,0,1,0-2.26c.37-1.08.79-1.57,3.89-4.55a11.66,11.66,0,0,0,3.34-4.67,6.54,6.54,0,0,0,.05-2.82C20,3.6,18.58,2,16.16.49c-.89-.56-1.29-.64-1.3-.24a3,3,0,0,1-.3.72l-.3.55L13.42.94C13,.62,12.4.26,12.19.15c-.4-.2-.73-.18-.72.05a9.39,9.39,0,0,1-.61,1.33s-.14,0-.27-.13C8.76.09,8-.27,8,.23A11.73,11.73,0,0,1,6.76,2.6C4.81,5.87,2.83,7.49.77,7.49c-.89,0-.88,0-.61,1,.22.85.33.92,1.09.69A5.29,5.29,0,0,0,3,8.33c.23-.17.45-.29.49-.26a2,2,0,0,1,.22.63A1.31,1.31,0,0,0,4,9.34a5.62,5.62,0,0,0,2.27-.87L7,8l.13.55c.19.74.32.82,1,.65a7.06,7.06,0,0,0,3.46-2.47l.6-.71-.06.64c-.17,1.63-1.3,3.42-3.39,5.42L6.73,14c-3.21,3.06-3,5.59.6,8a46.77,46.77,0,0,0,4.6,2.41c.28.13,1,.52,1.59.87,3.31,2,4.95,3.92,4.95,5.93a2.49,2.49,0,0,0,.07.77h0c.09.09,0,.1.9-.14a2.61,2.61,0,0,0,.83-.32,3.69,3.69,0,0,0-.55-1.83A11.14,11.14,0,0,0,17,26.81a35.7,35.7,0,0,0-5.1-2.91C9.37,22.64,8.38,22,7.52,21.17a3.53,3.53,0,0,1-1.18-2.48c0-1.38.71-2.58,2.5-4.23,2.84-2.6,3.92-3.91,4.67-5.65a3.64,3.64,0,0,0,.42-2A3.37,3.37,0,0,0,13.61,5l-.32-.74.29-.48c.17-.27.37-.63.46-.8l.15-.3.44.64a5.92,5.92,0,0,1,1,2.81,5.86,5.86,0,0,1-.42,1.94c0,.12-.12.3-.15.4a9.49,9.49,0,0,1-.67,1.1,28,28,0,0,1-4,4.29C8.62,15.49,8.05,16.44,8,17.78a3.28,3.28,0,0,0,1.11,2.76c.95,1,2.07,1.74,5.25,3.32,3.64,1.82,5.22,2.9,6.41,4.38A4.78,4.78,0,0,1,21.94,31a3.21,3.21,0,0,0,.14.92,1.06,1.06,0,0,0,.43-.05l.83-.22.46-.12-.06-.46c-.21-1.53-1.62-3.25-3.94-4.8a37.57,37.57,0,0,0-5.22-2.82A13.36,13.36,0,0,1,11,21.19a3.36,3.36,0,0,1-.8-4.19c.41-.85.83-1.31,3.77-4.15,2.39-2.31,3.43-4.13,3.43-6a5.85,5.85,0,0,0-2.08-4.29c-.23-.21-.44-.43-.65-.65A2.5,2.5,0,0,1,15.27.69a10.6,10.6,0,0,1,2.91,2.78A4.16,4.16,0,0,1,19,6.16a4.91,4.91,0,0,1-.87,3c-.71,1.22-1.26,1.82-4.27,4.67a9.47,9.47,0,0,0-2.07,2.6,2.76,2.76,0,0,0-.33,1.54,2.76,2.76,0,0,0,.29,1.47c.57,1.21,2.23,2.55,4.65,3.73a32.41,32.41,0,0,1,5.82,3.24c2.16,1.6,3.2,3.16,3.2,4.8a1.94,1.94,0,0,0,.09.76,4.54,4.54,0,0,0,1.66-.4C27.29,31.42,27.29,31.37,27.14,30.86ZM6.1,7h0a3.77,3.77,0,0,1-1.46.45L4,7.51l.68-.83a25.09,25.09,0,0,0,3-4.82A12,12,0,0,1,8.28.76c.11-.12.77.32,1.53,1l.63.58-.57.84A10.34,10.34,0,0,1,6.1,7Zm5.71-1.78A9.77,9.77,0,0,1,9.24,7.18h0a5.25,5.25,0,0,1-1.17.28l-.58,0,.65-.78a21.29,21.29,0,0,0,2.1-3.12c.22-.41.42-.76.44-.79s.5.43.9,1.24L12,5ZM13.41,3a2.84,2.84,0,0,1-.45.64,11,11,0,0,1-.9-.91l-.84-.9.19-.45c.34-.79.39-.8,1-.31A9.4,9.4,0,0,1,13.8,2.33q-.18.34-.39.69Z" />
</svg>
<div class="grid">
<h4>RanchiMall CC</h4>
</div>
</div>
<div class="flex gap-0-5">
<a href="#/sign_up" class="button">Sign up</a>
<a href="#/sign_in" class="button button--primary">Sign in</a>
</div>
</header>
<section class="grid justify-center">
<div class="grid gap-0-5">
<h1 class="h1">Create. Collaborate. <br>Publish.</h1>
<p>Write something great... <strong>Together</strong>.</p>
</div>
</section>
</article>
<article id="sign_in" class="page page-layout hidden">
<header>
<div class="logo">
<svg class="main-logo" viewBox="0 0 27.25 32">
<title>RanchiMall</title>
<path
d="M27.14,30.86c-.74-2.48-3-4.36-8.25-6.94a20,20,0,0,1-4.2-2.49,6,6,0,0,1-1.25-1.67,4,4,0,0,1,0-2.26c.37-1.08.79-1.57,3.89-4.55a11.66,11.66,0,0,0,3.34-4.67,6.54,6.54,0,0,0,.05-2.82C20,3.6,18.58,2,16.16.49c-.89-.56-1.29-.64-1.3-.24a3,3,0,0,1-.3.72l-.3.55L13.42.94C13,.62,12.4.26,12.19.15c-.4-.2-.73-.18-.72.05a9.39,9.39,0,0,1-.61,1.33s-.14,0-.27-.13C8.76.09,8-.27,8,.23A11.73,11.73,0,0,1,6.76,2.6C4.81,5.87,2.83,7.49.77,7.49c-.89,0-.88,0-.61,1,.22.85.33.92,1.09.69A5.29,5.29,0,0,0,3,8.33c.23-.17.45-.29.49-.26a2,2,0,0,1,.22.63A1.31,1.31,0,0,0,4,9.34a5.62,5.62,0,0,0,2.27-.87L7,8l.13.55c.19.74.32.82,1,.65a7.06,7.06,0,0,0,3.46-2.47l.6-.71-.06.64c-.17,1.63-1.3,3.42-3.39,5.42L6.73,14c-3.21,3.06-3,5.59.6,8a46.77,46.77,0,0,0,4.6,2.41c.28.13,1,.52,1.59.87,3.31,2,4.95,3.92,4.95,5.93a2.49,2.49,0,0,0,.07.77h0c.09.09,0,.1.9-.14a2.61,2.61,0,0,0,.83-.32,3.69,3.69,0,0,0-.55-1.83A11.14,11.14,0,0,0,17,26.81a35.7,35.7,0,0,0-5.1-2.91C9.37,22.64,8.38,22,7.52,21.17a3.53,3.53,0,0,1-1.18-2.48c0-1.38.71-2.58,2.5-4.23,2.84-2.6,3.92-3.91,4.67-5.65a3.64,3.64,0,0,0,.42-2A3.37,3.37,0,0,0,13.61,5l-.32-.74.29-.48c.17-.27.37-.63.46-.8l.15-.3.44.64a5.92,5.92,0,0,1,1,2.81,5.86,5.86,0,0,1-.42,1.94c0,.12-.12.3-.15.4a9.49,9.49,0,0,1-.67,1.1,28,28,0,0,1-4,4.29C8.62,15.49,8.05,16.44,8,17.78a3.28,3.28,0,0,0,1.11,2.76c.95,1,2.07,1.74,5.25,3.32,3.64,1.82,5.22,2.9,6.41,4.38A4.78,4.78,0,0,1,21.94,31a3.21,3.21,0,0,0,.14.92,1.06,1.06,0,0,0,.43-.05l.83-.22.46-.12-.06-.46c-.21-1.53-1.62-3.25-3.94-4.8a37.57,37.57,0,0,0-5.22-2.82A13.36,13.36,0,0,1,11,21.19a3.36,3.36,0,0,1-.8-4.19c.41-.85.83-1.31,3.77-4.15,2.39-2.31,3.43-4.13,3.43-6a5.85,5.85,0,0,0-2.08-4.29c-.23-.21-.44-.43-.65-.65A2.5,2.5,0,0,1,15.27.69a10.6,10.6,0,0,1,2.91,2.78A4.16,4.16,0,0,1,19,6.16a4.91,4.91,0,0,1-.87,3c-.71,1.22-1.26,1.82-4.27,4.67a9.47,9.47,0,0,0-2.07,2.6,2.76,2.76,0,0,0-.33,1.54,2.76,2.76,0,0,0,.29,1.47c.57,1.21,2.23,2.55,4.65,3.73a32.41,32.41,0,0,1,5.82,3.24c2.16,1.6,3.2,3.16,3.2,4.8a1.94,1.94,0,0,0,.09.76,4.54,4.54,0,0,0,1.66-.4C27.29,31.42,27.29,31.37,27.14,30.86ZM6.1,7h0a3.77,3.77,0,0,1-1.46.45L4,7.51l.68-.83a25.09,25.09,0,0,0,3-4.82A12,12,0,0,1,8.28.76c.11-.12.77.32,1.53,1l.63.58-.57.84A10.34,10.34,0,0,1,6.1,7Zm5.71-1.78A9.77,9.77,0,0,1,9.24,7.18h0a5.25,5.25,0,0,1-1.17.28l-.58,0,.65-.78a21.29,21.29,0,0,0,2.1-3.12c.22-.41.42-.76.44-.79s.5.43.9,1.24L12,5ZM13.41,3a2.84,2.84,0,0,1-.45.64,11,11,0,0,1-.9-.91l-.84-.9.19-.45c.34-.79.39-.8,1-.31A9.4,9.4,0,0,1,13.8,2.33q-.18.34-.39.69Z" />
</svg>
<div class="grid">
<h4>RanchiMall CC</h4>
</div>
</div>
</header>
<section>
<h1 class="h2">Sign In</h1>
<p>Welcome back, glad to see you again</p>
<sm-form>
<sm-input id="private_key_field" type="password" placeholder="FLO/BTC private key"
error-text="Private key is invalid" data-private-key required></sm-input>
<sm-button id="sign_in_button" variant="primary" disabled>Sign In</sm-button>
</sm-form>
<p>
New here? <a href="#/sign_up">get your FLO login credentials</a>
</p>
</section>
</article>
<article id="sign_up" class="page page-layout hidden">
<header>
<div class="logo">
<svg class="main-logo" viewBox="0 0 27.25 32">
<title>RanchiMall</title>
<path
d="M27.14,30.86c-.74-2.48-3-4.36-8.25-6.94a20,20,0,0,1-4.2-2.49,6,6,0,0,1-1.25-1.67,4,4,0,0,1,0-2.26c.37-1.08.79-1.57,3.89-4.55a11.66,11.66,0,0,0,3.34-4.67,6.54,6.54,0,0,0,.05-2.82C20,3.6,18.58,2,16.16.49c-.89-.56-1.29-.64-1.3-.24a3,3,0,0,1-.3.72l-.3.55L13.42.94C13,.62,12.4.26,12.19.15c-.4-.2-.73-.18-.72.05a9.39,9.39,0,0,1-.61,1.33s-.14,0-.27-.13C8.76.09,8-.27,8,.23A11.73,11.73,0,0,1,6.76,2.6C4.81,5.87,2.83,7.49.77,7.49c-.89,0-.88,0-.61,1,.22.85.33.92,1.09.69A5.29,5.29,0,0,0,3,8.33c.23-.17.45-.29.49-.26a2,2,0,0,1,.22.63A1.31,1.31,0,0,0,4,9.34a5.62,5.62,0,0,0,2.27-.87L7,8l.13.55c.19.74.32.82,1,.65a7.06,7.06,0,0,0,3.46-2.47l.6-.71-.06.64c-.17,1.63-1.3,3.42-3.39,5.42L6.73,14c-3.21,3.06-3,5.59.6,8a46.77,46.77,0,0,0,4.6,2.41c.28.13,1,.52,1.59.87,3.31,2,4.95,3.92,4.95,5.93a2.49,2.49,0,0,0,.07.77h0c.09.09,0,.1.9-.14a2.61,2.61,0,0,0,.83-.32,3.69,3.69,0,0,0-.55-1.83A11.14,11.14,0,0,0,17,26.81a35.7,35.7,0,0,0-5.1-2.91C9.37,22.64,8.38,22,7.52,21.17a3.53,3.53,0,0,1-1.18-2.48c0-1.38.71-2.58,2.5-4.23,2.84-2.6,3.92-3.91,4.67-5.65a3.64,3.64,0,0,0,.42-2A3.37,3.37,0,0,0,13.61,5l-.32-.74.29-.48c.17-.27.37-.63.46-.8l.15-.3.44.64a5.92,5.92,0,0,1,1,2.81,5.86,5.86,0,0,1-.42,1.94c0,.12-.12.3-.15.4a9.49,9.49,0,0,1-.67,1.1,28,28,0,0,1-4,4.29C8.62,15.49,8.05,16.44,8,17.78a3.28,3.28,0,0,0,1.11,2.76c.95,1,2.07,1.74,5.25,3.32,3.64,1.82,5.22,2.9,6.41,4.38A4.78,4.78,0,0,1,21.94,31a3.21,3.21,0,0,0,.14.92,1.06,1.06,0,0,0,.43-.05l.83-.22.46-.12-.06-.46c-.21-1.53-1.62-3.25-3.94-4.8a37.57,37.57,0,0,0-5.22-2.82A13.36,13.36,0,0,1,11,21.19a3.36,3.36,0,0,1-.8-4.19c.41-.85.83-1.31,3.77-4.15,2.39-2.31,3.43-4.13,3.43-6a5.85,5.85,0,0,0-2.08-4.29c-.23-.21-.44-.43-.65-.65A2.5,2.5,0,0,1,15.27.69a10.6,10.6,0,0,1,2.91,2.78A4.16,4.16,0,0,1,19,6.16a4.91,4.91,0,0,1-.87,3c-.71,1.22-1.26,1.82-4.27,4.67a9.47,9.47,0,0,0-2.07,2.6,2.76,2.76,0,0,0-.33,1.54,2.76,2.76,0,0,0,.29,1.47c.57,1.21,2.23,2.55,4.65,3.73a32.41,32.41,0,0,1,5.82,3.24c2.16,1.6,3.2,3.16,3.2,4.8a1.94,1.94,0,0,0,.09.76,4.54,4.54,0,0,0,1.66-.4C27.29,31.42,27.29,31.37,27.14,30.86ZM6.1,7h0a3.77,3.77,0,0,1-1.46.45L4,7.51l.68-.83a25.09,25.09,0,0,0,3-4.82A12,12,0,0,1,8.28.76c.11-.12.77.32,1.53,1l.63.58-.57.84A10.34,10.34,0,0,1,6.1,7Zm5.71-1.78A9.77,9.77,0,0,1,9.24,7.18h0a5.25,5.25,0,0,1-1.17.28l-.58,0,.65-.78a21.29,21.29,0,0,0,2.1-3.12c.22-.41.42-.76.44-.79s.5.43.9,1.24L12,5ZM13.41,3a2.84,2.84,0,0,1-.45.64,11,11,0,0,1-.9-.91l-.84-.9.19-.45c.34-.79.39-.8,1-.31A9.4,9.4,0,0,1,13.8,2.33q-.18.34-.39.69Z" />
</svg>
<div class="grid">
<h4>RanchiMall CC</h4>
</div>
</div>
</header>
<keys-generator id="keys_generator"></keys-generator>
</article>
<article id="loading" class="page page-layout">
<sm-spinner></sm-spinner>
<h4>Loading RanchiMall CC</h4>
</article>
<article id="main_page" class="grid page hidden">
<header id="main_header">
<div id="article_name_wrapper" class="flex gap-0-5 align-center">
<button class="icon-only" title="Show all articles list" onclick="openPopup('article_list_popup')">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M4 8h4V4H4v4zm6 12h4v-4h-4v4zm-6 0h4v-4H4v4zm0-6h4v-4H4v4zm6 0h4v-4h-4v4zm6-10v4h4V4h-4zm-6 4h4V4h-4v4zm6 6h4v-4h-4v4zm0 6h4v-4h-4v4z" />
</svg>
</button>
<h4 id="current_article_title"></h4>
<button id="article_outline_button" class="icon-only" title="View article plot"
onclick="openPopup('plot_popup')">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px"
viewBox="0 0 24 24" width="24px" fill="#000000">
<g>
<rect fill="none" height="24" width="24" />
</g>
<g>
<path d="M9,18h12v-2H9V18z M3,6v2h18V6H3z M9,13h12v-2H9V13z" />
</g>
</svg>
</button>
<button id="filter_button" class="icon-only" title="Show filters" onclick="toggleOptionsPanel()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z" />
</svg>
</button>
<button class="icon-only" title="Refresh article" onclick="refreshArticle()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" />
</svg>
</button>
<sm-menu align-options="right" title="Admin options" class="admin-option">
<menu-option onclick="openPopup('create_article_popup')">
<svg class="icon margin-right-0-5" xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
</svg>
Create new article
</menu-option>
<menu-option onclick="openPopup('edit_sections_popup')">
<svg class="icon margin-right-0-5" title="edit" xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M0 0h24v24H0z" fill="none"></path>
<path
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z">
</path>
</svg>
Edit title & sections
</menu-option>
<menu-option id="mark_as_wip" class="hidden" onclick="markAsWIP()">
<svg class="icon margin-right-0-5" xmlns="http://www.w3.org/2000/svg"
enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<rect fill="none" height="24" width="24" />
<path
d="M22,12c0,5.52-4.48,10-10,10S2,17.52,2,12c0-2.76,1.12-5.26,2.93-7.07L12,12V2C17.52,2,22,6.48,22,12z" />
</svg>
Mark as WIP
</menu-option>
<menu-option id="mark_as_done" class="hidden" onclick="markAsDone()">
<svg class="icon margin-right-0-5" xmlns="http://www.w3.org/2000/svg"
enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<g>
<path d="M0,0h24v24H0V0z" fill="none" />
</g>
<g>
<path
d="M14,2H6C4.9,2,4.01,2.9,4.01,4L4,20c0,1.1,0.89,2,1.99,2H18c1.1,0,2-0.9,2-2V8L14,2z M18,20H6V4h7v5h5V20z M8.82,13.05 L7.4,14.46L10.94,18l5.66-5.66l-1.41-1.41l-4.24,4.24L8.82,13.05z" />
</g>
</svg>
Mark as Done
</menu-option>
</sm-menu>
</div>
<div id="selected_content_options" class="flex space-between hidden">
<button onclick="clearSelection()" title="Clear selection">
<svg class="icon margin-right-0-5" xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
</svg>
<div id="selected_entries_no"></div>
</button>
<div class="flex gap-0-5">
<button class="button icon-only" title="Select all" onclick="selectAll()">
<svg class="icon margin-right-0-5" xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M3 5h2V3c-1.1 0-2 .9-2 2zm0 8h2v-2H3v2zm4 8h2v-2H7v2zM3 9h2V7H3v2zm10-6h-2v2h2V3zm6 0v2h2c0-1.1-.9-2-2-2zM5 21v-2H3c0 1.1.9 2 2 2zm-2-4h2v-2H3v2zM9 3H7v2h2V3zm2 18h2v-2h-2v2zm8-8h2v-2h-2v2zm0 8c1.1 0 2-.9 2-2h-2v2zm0-12h2V7h-2v2zm0 8h2v-2h-2v2zm-4 4h2v-2h-2v2zm0-16h2V3h-2v2zM7 17h10V7H7v10zm2-8h6v6H9V9z" />
</svg>
Select all
</button>
<button class="button" onclick="renderPreview()">
<svg class="icon margin-right-0-5" xmlns="http://www.w3.org/2000/svg"
enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<g>
<rect fill="none" height="24" width="24" />
<path
d="M19,3H5C3.89,3,3,3.9,3,5v14c0,1.1,0.89,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.11,3,19,3z M19,19H5V7h14V19z M12,10.5 c1.84,0,3.48,0.96,4.34,2.5c-0.86,1.54-2.5,2.5-4.34,2.5S8.52,14.54,7.66,13C8.52,11.46,10.16,10.5,12,10.5 M12,9 c-2.73,0-5.06,1.66-6,4c0.94,2.34,3.27,4,6,4s5.06-1.66,6-4C17.06,10.66,14.73,9,12,9L12,9z M12,14.5c-0.83,0-1.5-0.67-1.5-1.5 s0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5S12.83,14.5,12,14.5z" />
</g>
</svg>
Preview
</button>
</div>
</div>
<button id="user_popup_button" onclick="openPopup('user_popup')" title="Click to view user profile"
aria-label="User profile"></button>
<theme-toggle></theme-toggle>
</header>
<section id="options_panel" class="flex hidden">
<div id="filter_panel" class="flex w-100 align-center gap-1 ">
<h5>Sort by</h5>
<sm-chips id="sort_content_list" label="Sort by:">
<sm-chip value="score" selected>Score</sm-chip>
<sm-chip value="time">Most recent</sm-chip>
</sm-chips>
</div>
</section>
<div id="text_toolbar" class="hidden">
<div id="formatting_options">
<button id="strong_button" title="Bold (ctrl+b)" class="formatting-button" onclick="formatDoc('bold')">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z" />
</svg>
</button>
<button id="em_button" title="Italic (ctrl+i)" class="formatting-button" onclick="formatDoc('italic');">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4h-8z" />
</svg>
</button>
<button id="u_button" title="Underline (ctrl+u)" class="formatting-button"
onclick="formatDoc('underline');">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z" />
</svg>
</button>
<button id="sup_button" class="formatting-button" title="Superscript"
onclick="formatDoc('superscript')">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px"
viewBox="0 0 24 24" width="24px" fill="#000000">
<g>
<rect fill="none" height="24" width="24" x="0" y="0" />
<path
d="M22,7h-2v1h3v1h-4V7c0-0.55,0.45-1,1-1h2V5h-3V4h3c0.55,0,1,0.45,1,1v1C23,6.55,22.55,7,22,7z M5.88,20h2.66l3.4-5.42h0.12 l3.4,5.42h2.66l-4.65-7.27L17.81,6h-2.68l-3.07,4.99h-0.12L8.85,6H6.19l4.32,6.73L5.88,20z" />
</g>
</svg>
</button>
<button id="sub_button" class="formatting-button" title="Subscript" onclick="formatDoc('subscript')">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px"
viewBox="0 0 24 24" width="24px" fill="#000000">
<g>
<rect fill="none" height="24" width="24" />
<path
d="M22,18h-2v1h3v1h-4v-2c0-0.55,0.45-1,1-1h2v-1h-3v-1h3c0.55,0,1,0.45,1,1v1C23,17.55,22.55,18,22,18z M5.88,18h2.66 l3.4-5.42h0.12l3.4,5.42h2.66l-4.65-7.27L17.81,4h-2.68l-3.07,4.99h-0.12L8.85,4H6.19l4.32,6.73L5.88,18z" />
</g>
</svg>
</button>
<button id="a_button" class="formatting-button" title="Create link" onclick="toggleLinkPanel(this)">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M17 7h-4v2h4c1.65 0 3 1.35 3 3s-1.35 3-3 3h-4v2h4c2.76 0 5-2.24 5-5s-2.24-5-5-5zm-6 8H7c-1.65 0-3-1.35-3-3s1.35-3 3-3h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-2zm-3-4h8v2H8z" />
</svg>
</button>
</div>
<div id="link_panel" class="hidden">
<sm-form style="--gap: 0.5rem">
<button class="icon-only justify-self-start" onclick="toggleLinkPanel()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
</svg>
</button>
<sm-input id="create_link_href" placeholder="Link address" required hiderequired></sm-input>
<sm-button variant="primary" onclick="createLink()">Add link</sm-button>
</sm-form>
</div>
</div>
<div id="article_wrapper"></div>
<aside id="version_history_panel" class="flex flex-direction-column hidden">
<div class="flex align-center space-between">
<div class="flex align-center">
<svg class="icon margin-right-0-5" xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.25 2.52.77-1.28-3.52-2.09V8z" />
</svg>
<h4>Version history</h4>
</div>
<button onclick="hideVersionHistory()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
</svg>
</button>
</div>
<ul id="version_timeline" class="flex flex-direction-column gap-1-5"></ul>
</aside>
</article>
<article id="preview_page" class="grid page hidden">
<header class="grid gap-1 full-bleed">
<div class="flex align-center space-between">
<div class="flex gap-0-5">
<button onclick="goHome()">
<svg class="icon margin-right-0-5" xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
</svg>
Preview
</button>
</div>
<div id="preview_options" class="flex align-center gap-0-5"></div>
</div>
<h1 id="preview__title" class="article__title"></h1>
</header>
<main id="preview__body"></main>
</article>
<sm-popup id="article_list_popup">
<header slot="header" class="popup__header">
<div class="flex align-center">
<button class="popup__header__close" onclick="closePopup()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
</svg>
</button>
<h3>All articles</h3>
</div>
<sm-input id="article_list_search" placeholder="Search articles" type="search" autofocus>
<svg slot="icon" class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000">
<path d="M0 0h24v24H0z" fill="none" />
<path
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" />
</svg>
</sm-input>
<sm-select id="sort_article_list" label="Sort by:">
<sm-option value="time">Most recent</sm-option>
<sm-option value="az">A-Z</sm-option>
</sm-select>
</header>
<div id="article_list" class="observe-empty-state grid"></div>
<h4 class="empty-state">
No related article
</h4>
</sm-popup>
<sm-popup id="create_article_popup">
<header slot="header" class="popup__header">
<button class="popup__header__close" onclick="closePopup()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
</svg>
</button>
<h3>Create article</h3>
</header>
<sm-form>
<sm-input id="get_article_title" placeholder="Article title" required autofocus></sm-input>
<div class="grid gap-1">
<div class="grid gap-0-5">
<h4>Define plot (optional)</h4>
<p>Create and name sections by writing section title encapsulated in '()' and each separated by a
'->'.
</p>
</div>
<sm-textarea id="get_plot" rows="8"
placeholder="(Name, place, time, context) -> (Name, place, time, context) ...">
</sm-textarea>
</div>
<!-- <div class="grid gap-1">
<div class="grid gap-0-5">
<sm-switch>
<h4 slot="left">
Make private
</h4>
</sm-switch>
<p>Define the FLO IDs separated by a comma (,), which are allowed to contribute.</p>
</div>
<sm-textarea id="get_plot" rows="4"></sm-textarea>
</div> -->
<sm-button variant="primary" onclick="cc.createNewArticle()">Create</sm-button>
</sm-form>
</sm-popup>
<sm-popup id="edit_sections_popup">
<header slot="header" class="popup__header">
<button class="popup__header__close" onclick="closePopup()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
</svg>
</button>
<h3>Edit title & sections</h3>
<div class="multi-state-button">
<button id="save_section_changes" class="button button--primary"
onclick="saveSectionEdit()">Save</button>
</div>
</header>
<section class="grid gap-1-5">
<div class="grid gap-0-5">
<h5 class="label">Title</h5>
<sm-input id="edit_article_title"></sm-input>
</div>
<div class="grid gap-0-5">
<h5 class="label">Sections</h5>
<div id="section_list_container" class="observe-empty-state grid"></div>
<p class="empty-state">
There are no sections so far, you can add section with button below.
</p>
</div>
<button id="insert_section_button" class="button" onclick="insertEmptySection()">
<svg class="icon margin-right-0-5" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24"
height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
<g>
<rect fill="none" height="24" width="24" />
</g>
<g>
<g />
<g>
<path
d="M17,19.22H5V7h7V5H5C3.9,5,3,5.9,3,7v12c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-7h-2V19.22z" />
<path d="M19,2h-2v3h-3c0.01,0.01,0,2,0,2h3v2.99c0.01,0.01,2,0,2,0V7h3V5h-3V2z" />
<rect height="2" width="8" x="7" y="9" />
<polygon points="7,12 7,14 15,14 15,12 12,12" />
<rect height="2" width="8" x="7" y="15" />
</g>
</g>
</svg>
Insert section
</button>
</section>
</sm-popup>
<sm-popup id="plot_popup">
<header slot="header" class="popup__header">
<div class="flex align-center">
<button class="popup__header__close" onclick="closePopup()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
</svg>
</button>
<h3>plot</h3>
</div>
</header>
<div class="grid gap-2">
<details id="plot_wrapper" class="hidden" open>
<summary class="interact">
<b style="font-size: 0.9rem;">See plot diagram</b>
<svg class="icon down-arrow" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z" />
</svg>
</summary>
<div id="plot_diagram" style="line-height: 1.5;"></div>
</details>
<div class="grid gap-1">
<div class="flex align-center space-between">
<h4>Article sections</h4>
<button id="toggle_focus_mode" class="button hide" onclick="toggleFocusMode()">Turn on focus
mode</button>
</div>
<ul id="article_outline" class="flex flex-direction-column gap-1"></ul>
</div>
</div>
</sm-popup>
<sm-popup id="contributors_popup">
<header slot="header" class="popup__header">
<div class="flex align-center">
<button class="popup__header__close" onclick="closePopup()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
</svg>
</button>
<h3>Contributors</h3>
</div>
</header>
<div id="contributor_list" class="grid gap-1-5"></div>
</sm-popup>
<sm-popup id="share_popup">
<header slot="header" class="popup__header">
<div class="flex align-center">
<button class="popup__header__close" onclick="closePopup()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
</svg>
</button>
<h3>Share article preview</h3>
</div>
</header>
<span class="label">Copy link</span>
<sm-copy id="shared_url" clip-text></sm-copy>
</sm-popup>
<sm-popup id="user_popup">
<header slot="header" class="popup__header">
<div class="flex align-center">
<button class="popup__header__close" onclick="closePopup()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
</svg>
</button>
</div>
</header>
<section class="grid gap-1-5">
<div class="grid gap-0-5">
<h4>
BTC integrated with FLO
</h4>
<p>
You can use your FLO private key to perform transactions on the BTC network within our app
ecosystem. The private key is the same for both.
</p>
</div>
<div class="grid gap-0-5">
<h5>My FLO address</h5>
<sm-copy id="user_flo_id"></sm-copy>
</div>
<div class="grid gap-0-5">
<h5>My Bitcoin address</h5>
<sm-copy id="user_btc_id"></sm-copy>
</div>
<sm-button class="danger" onclick="signOut()">Sign out</sm-button>
</section>
</sm-popup>
<sm-popup id="scoring_popup">
<header slot="header" class="popup__header">
<button class="popup__header__close" onclick="closePopup()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
</svg>
</button>
<h3>Update score</h3>
</header>
<sm-form id="update_score_form">
<sm-input id="update_score_field" class="outlined" placeholder="Score" type="number" step="0.1" min="0"
max="100" error-text="Value must be between 1-100" autofocus animate required hiderequired></sm-input>
<div id="inc_section" class="flex">
<button class="button" onclick="incScore(0.5)">+ 0.5</button>
<button class="button" onclick="incScore(1)">+ 1</button>
<button class="button" onclick="incScore(5)">+ 5</button>
<sm-button id="get_new_score" variant="primary" class="justify-right" onclick="updateScore()" disabled>
Update</sm-button>
</div>
</sm-form>
</sm-popup>
<template id="contributor_template">
<div class="contributor grid">
<svg class="icon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M14.6243 7.99154C13.9395 8.14279 13.4165 8.69672 13.3047 9.38904C13.2248 9.88365 13.3664 10.3773 13.6735 10.7498L9.29125 15.0941C8.93718 15.4451 8.48852 15.6853 8.00011 15.7854L6.12418 16.1699L9.19811 13.1381C9.53438 12.8064 9.53812 12.265 9.20646 11.9287C8.8748 11.5924 8.33333 11.5887 7.99706 11.9203L4.97835 14.8977L5.47976 12.451C5.50451 12.3303 5.55656 12.2168 5.63191 12.1192L6.49083 11.0073C9.58386 7.00317 14.32 4.72705 19.2553 4.71054L16.3569 7.60884L14.6243 7.99154ZM2.79008 17.0559L3.8042 12.1076C3.88132 11.7313 4.0435 11.3776 4.27833 11.0736L5.13724 9.96172C8.89964 5.09105 14.861 2.53305 20.8954 3.07051C21.5971 3.133 22.2997 3.23735 23 3.38478L17.2133 9.1713L14.9932 9.66167L15.4238 9.91837C15.9039 10.2046 15.9849 10.8668 15.588 11.2603L10.4954 16.3088C9.90529 16.8938 9.15752 17.2941 8.34351 17.461L3.88965 18.3738L2.45572 19.788C2.11945 20.1197 1.57798 20.116 1.24632 19.7797C0.91466 19.4434 0.918397 18.9019 1.25467 18.5703L2.79008 17.0559Z" />
</svg>
<div class="contributor__id breakable"></div>
<time class="contributor__time"></time>
</div>
</template>
<template id="section_template">
<section class="grid gap-0-5">
<div class="heading flex align-center">
<h4 class="section-title"></h4>
</div>
<div class="article-section">
<div class="content-card content-card--empty">
<div class="content__area" data-type="origin"
placeholder="Write something new or edit existing content" contenteditable="true"></div>
<button class="submit-entry hidden">Submit</button>
</div>
<!-- <div class="content-card-container"></div> -->
</div>
</section>
</template>
<template id="content_card_template">
<div class="content-card">
<div class="content__header flex align-center space-between">
<Button class="content__contributors flex align-center" title="Contributors">
<svg class="icon" width="24" height="24" style="margin-right: 0.3rem;" viewBox="0 0 24 24"
fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M14.6243 7.99154C13.9395 8.14279 13.4165 8.69672 13.3047 9.38904C13.2248 9.88365 13.3664 10.3773 13.6735 10.7498L9.29125 15.0941C8.93718 15.4451 8.48852 15.6853 8.00011 15.7854L6.12418 16.1699L9.19811 13.1381C9.53438 12.8064 9.53812 12.265 9.20646 11.9287C8.8748 11.5924 8.33333 11.5887 7.99706 11.9203L4.97835 14.8977L5.47976 12.451C5.50451 12.3303 5.55656 12.2168 5.63191 12.1192L6.49083 11.0073C9.58386 7.00317 14.32 4.72705 19.2553 4.71054L16.3569 7.60884L14.6243 7.99154ZM2.79008 17.0559L3.8042 12.1076C3.88132 11.7313 4.0435 11.3776 4.27833 11.0736L5.13724 9.96172C8.89964 5.09105 14.861 2.53305 20.8954 3.07051C21.5971 3.133 22.2997 3.23735 23 3.38478L17.2133 9.1713L14.9932 9.66167L15.4238 9.91837C15.9039 10.2046 15.9849 10.8668 15.588 11.2603L10.4954 16.3088C9.90529 16.8938 9.15752 17.2941 8.34351 17.461L3.88965 18.3738L2.45572 19.788C2.11945 20.1197 1.57798 20.116 1.24632 19.7797C0.91466 19.4434 0.918397 18.9019 1.25467 18.5703L2.79008 17.0559Z" />
</svg>
<div class="content__author flex align-center"></div>
</Button>
<sm-checkbox class="content__checkbox" aria-label="Select content"
title="Select this snippet to download as HTML"></sm-checkbox>
</div>
<div class="content__area"></div>
<div class="flex align-center space-between">
<div class="content__options grid align-center">
<button class="version-history-button" title="See version history">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.25 2.52.77-1.28-3.52-2.09V8z" />
</svg>
</button>
</div>
<button class="submit-entry hidden">Submit</button>
</div>
</div>
</template>
<template id="history_entry_template">
<li class="history-entry grid gap-1">
<div class="flex align-center space-between">
<time class="entry__time"></time>
</div>
<div class="grid">
<div class="label flex align-center">
<svg class="icon" style="margin-right: 0.2rem;" width="24" height="24" viewBox="0 0 24 24"
fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M14.6243 7.99154C13.9395 8.14279 13.4165 8.69672 13.3047 9.38904C13.2248 9.88365 13.3664 10.3773 13.6735 10.7498L9.29125 15.0941C8.93718 15.4451 8.48852 15.6853 8.00011 15.7854L6.12418 16.1699L9.19811 13.1381C9.53438 12.8064 9.53812 12.265 9.20646 11.9287C8.8748 11.5924 8.33333 11.5887 7.99706 11.9203L4.97835 14.8977L5.47976 12.451C5.50451 12.3303 5.55656 12.2168 5.63191 12.1192L6.49083 11.0073C9.58386 7.00317 14.32 4.72705 19.2553 4.71054L16.3569 7.60884L14.6243 7.99154ZM2.79008 17.0559L3.8042 12.1076C3.88132 11.7313 4.0435 11.3776 4.27833 11.0736L5.13724 9.96172C8.89964 5.09105 14.861 2.53305 20.8954 3.07051C21.5971 3.133 22.2997 3.23735 23 3.38478L17.2133 9.1713L14.9932 9.66167L15.4238 9.91837C15.9039 10.2046 15.9849 10.8668 15.588 11.2603L10.4954 16.3088C9.90529 16.8938 9.15752 17.2941 8.34351 17.461L3.88965 18.3738L2.45572 19.788C2.11945 20.1197 1.57798 20.116 1.24632 19.7797C0.91466 19.4434 0.918397 18.9019 1.25467 18.5703L2.79008 17.0559Z" />
</svg>
Author
</div>
<span class="entry__author breakable"></span>
</div>
<div class="entry__changes"></div>
</li>
</template>
<script id="lib" src="scripts/lib.js"></script>
<script id="floCrypto" src="scripts/floCrypto.js"></script>
<script id="floBlockchainAPI" src="scripts/floBlockchainAPI.js"></script>
<script id="compactIDB" src="scripts/compactIDB.js"></script>
<script id="floCloudAPI" src="scripts/floCloudAPI.js"></script>
<script id="floDapps" src="scripts/floDapps.js"></script>
<script src="scripts/btcOperator.js"></script>
<script>
// dragula
!function (e) { "object" == typeof exports && "undefined" != typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define([], e) : ("undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof self ? self : this).dragula = e() }(function () { return function o(r, i, u) { function c(t, e) { if (!i[t]) { if (!r[t]) { var n = "function" == typeof require && require; if (!e && n) return n(t, !0); if (a) return a(t, !0); throw (n = new Error("Cannot find module '" + t + "'")).code = "MODULE_NOT_FOUND", n } n = i[t] = { exports: {} }, r[t][0].call(n.exports, function (e) { return c(r[t][1][e] || e) }, n, n.exports, o, r, i, u) } return i[t].exports } for (var a = "function" == typeof require && require, e = 0; e < u.length; e++)c(u[e]); return c }({ 1: [function (e, t, n) { "use strict"; var o = {}, r = "(?:^|\\s)", i = "(?:\\s|$)"; function u(e) { var t = o[e]; return t ? t.lastIndex = 0 : o[e] = t = new RegExp(r + e + i, "g"), t } t.exports = { add: function (e, t) { var n = e.className; n.length ? u(t).test(n) || (e.className += " " + t) : e.className = t }, rm: function (e, t) { e.className = e.className.replace(u(t), " ").trim() } } }, {}], 2: [function (e, t, n) { (function (r) { "use strict"; var M = e("contra/emitter"), k = e("crossvent"), j = e("./classes"), R = document, q = R.documentElement; function U(e, t, n, o) { r.navigator.pointerEnabled ? k[t](e, { mouseup: "pointerup", mousedown: "pointerdown", mousemove: "pointermove" }[n], o) : r.navigator.msPointerEnabled ? k[t](e, { mouseup: "MSPointerUp", mousedown: "MSPointerDown", mousemove: "MSPointerMove" }[n], o) : (k[t](e, { mouseup: "touchend", mousedown: "touchstart", mousemove: "touchmove" }[n], o), k[t](e, n, o)) } function K(e) { if (void 0 !== e.touches) return e.touches.length; if (void 0 !== e.which && 0 !== e.which) return e.which; if (void 0 !== e.buttons) return e.buttons; e = e.button; return void 0 !== e ? 1 & e ? 1 : 2 & e ? 3 : 4 & e ? 2 : 0 : void 0 } function z(e, t) { return void 0 !== r[t] ? r[t] : (q.clientHeight ? q : R.body)[e] } function H(e, t, n) { var o = (e = e || {}).className || ""; return e.className += " gu-hide", n = R.elementFromPoint(t, n), e.className = o, n } function V() { return !1 } function $() { return !0 } function G(e) { return e.width || e.right - e.left } function J(e) { return e.height || e.bottom - e.top } function Q(e) { return e.parentNode === R ? null : e.parentNode } function W(e) { return "INPUT" === e.tagName || "TEXTAREA" === e.tagName || "SELECT" === e.tagName || function e(t) { if (!t) return !1; if ("false" === t.contentEditable) return !1; if ("true" === t.contentEditable) return !0; return e(Q(t)) }(e) } function Z(t) { return t.nextElementSibling || function () { var e = t; for (; e = e.nextSibling, e && 1 !== e.nodeType;); return e }() } function ee(e, t) { var t = (n = t).targetTouches && n.targetTouches.length ? n.targetTouches[0] : n.changedTouches && n.changedTouches.length ? n.changedTouches[0] : n, n = { pageX: "clientX", pageY: "clientY" }; return e in n && !(e in t) && n[e] in t && (e = n[e]), t[e] } t.exports = function (e, t) { var l, f, s, d, m, o, r, v, p, h, n; 1 === arguments.length && !1 === Array.isArray(e) && (t = e, e = []); var i, g = null, y = t || {}; void 0 === y.moves && (y.moves = $), void 0 === y.accepts && (y.accepts = $), void 0 === y.invalid && (y.invalid = function () { return !1 }), void 0 === y.containers && (y.containers = e || []), void 0 === y.isContainer && (y.isContainer = V), void 0 === y.copy && (y.copy = !1), void 0 === y.copySortSource && (y.copySortSource = !1), void 0 === y.revertOnSpill && (y.revertOnSpill = !1), void 0 === y.removeOnSpill && (y.removeOnSpill = !1), void 0 === y.direction && (y.direction = "vertical"), void 0 === y.ignoreInputTextSelection && (y.ignoreInputTextSelection = !0), void 0 === y.mirrorContainer && (y.mirrorContainer = R.body); var w = M({ containers: y.containers, start: function (e) { e = S(e); e && C(e) }, end: O, cancel: L, remove: X, destroy: function () { c(!0), N({}) }, canMove: function (e) { return !!S(e) }, dragging: !1 }); return !0 === y.removeOnSpill && w.on("over", function (e) { j.rm(e, "gu-hide") }).on("out", function (e) { w.dragging && j.add(e, "gu-hide") }), c(), w; function u(e) { return -1 !== w.containers.indexOf(e) || y.isContainer(e) } function c(e) { e = e ? "remove" : "add"; U(q, e, "mousedown", E), U(q, e, "mouseup", N) } function a(e) { U(q, e ? "remove" : "add", "mousemove", x) } function b(e) { e = e ? "remove" : "add"; k[e](q, "selectstart", T), k[e](q, "click", T) } function T(e) { i && e.preventDefault() } function E(e) { var t, n; o = e.clientX, r = e.clientY, 1 !== K(e) || e.metaKey || e.ctrlKey || (n = S(t = e.target)) && (i = n, a(), "mousedown" === e.type && (W(t) ? t.focus() : e.preventDefault())) } function x(e) { if (i) if (0 !== K(e)) { if (!(void 0 !== e.clientX && Math.abs(e.clientX - o) <= (y.slideFactorX || 0) && void 0 !== e.clientY && Math.abs(e.clientY - r) <= (y.slideFactorY || 0))) { if (y.ignoreInputTextSelection) { var t = ee("clientX", e) || 0, n = ee("clientY", e) || 0; if (W(R.elementFromPoint(t, n))) return } n = i; a(!0), b(), O(), C(n); n = function (e) { e = e.getBoundingClientRect(); return { left: e.left + z("scrollLeft", "pageXOffset"), top: e.top + z("scrollTop", "pageYOffset") } }(s); d = ee("pageX", e) - n.left, m = ee("pageY", e) - n.top, j.add(h || s, "gu-transit"), function () { if (l) return; var e = s.getBoundingClientRect(); (l = s.cloneNode(!0)).style.width = G(e) + "px", l.style.height = J(e) + "px", j.rm(l, "gu-transit"), j.add(l, "gu-mirror"), y.mirrorContainer.appendChild(l), U(q, "add", "mousemove", P), j.add(y.mirrorContainer, "gu-unselectable"), w.emit("cloned", l, s, "mirror") }(), P(e) } } else N({}) } function S(e) { if (!(w.dragging && l || u(e))) { for (var t = e; Q(e) && !1 === u(Q(e));) { if (y.invalid(e, t)) return; if (!(e = Q(e))) return } var n = Q(e); if (n) if (!y.invalid(e, t)) if (y.moves(e, n, t, Z(e))) return { item: e, source: n } } } function C(e) { var t, n; t = e.item, n = e.source, ("boolean" == typeof y.copy ? y.copy : y.copy(t, n)) && (h = e.item.cloneNode(!0), w.emit("cloned", h, e.item, "copy")), f = e.source, s = e.item, v = p = Z(e.item), w.dragging = !0, w.emit("drag", s, f) } function O() { var e; w.dragging && _(e = h || s, Q(e)) } function I() { a(!(i = !1)), b(!0) } function N(e) { var t, n; I(), w.dragging && (t = h || s, n = ee("clientX", e) || 0, e = ee("clientY", e) || 0, (e = B(H(l, n, e), n, e)) && (h && y.copySortSource || !h || e !== f) ? _(t, e) : (y.removeOnSpill ? X : L)()) } function _(e, t) { var n = Q(e); h && y.copySortSource && t === f && n.removeChild(s), A(t) ? w.emit("cancel", e, f, f) : w.emit("drop", e, t, f, p), Y() } function X() { var e, t; w.dragging && ((t = Q(e = h || s)) && t.removeChild(e), w.emit(h ? "cancel" : "remove", e, t, f), Y()) } function L(e) { var t, n, o; w.dragging && (t = 0 < arguments.length ? e : y.revertOnSpill, !1 === (e = A(o = Q(n = h || s))) && t && (h ? o && o.removeChild(h) : f.insertBefore(n, v)), e || t ? w.emit("cancel", n, f, f) : w.emit("drop", n, o, f, p), Y()) } function Y() { var e = h || s; I(), l && (j.rm(y.mirrorContainer, "gu-unselectable"), U(q, "remove", "mousemove", P), Q(l).removeChild(l), l = null), e && j.rm(e, "gu-transit"), n && clearTimeout(n), w.dragging = !1, g && w.emit("out", e, g, f), w.emit("dragend", e), f = s = h = v = p = n = g = null } function A(e, t) { t = void 0 !== t ? t : l ? p : Z(h || s); return e === f && t === v } function B(t, n, o) { for (var r = t; r && !function () { if (!1 === u(r)) return !1; var e = D(r, t), e = F(r, e, n, o); if (A(r, e)) return !0; return y.accepts(s, r, f, e) }();)r = Q(r); return r } function P(e) { if (l) { e.preventDefault(); var t = ee("clientX", e) || 0, n = ee("clientY", e) || 0, o = t - d, r = n - m; l.style.left = o + "px", l.style.top = r + "px"; var i = h || s, e = H(l, t, n), o = B(e, t, n), u = null !== o && o !== g; !u && null !== o || (g && a("out"), g = o, u && a("over")); r = Q(i); if (o !== f || !h || y.copySortSource) { var c, e = D(o, e); if (null !== e) c = F(o, e, t, n); else { if (!0 !== y.revertOnSpill || h) return void (h && r && r.removeChild(i)); c = v, o = f } (null === c && u || c !== i && c !== Z(i)) && (p = c, o.insertBefore(i, c), w.emit("shadow", i, o, f)) } else r && r.removeChild(i) } function a(e) { w.emit(e, i, g, f) } } function D(e, t) { for (var n = t; n !== e && Q(n) !== e;)n = Q(n); return n === q ? null : n } function F(r, t, i, u) { var c = "horizontal" === y.direction; return (t !== r ? function () { var e = t.getBoundingClientRect(); if (c) return n(i > e.left + G(e) / 2); return n(u > e.top + J(e) / 2) } : function () { var e, t, n, o = r.children.length; for (e = 0; e < o; e++) { if (t = r.children[e], n = t.getBoundingClientRect(), c && n.left + n.width / 2 > i) return t; if (!c && n.top + n.height / 2 > u) return t } return null })(); function n(e) { return e ? Z(t) : t } } } }).call(this, "undefined" != typeof global ? global : "undefined" != typeof self ? self : "undefined" != typeof window ? window : {}) }, { "./classes": 1, "contra/emitter": 5, crossvent: 6 }], 3: [function (e, t, n) { t.exports = function (e, t) { return Array.prototype.slice.call(e, t) } }, {}], 4: [function (e, t, n) { "use strict"; var o = e("ticky"); t.exports = function (e, t, n) { e && o(function () { e.apply(n || null, t || []) }) } }, { ticky: 10 }], 5: [function (e, t, n) { "use strict"; var c = e("atoa"), a = e("./debounce"); t.exports = function (r, e) { var i = e || {}, u = {}; return void 0 === r && (r = {}), r.on = function (e, t) { return u[e] ? u[e].push(t) : u[e] = [t], r }, r.once = function (e, t) { return t._once = !0, r.on(e, t), r }, r.off = function (e, t) { var n = arguments.length; if (1 === n) delete u[e]; else if (0 === n) u = {}; else { e = u[e]; if (!e) return r; e.splice(e.indexOf(t), 1) } return r }, r.emit = function () { var e = c(arguments); return r.emitterSnapshot(e.shift()).apply(this, e) }, r.emitterSnapshot = function (o) { var e = (u[o] || []).slice(0); return function () { var t = c(arguments), n = this || r; if ("error" === o && !1 !== i.throws && !e.length) throw 1 === t.length ? t[0] : t; return e.forEach(function (e) { i.async ? a(e, t, n) : e.apply(n, t), e._once && r.off(o, e) }), r } }, r } }, { "./debounce": 4, atoa: 3 }], 6: [function (n, o, e) { (function (r) { "use strict"; var i = n("custom-event"), u = n("./eventmap"), c = r.document, e = function (e, t, n, o) { return e.addEventListener(t, n, o) }, t = function (e, t, n, o) { return e.removeEventListener(t, n, o) }, a = []; function l(e, t, n) { t = function (e, t, n) { var o, r; for (o = 0; o < a.length; o++)if ((r = a[o]).element === e && r.type === t && r.fn === n) return o }(e, t, n); if (t) { n = a[t].wrapper; return a.splice(t, 1), n } } r.addEventListener || (e = function (e, t, n) { return e.attachEvent("on" + t, function (e, t, n) { var o = l(e, t, n) || function (n, o) { return function (e) { var t = e || r.event; t.target = t.target || t.srcElement, t.preventDefault = t.preventDefault || function () { t.returnValue = !1 }, t.stopPropagation = t.stopPropagation || function () { t.cancelBubble = !0 }, t.which = t.which || t.keyCode, o.call(n, t) } }(e, n); return a.push({ wrapper: o, element: e, type: t, fn: n }), o }(e, t, n)) }, t = function (e, t, n) { n = l(e, t, n); if (n) return e.detachEvent("on" + t, n) }), o.exports = { add: e, remove: t, fabricate: function (e, t, n) { var o = -1 === u.indexOf(t) ? new i(t, { detail: n }) : function () { var e; c.createEvent ? (e = c.createEvent("Event")).initEvent(t, !0, !0) : c.createEventObject && (e = c.createEventObject()); return e }(); e.dispatchEvent ? e.dispatchEvent(o) : e.fireEvent("on" + t, o) } } }).call(this, "undefined" != typeof global ? global : "undefined" != typeof self ? self : "undefined" != typeof window ? window : {}) }, { "./eventmap": 7, "custom-event": 8 }], 7: [function (e, r, t) { (function (e) { "use strict"; var t = [], n = "", o = /^on/; for (n in e) o.test(n) && t.push(n.slice(2)); r.exports = t }).call(this, "undefined" != typeof global ? global : "undefined" != typeof self ? self : "undefined" != typeof window ? window : {}) }, {}], 8: [function (e, n, t) { (function (e) { var t = e.CustomEvent; n.exports = function () { try { var e = new t("cat", { detail: { foo: "bar" } }); return "cat" === e.type && "bar" === e.detail.foo } catch (e) { } }() ? t : "undefined" != typeof document && "function" == typeof document.createEvent ? function (e, t) { var n = document.createEvent("CustomEvent"); return t ? n.initCustomEvent(e, t.bubbles, t.cancelable, t.detail) : n.initCustomEvent(e, !1, !1, void 0), n } : function (e, t) { var n = document.createEventObject(); return n.type = e, t ? (n.bubbles = Boolean(t.bubbles), n.cancelable = Boolean(t.cancelable), n.detail = t.detail) : (n.bubbles = !1, n.cancelable = !1, n.detail = void 0), n } }).call(this, "undefined" != typeof global ? global : "undefined" != typeof self ? self : "undefined" != typeof window ? window : {}) }, {}], 9: [function (e, t, n) { var o, r, t = t.exports = {}; function i() { throw new Error("setTimeout has not been defined") } function u() { throw new Error("clearTimeout has not been defined") } function c(t) { if (o === setTimeout) return setTimeout(t, 0); if ((o === i || !o) && setTimeout) return o = setTimeout, setTimeout(t, 0); try { return o(t, 0) } catch (e) { try { return o.call(null, t, 0) } catch (e) { return o.call(this, t, 0) } } } !function () { try { o = "function" == typeof setTimeout ? setTimeout : i } catch (e) { o = i } try { r = "function" == typeof clearTimeout ? clearTimeout : u } catch (e) { r = u } }(); var a, l = [], f = !1, s = -1; function d() { f && a && (f = !1, a.length ? l = a.concat(l) : s = -1, l.length && m()) } function m() { if (!f) { var e = c(d); f = !0; for (var t = l.length; t;) { for (a = l, l = []; ++s < t;)a && a[s].run(); s = -1, t = l.length } a = null, f = !1, function (t) { if (r === clearTimeout) return clearTimeout(t); if ((r === u || !r) && clearTimeout) return r = clearTimeout, clearTimeout(t); try { r(t) } catch (e) { try { return r.call(null, t) } catch (e) { return r.call(this, t) } } }(e) } } function v(e, t) { this.fun = e, this.array = t } function p() { } t.nextTick = function (e) { var t = new Array(arguments.length - 1); if (1 < arguments.length) for (var n = 1; n < arguments.length; n++)t[n - 1] = arguments[n]; l.push(new v(e, t)), 1 !== l.length || f || c(m) }, v.prototype.run = function () { this.fun.apply(null, this.array) }, t.title = "browser", t.browser = !0, t.env = {}, t.argv = [], t.version = "", t.versions = {}, t.on = p, t.addListener = p, t.once = p, t.off = p, t.removeListener = p, t.removeAllListeners = p, t.emit = p, t.prependListener = p, t.prependOnceListener = p, t.listeners = function (e) { return [] }, t.binding = function (e) { throw new Error("process.binding is not supported") }, t.cwd = function () { return "/" }, t.chdir = function (e) { throw new Error("process.chdir is not supported") }, t.umask = function () { return 0 } }, {}], 10: [function (e, n, t) { (function (t) { var e = "function" == typeof t ? function (e) { t(e) } : function (e) { setTimeout(e, 0) }; n.exports = e }).call(this, e("timers").setImmediate) }, { timers: 11 }], 11: [function (a, e, l) { (function (e, t) { var o = a("process/browser.js").nextTick, n = Function.prototype.apply, r = Array.prototype.slice, i = {}, u = 0; function c(e, t) { this._id = e, this._clearFn = t } l.setTimeout = function () { return new c(n.call(setTimeout, window, arguments), clearTimeout) }, l.setInterval = function () { return new c(n.call(setInterval, window, arguments), clearInterval) }, l.clearTimeout = l.clearInterval = function (e) { e.close() }, c.prototype.unref = c.prototype.ref = function () { }, c.prototype.close = function () { this._clearFn.call(window, this._id) }, l.enroll = function (e, t) { clearTimeout(e._idleTimeoutId), e._idleTimeout = t }, l.unenroll = function (e) { clearTimeout(e._idleTimeoutId), e._idleTimeout = -1 }, l._unrefActive = l.active = function (e) { clearTimeout(e._idleTimeoutId); var t = e._idleTimeout; 0 <= t && (e._idleTimeoutId = setTimeout(function () { e._onTimeout && e._onTimeout() }, t)) }, l.setImmediate = "function" == typeof e ? e : function (e) { var t = u++, n = !(arguments.length < 2) && r.call(arguments, 1); return i[t] = !0, o(function () { i[t] && (n ? e.apply(null, n) : e.call(null), l.clearImmediate(t)) }), t }, l.clearImmediate = "function" == typeof t ? t : function (e) { delete i[e] } }).call(this, a("timers").setImmediate, a("timers").clearImmediate) }, { "process/browser.js": 9, timers: 11 }] }, {}, [2])(2) });
</script>
<script id="ui_utils">
const { html, render: renderElem } = uhtml;
const domRefs = {}
//Checks for internet connection status
if (!navigator.onLine)
notify('There seems to be a problem connecting to the internet, Please check you internet connection.', 'error', '', true)
window.addEventListener('offline', () => {
notify('There seems to be a problem connecting to the internet, Please check you internet connection.', 'error', true, true)
})
window.addEventListener('online', () => {
getRef('notification_drawer').clearAll()
notify('We are back online.', 'success')
})
// Use instead of document.getElementById
function getRef(elementId) {
if (!domRefs.hasOwnProperty(elementId)) {
domRefs[elementId] = {
count: 1,
ref: null,
};
return document.getElementById(elementId);
} else {
if (domRefs[elementId].count < 3) {
domRefs[elementId].count = domRefs[elementId].count + 1;
return document.getElementById(elementId);
} else {
if (!domRefs[elementId].ref)
domRefs[elementId].ref = document.getElementById(elementId);
return domRefs[elementId].ref;
}
}
}
// returns dom with specified element
function createElement(tagName, options = {}) {
const { className, textContent, innerHTML, attributes = {} } = options
const elem = document.createElement(tagName)
for (let attribute in attributes) {
elem.setAttribute(attribute, attributes[attribute])
}
if (className)
elem.className = className
if (textContent)
elem.textContent = textContent
if (innerHTML)
elem.innerHTML = innerHTML
return elem
}
// Use when a function needs to be executed after user finishes changes
const debounce = (callback, wait) => {
let timeoutId = null;
return (...args) => {
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => {
callback.apply(null, args);
}, wait);
};
}
// adds a class to all elements in an array
function addClass(elements, className) {
elements.forEach((element) => {
document.querySelector(element).classList.add(className);
});
}
// removes a class from all elements in an array
function removeClass(elements, className) {
elements.forEach((element) => {
document.querySelector(element).classList.remove(className);
});
}
// return querySelectorAll elements as an array
function getAllElements(selector) {
return Array.from(document.querySelectorAll(selector));
}
let zIndex = 50
// function required for popups or modals to appear
function openPopup(popupId, pinned) {
zIndex++
getRef(popupId).setAttribute('style', `z-index: ${zIndex}`)
getRef(popupId).show({ pinned })
return getRef(popupId);
}
// hides the popup or modal
function closePopup() {
if (popupStack.peek() === undefined)
return;
popupStack.peek().popup.hide()
}
let sectionDragger
document.addEventListener('popupopened', e => {
switch (e.target.id) {
case 'edit_sections_popup':
renderSectionList()
if (sectionDragger)
sectionDragger.destroy()
sectionDragger = dragula([getRef('section_list_container')], {
moves: function (el, container, handle) {
return handle.classList.contains('handle');
}
})
break;
case 'article_list_popup':
renderArticleList()
break;
case 'plot_popup':
getRef('toggle_focus_mode').textContent = floGlobals.focusMode.active ? 'Turn off focus mode' : 'Turn on focus mode'
break;
}
})
document.addEventListener('popupclosed', e => {
zIndex--
switch (e.target.id) {
case 'edit_sections_popup':
renderElem(getRef('section_list_container'), html``)
if (sectionDragger)
sectionDragger.destroy()
break;
case 'article_list_popup':
renderElem(getRef('article_list'), html``)
break;
case 'contributors_popup':
getRef('contributor_list').innerHTML = ''
break;
}
})
// displays a popup for asking permission. Use this instead of JS confirm
const getConfirmation = (title, options = {}) => {
return new Promise(resolve => {
const { message = '', cancelText = 'Cancel', confirmText = 'OK' } = options
openPopup('confirmation_popup', true)
getRef('confirm_title').innerText = title;
getRef('confirm_message').innerText = message;
let cancelButton = getRef('confirmation_popup').children[2].children[0],
submitButton = getRef('confirmation_popup').children[2].children[1]
submitButton.textContent = confirmText
cancelButton.textContent = cancelText
submitButton.onclick = () => {
closePopup()
resolve(true);
}
cancelButton.onclick = () => {
closePopup()
resolve(false);
}
})
}
//Function for displaying toast notifications. pass in error for mode param if you want to show an error.
function notify(message, mode, options = {}) {
let icon
switch (mode) {
case 'success':
icon = `<svg class="icon icon--success" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 15.172l9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z"/></svg>`
break;
case 'error':
icon = `<svg class="icon icon--error" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm0-8v6h2V7h-2z"/></svg>`
break;
}
getRef("notification_drawer").push(message, { icon, ...options });
if (mode === 'error') {
console.error(message)
}
}
function getFormattedTime(timestamp, format) {
try {
if (String(timestamp).length < 13)
timestamp *= 1000
let [day, month, date, year] = new Date(timestamp).toString().split(' '),
minutes = new Date(timestamp).getMinutes(),
hours = new Date(timestamp).getHours(),
currentTime = new Date().toString().split(' ')
minutes = minutes < 10 ? `0${minutes}` : minutes
let finalHours = ``;
if (hours > 12)
finalHours = `${hours - 12}:${minutes}`
else if (hours === 0)
finalHours = `12:${minutes}`
else
finalHours = `${hours}:${minutes}`
finalHours = hours >= 12 ? `${finalHours} PM` : `${finalHours} AM`
switch (format) {
case 'date-only':
return `${month} ${date}, ${year}`;
break;
case 'time-only':
return finalHours;
case 'relative':
// check if timestamp is older than a day
if (Date.now() - new Date(timestamp) < 60 * 60 * 24 * 1000)
return `${finalHours}`;
else
return relativeTime.from(timestamp)
default:
return `${month} ${date}, ${year} at ${finalHours}`;
}
} catch (e) {
console.error(e);
return timestamp;
}
}
window.addEventListener('hashchange', e => routeTo(window.location.hash))
window.addEventListener("load", () => {
document.body.classList.remove('hidden')
document.querySelectorAll('sm-input[data-private-key]').forEach(input => input.customValidation = floCrypto.getPubKeyHex)
document.addEventListener('keyup', (e) => {
if (e.key === 'Escape') {
closePopup()
}
})
document.addEventListener('copy', () => {
notify('copied', 'success')
})
document.addEventListener("pointerdown", (e) => {
if (e.target.closest("button, sm-button:not([disabled]), .interact")) {
createRipple(e, e.target.closest("button, sm-button, .interact"));
}
});
});
function createRipple(event, target) {
const circle = document.createElement("span");
const diameter = Math.max(target.clientWidth, target.clientHeight);
const radius = diameter / 2;
const targetDimensions = target.getBoundingClientRect();
circle.style.width = circle.style.height = `${diameter}px`;
circle.style.left = `${event.clientX - (targetDimensions.left + radius)}px`;
circle.style.top = `${event.clientY - (targetDimensions.top + radius)}px`;
circle.classList.add("ripple");
const rippleAnimation = circle.animate(
[
{
opacity: 1,
transform: `scale(0)`
},
{
transform: "scale(4)",
opacity: 0,
},
],
{
duration: 600,
fill: "forwards",
easing: "ease-out",
}
);
target.append(circle);
rippleAnimation.onfinish = () => {
circle.remove();
};
}
const pagesData = {
openedPages: [],
params: {}
}
async function routeTo(targetPage, options = {}) {
const { firstLoad, hashChange, isPreview, redirected } = options
let pageId
let params = {}
let searchParams
if (targetPage === '') {
if (typeof floGlobals.myFloID === "undefined") {
pageId = 'landing'
} else {
pageId = 'main_page'
}
} else {
if (targetPage.includes('/')) {
if (targetPage.includes('?')) {
const splitAddress = targetPage.split('?')
searchParams = splitAddress.pop()
const pages = splitAddress.pop().split('/')
pageId = pages[1]
subPageId = pages[2]
} else {
const pages = targetPage.split('/')
pageId = pages[1]
subPageId = pages[2]
}
} else {
pageId = targetPage
}
}
if (isPreview) {
floGlobals.preview = location.hash;
}
if (redirected) {
floGlobals.redirectToArticle = location.hash
}
if (typeof floGlobals.myFloID === "undefined" && !(['sign_up', 'sign_in', 'loading', 'landing'].includes(pageId))) return
if (searchParams) {
const urlSearchParams = new URLSearchParams('?' + searchParams);
params = Object.fromEntries(urlSearchParams.entries());
}
if (firstLoad || params.articleID !== pagesData.params.articleID) {
if (!params.articleID) {
// remove empty slots from array
floGlobals.appObjects.cc.wipArticles = floGlobals.appObjects.cc.wipArticles.filter(v => v)
// if no articleID is provided, get a random article from work-in-progress
const randomArticle = floCrypto.randInt(0, floGlobals.appObjects.cc.wipArticles.length - 1)
params['articleID'] = floGlobals.appObjects.cc.wipArticles[randomArticle]
}
await Promise.all([
floCloudAPI.requestObjectData(params.articleID),
floCloudAPI.requestGeneralData(`${params.articleID}_gd`)
])
}
console.log(params)
switch (pageId) {
case 'landing':
targetPage = 'landing'
break;
case 'sign_in':
setTimeout(() => {
getRef('private_key_field').focusIn()
}, 0);
targetPage = 'sign_in'
break;
case 'sign_up':
getRef('keys_generator').generateKeys()
targetPage = 'sign_up'
break;
case 'home':
case 'main_page':
if (!floGlobals.currentArticle.id || params.articleID !== pagesData.params.articleID || params.focusMode !== pagesData.params.focusMode) {
closePopup()
render.article(params.articleID, params.focusMode === 'true')
}
if (floGlobals.appObjects.cc.wipArticles.includes(params.articleID)) {
getRef('mark_as_done').classList.remove('hidden')
getRef('mark_as_wip').classList.add('hidden')
} else {
getRef('mark_as_done').classList.add('hidden')
getRef('mark_as_wip').classList.remove('hidden')
}
hideVersionHistory()
window.history.replaceState('', '', `#/home?articleID=${params.articleID}&focusMode=${params.hasOwnProperty('focusMode') ? params.focusMode : 'true'}`)
renderElem(getRef('preview__body'), html``)
targetPage = 'main_page'
break;
case 'preview':
case 'preview_page':
if (params.uid) {
getRef('preview__title').textContent = floGlobals.appObjects.cc.articleList[params.articleID].title
getRef('preview__body').innerHTML = floGlobals.appObjects[params.articleID].preview.content
}
targetPage = 'preview_page'
break
}
document.querySelectorAll('.page').forEach(page => page.classList.add('hidden'))
getRef(targetPage).classList.remove('hidden')
if (pagesData.lastPage !== pageId) {
pagesData.lastPage = pageId
if (!pagesData.openedPages.includes(pageId)) {
pagesData.openedPages.push(pageId)
}
}
if (params)
pagesData.params = params
}
// class based lazy loading
class LazyLoader {
constructor(container, elementsToRender, renderFn, options = {}) {
const { batchSize = 10, freshRender, bottomFirst = false, domUpdated } = options
this.elementsToRender = elementsToRender
this.arrayOfElements = (typeof elementsToRender === 'function') ? this.elementsToRender() : elementsToRender || []
this.renderFn = renderFn
this.intersectionObserver
this.batchSize = batchSize
this.freshRender = freshRender
this.domUpdated = domUpdated
this.bottomFirst = bottomFirst
this.shouldLazyLoad = false
this.lastScrollTop = 0
this.lastScrollHeight = 0
this.lazyContainer = document.querySelector(container)
this.update = this.update.bind(this)
this.render = this.render.bind(this)
this.init = this.init.bind(this)
this.clear = this.clear.bind(this)
}
get elements() {
return this.arrayOfElements
}
init() {
this.intersectionObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
observer.disconnect()
this.render({ lazyLoad: true })
}
})
}, {
root: this.lazyContainer
})
this.mutationObserver = new MutationObserver(mutationList => {
mutationList.forEach(mutation => {
if (mutation.type === 'childList') {
if (mutation.addedNodes.length) {
if (this.bottomFirst) {
if (this.lazyContainer.firstElementChild)
this.intersectionObserver.observe(this.lazyContainer.firstElementChild)
} else {
if (this.lazyContainer.lastElementChild)
this.intersectionObserver.observe(this.lazyContainer.lastElementChild)
}
}
}
})
})
this.mutationObserver.observe(this.lazyContainer, {
childList: true,
})
this.render()
}
update(elementsToRender) {
this.arrayOfElements = (typeof elementsToRender === 'function') ? this.elementsToRender() : elementsToRender || []
}
render(options = {}) {
let { lazyLoad = false } = options
this.shouldLazyLoad = lazyLoad
const frag = document.createDocumentFragment();
if (lazyLoad) {
if (this.bottomFirst) {
this.updateEndIndex = this.updateStartIndex
this.updateStartIndex = this.updateEndIndex - this.batchSize
} else {
this.updateStartIndex = this.updateEndIndex
this.updateEndIndex = this.updateEndIndex + this.batchSize
}
} else {
this.intersectionObserver.disconnect()
if (this.bottomFirst) {
this.updateEndIndex = this.arrayOfElements.length
this.updateStartIndex = this.updateEndIndex - this.batchSize - 1
} else {
this.updateStartIndex = 0
this.updateEndIndex = this.batchSize
}
this.lazyContainer.innerHTML = ``;
}
this.lastScrollHeight = this.lazyContainer.scrollHeight
this.lastScrollTop = this.lazyContainer.scrollTop
this.arrayOfElements.slice(this.updateStartIndex, this.updateEndIndex).forEach((element, index) => {
frag.append(this.renderFn(element))
})
if (this.bottomFirst) {
this.lazyContainer.prepend(frag)
// scroll anchoring for reverse scrolling
this.lastScrollTop += this.lazyContainer.scrollHeight - this.lastScrollHeight
this.lazyContainer.scrollTo({ top: this.lastScrollTop })
this.lastScrollHeight = this.lazyContainer.scrollHeight
} else {
this.lazyContainer.append(frag)
}
if (!lazyLoad && this.bottomFirst) {
this.lazyContainer.scrollTop = this.lazyContainer.scrollHeight
}
// Callback to be called if elements are updated or rendered for first time
if (!lazyLoad && this.freshRender)
this.freshRender()
}
clear() {
this.intersectionObserver.disconnect()
this.mutationObserver.disconnect()
this.lazyContainer.innerHTML = ``;
}
reset() {
this.arrayOfElements = (typeof this.elementsToRender === 'function') ? this.elementsToRender() : this.elementsToRender || []
this.render()
}
}
function animateTo(element, keyframes, options) {
const anime = element.animate(keyframes, { ...options, fill: 'both' })
anime.finished.then(() => {
anime.commitStyles()
anime.cancel()
})
return anime
}
const generateKeys = document.createElement('template')
generateKeys.innerHTML = `
<style>
:host{
display: flex;
justify-content: center;
}
.generated-keys-wrapper {
padding: 1rem;
background-color: rgba(var(--foreground-color), 1);
border-radius: 0.5rem;
}
#flo_id_warning{
padding-bottom: 1.5rem;
}
#flo_id_warning .icon {
height: 3rem;
width: 3rem;
padding: 0.8rem;
overflow: visible;
background-color: #ffc107;
border-radius: 3rem;
fill: rgba(0, 0, 0, 0.8);
}
</style>
<section class="grid gap-1-5">
<div id="flo_id_warning" class="flex gap-1">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <path d="M0 0h24v24H0z" fill="none" /> <path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" /> </svg>
<div class="grid gap-0-5">
<strong>
<h3> Keep your keys safe! </h3>
</strong>
<p>Don't share with anyone. Once lost private key can't be recovered.</p>
</div>
</div>
<div class="grid gap-1-5 generated-keys-wrapper">
<div class="grid gap-0-5">
<h5>FLO address</h5>
<sm-copy id="generated_flo_address"></sm-copy>
</div>
<div class="grid gap-0-5">
<h5>Private key</h5>
<sm-copy id="generated_private_key"></sm-copy>
</div>
</div>
<button id="sign_up_button" class="button button--primary w-100">Sign in with these credentials</button>
<p class="margin-top-1">You can use these FLO credentials with other RanchiMall apps too. </p>
</section>
`
window.customElements.define('keys-generator', class extends HTMLElement {
constructor() {
super();
this.appendChild(generateKeys.content.cloneNode(true));
}
get keys() {
return {
floID: this.querySelector('#generated_flo_address').value,
privKey: this.querySelector('#generated_private_key').value
}
}
generateKeys() {
const { floID, privKey } = floCrypto.generateNewID()
this.querySelector('#generated_flo_address').value = floID
this.querySelector('#generated_private_key').value = privKey
}
clearKeys() {
this.querySelector('#generated_flo_address').value = ''
this.querySelector('#generated_private_key').value = ''
}
});
function buttonLoader(id, show) {
const button = typeof id === 'string' ? getRef(id) : id;
button.disabled = show;
const animOptions = {
duration: 200,
fill: 'forwards',
easing: 'ease'
}
if (show) {
button.animate([
{
clipPath: 'circle(100%)',
},
{
clipPath: 'circle(0)',
},
], animOptions).onfinish = e => {
e.target.commitStyles()
e.target.cancel()
}
button.parentNode.append(createElement('sm-spinner'))
} else {
button.style = ''
const potentialTarget = button.parentNode.querySelector('sm-spinner')
if (potentialTarget) potentialTarget.remove();
}
}
</script>
<script>
// JS Diff
!function (e, n) { "object" == typeof exports && "undefined" != typeof module ? n(exports) : "function" == typeof define && define.amd ? define(["exports"], n) : n((e = e || self).Diff = {}) }(this, function (e) { "use strict"; function t() { } t.prototype = { diff: function (s, u, e) { var e = 2 < arguments.length && void 0 !== e ? e : {}, n = e.callback; "function" == typeof e && (n = e, e = {}), this.options = e; var a = this; function f(e) { return n ? (setTimeout(function () { n(void 0, e) }, 0), !0) : e } s = this.castInput(s), u = this.castInput(u), s = this.removeEmpty(this.tokenize(s)); var d = (u = this.removeEmpty(this.tokenize(u))).length, c = s.length, h = 1, t = d + c, p = [{ newPos: -1, components: [] }], e = this.extractCommon(p[0], u, s, 0); if (p[0].newPos + 1 >= d && c <= e + 1) return f([{ value: this.join(u), count: u.length }]); function r() { for (var e = -1 * h; e <= h; e += 2) { var n = void 0, t = p[e - 1], r = p[e + 1], i = (r ? r.newPos : 0) - e; t && (p[e - 1] = void 0); var o = t && t.newPos + 1 < d, l = r && 0 <= i && i < c; if (o || l) { if (!o || l && t.newPos < r.newPos ? (n = { newPos: (r = r).newPos, components: r.components.slice(0) }, a.pushComponent(n.components, void 0, !0)) : ((n = t).newPos++, a.pushComponent(n.components, !0, void 0)), i = a.extractCommon(n, u, s, e), n.newPos + 1 >= d && c <= i + 1) return f(function (e, n, t, r, i) { for (var o = 0, l = n.length, s = 0, u = 0; o < l; o++) { var a, f = n[o]; f.removed ? (f.value = e.join(r.slice(u, u + f.count)), u += f.count, o && n[o - 1].added && (a = n[o - 1], n[o - 1] = n[o], n[o] = a)) : (!f.added && i ? (a = (a = t.slice(s, s + f.count)).map(function (e, n) { n = r[u + n]; return n.length > e.length ? n : e }), f.value = e.join(a)) : f.value = e.join(t.slice(s, s + f.count)), s += f.count, f.added || (u += f.count)) } var d = n[l - 1]; 1 < l && "string" == typeof d.value && (d.added || d.removed) && e.equals("", d.value) && (n[l - 2].value += d.value, n.pop()); return n }(a, n.components, u, s, a.useLongestToken)); p[e] = n } else p[e] = void 0 } h++ } if (n) !function e() { setTimeout(function () { return t < h ? n() : void (r() || e()) }, 0) }(); else for (; h <= t;) { var i = r(); if (i) return i } }, pushComponent: function (e, n, t) { var r = e[e.length - 1]; r && r.added === n && r.removed === t ? e[e.length - 1] = { count: r.count + 1, added: n, removed: t } : e.push({ count: 1, added: n, removed: t }) }, extractCommon: function (e, n, t, r) { for (var i = n.length, o = t.length, l = e.newPos, s = l - r, u = 0; l + 1 < i && s + 1 < o && this.equals(n[l + 1], t[s + 1]);)l++, s++, u++; return u && e.components.push({ count: u }), e.newPos = l, s }, equals: function (e, n) { return this.options.comparator ? this.options.comparator(e, n) : e === n || this.options.ignoreCase && e.toLowerCase() === n.toLowerCase() }, removeEmpty: function (e) { for (var n = [], t = 0; t < e.length; t++)e[t] && n.push(e[t]); return n }, castInput: function (e) { return e }, tokenize: function (e) { return e.split("") }, join: function (e) { return e.join("") } }; var r = new t; function i(e, n) { if ("function" == typeof e) n.callback = e; else if (e) for (var t in e) e.hasOwnProperty(t) && (n[t] = e[t]); return n } var o = /^[A-Za-z\xC0-\u02C6\u02C8-\u02D7\u02DE-\u02FF\u1E00-\u1EFF]+$/, l = /\S/, s = new t; s.equals = function (e, n) { return this.options.ignoreCase && (e = e.toLowerCase(), n = n.toLowerCase()), e === n || this.options.ignoreWhitespace && !l.test(e) && !l.test(n) }, s.tokenize = function (e) { for (var n = e.split(/([^\S\r\n]+|[()[\]{}'"\r\n]|\b)/), t = 0; t < n.length - 1; t++)!n[t + 1] && n[t + 2] && o.test(n[t]) && o.test(n[t + 2]) && (n[t] += n[t + 2], n.splice(t + 1, 2), t--); return n }; var u = new t; function m(e, n, t) { return u.diff(e, n, t) } u.tokenize = function (e) { var n = [], t = e.split(/(\n|\r\n)/); t[t.length - 1] || t.pop(); for (var r = 0; r < t.length; r++) { var i = t[r]; r % 2 && !this.options.newlineIsToken ? n[n.length - 1] += i : (this.options.ignoreWhitespace && (i = i.trim()), n.push(i)) } return n }; var a = new t; a.tokenize = function (e) { return e.split(/(\S.+?[.!?])(?=\s+|$)/) }; var f = new t; function d(e) { return (d = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (e) { return typeof e } : function (e) { return e && "function" == typeof Symbol && e.constructor === Symbol && e !== Symbol.prototype ? "symbol" : typeof e })(e) } function w(e) { return function (e) { if (Array.isArray(e)) return c(e) }(e) || function (e) { if ("undefined" != typeof Symbol && Symbol.iterator in Object(e)) return Array.from(e) }(e) || function (e, n) { if (!e) return; if ("string" == typeof e) return c(e, n); var t = Object.prototype.toString.call(e).slice(8, -1); "Object" === t && e.constructor && (t = e.constructor.name); if ("Map" === t || "Set" === t) return Array.from(e); if ("Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t)) return c(e, n) }(e) || function () { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.") }() } function c(e, n) { (null == n || n > e.length) && (n = e.length); for (var t = 0, r = new Array(n); t < n; t++)r[t] = e[t]; return r } f.tokenize = function (e) { return e.split(/([{}:;,]|\s+)/) }; var h = Object.prototype.toString, p = new t; function v(e, n, t, r, i) { var o, l; for (n = n || [], t = t || [], r && (e = r(i, e)), o = 0; o < n.length; o += 1)if (n[o] === e) return t[o]; if ("[object Array]" === h.call(e)) { for (n.push(e), l = new Array(e.length), t.push(l), o = 0; o < e.length; o += 1)l[o] = v(e[o], n, t, r, i); return n.pop(), t.pop(), l } if (e && e.toJSON && (e = e.toJSON()), "object" === d(e) && null !== e) { n.push(e), l = {}, t.push(l); var s, u = []; for (s in e) e.hasOwnProperty(s) && u.push(s); for (u.sort(), o = 0; o < u.length; o += 1)l[s = u[o]] = v(e[s], n, t, r, s); n.pop(), t.pop() } else l = e; return l } p.useLongestToken = !0, p.tokenize = u.tokenize, p.castInput = function (e) { var n = this.options, t = n.undefinedReplacement, n = n.stringifyReplacer, n = void 0 === n ? function (e, n) { return void 0 === n ? t : n } : n; return "string" == typeof e ? e : JSON.stringify(v(e, null, null, n), n, " ") }, p.equals = function (e, n) { return t.prototype.equals.call(p, e.replace(/,([\r\n])/g, "$1"), n.replace(/,([\r\n])/g, "$1")) }; var g = new t; function P(e) { var l = 1 < arguments.length && void 0 !== arguments[1] ? arguments[1] : {}, s = e.split(/\r\n|[\n\v\f\r\x85]/), u = e.match(/\r\n|[\n\v\f\r\x85]/g) || [], r = [], a = 0; function n() { var e = {}; for (r.push(e); a < s.length;) { var n = s[a]; if (/^(\-\-\-|\+\+\+|@@)\s/.test(n)) break; n = /^(?:Index:|diff(?: -r \w+)+)\s+(.+?)\s*$/.exec(n); n && (e.index = n[1]), a++ } for (i(e), i(e), e.hunks = []; a < s.length;) { var t = s[a]; if (/^(Index:|diff|\-\-\-|\+\+\+)\s/.test(t)) break; if (/^@@/.test(t)) e.hunks.push(function () { var e = a, n = s[a++].split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/), t = { oldStart: +n[1], oldLines: void 0 === n[2] ? 1 : +n[2], newStart: +n[3], newLines: void 0 === n[4] ? 1 : +n[4], lines: [], linedelimiters: [] }; 0 === t.oldLines && (t.oldStart += 1); 0 === t.newLines && (t.newStart += 1); for (var r = 0, i = 0; a < s.length && !(0 === s[a].indexOf("--- ") && a + 2 < s.length && 0 === s[a + 1].indexOf("+++ ") && 0 === s[a + 2].indexOf("@@")); a++) { var o = 0 == s[a].length && a != s.length - 1 ? " " : s[a][0]; if ("+" !== o && "-" !== o && " " !== o && "\\" !== o) break; t.lines.push(s[a]), t.linedelimiters.push(u[a] || "\n"), "+" === o ? r++ : "-" === o ? i++ : " " === o && (r++, i++) } r || 1 !== t.newLines || (t.newLines = 0); i || 1 !== t.oldLines || (t.oldLines = 0); if (l.strict) { if (r !== t.newLines) throw new Error("Added line count did not match for hunk at line " + (e + 1)); if (i !== t.oldLines) throw new Error("Removed line count did not match for hunk at line " + (e + 1)) } return t }()); else { if (t && l.strict) throw new Error("Unknown line " + (a + 1) + " " + JSON.stringify(t)); a++ } } } function i(e) { var n, t, r = /^(---|\+\+\+)\s+(.*)$/.exec(s[a]); r && (n = "---" === r[1] ? "old" : "new", r = (t = r[2].split("\t", 2))[0].replace(/\\\\/g, "\\"), /^".*"$/.test(r) && (r = r.substr(1, r.length - 2)), e[n + "FileName"] = r, e[n + "Header"] = (t[1] || "").trim(), a++) } for (; a < s.length;)n(); return r } function y(e, n) { var t = 2 < arguments.length && void 0 !== arguments[2] ? arguments[2] : {}; if ("string" == typeof n && (n = P(n)), Array.isArray(n)) { if (1 < n.length) throw new Error("applyPatch only works with a single input."); n = n[0] } var r, i, o = e.split(/\r\n|[\n\v\f\r\x85]/), l = e.match(/\r\n|[\n\v\f\r\x85]/g) || [], s = n.hunks, u = t.compareLine || function (e, n, t, r) { return n === r }, a = 0, f = t.fuzzFactor || 0, d = 0, c = 0; for (var h = 0; h < s.length; h++) { for (var p = s[h], v = o.length - p.oldLines, g = 0, m = c + p.oldStart - 1, w = function (n, t, r) { var i = !0, o = !1, l = !1, s = 1; return function e() { if (i && !l) { if (o ? s++ : i = !1, n + s <= r) return s; l = !0 } if (!o) return l || (i = !0), t <= n - s ? -s++ : (o = !0, e()) } }(m, d, v); void 0 !== g; g = w())if (function (e, n) { for (var t = 0; t < e.lines.length; t++) { var r = e.lines[t], i = 0 < r.length ? r[0] : " ", r = 0 < r.length ? r.substr(1) : r; if (" " === i || "-" === i) { if (!u(n + 1, o[n], i, r) && f < ++a) return; n++ } } return 1 }(p, m + g)) { p.offset = c += g; break } if (void 0 === g) return !1; d = p.offset + p.oldStart + p.oldLines } for (var y = 0, L = 0; L < s.length; L++) { var x = s[L], S = x.oldStart + x.offset + y - 1; y += x.newLines - x.oldLines; for (var k = 0; k < x.lines.length; k++) { var b = x.lines[k], F = 0 < b.length ? b[0] : " ", N = 0 < b.length ? b.substr(1) : b, b = x.linedelimiters[k]; " " === F ? S++ : "-" === F ? (o.splice(S, 1), l.splice(S, 1)) : "+" === F ? (o.splice(S, 0, N), l.splice(S, 0, b), S++) : "\\" === F && ("+" === (F = x.lines[k - 1] ? x.lines[k - 1][0] : null) ? r = !0 : "-" === F && (i = !0)) } } if (r) for (; !o[o.length - 1];)o.pop(), l.pop(); else i && (o.push(""), l.push("\n")); for (var H = 0; H < o.length - 1; H++)o[H] = o[H] + l[H]; return o.join("") } function L(e, n, l, s, t, r, u) { void 0 === (u = u || {}).context && (u.context = 4); var a = m(l, s, u); function f(e) { return e.map(function (e) { return " " + e }) } a.push({ value: "", lines: [] }); for (var d = [], c = 0, h = 0, p = [], v = 1, g = 1, i = 0; i < a.length; i++)!function (e) { var n, t, r, i = a[e], o = i.lines || i.value.replace(/\n$/, "").split("\n"); i.lines = o, i.added || i.removed ? (c || (t = a[e - 1], c = v, h = g, t && (p = 0 < u.context ? f(t.lines.slice(-u.context)) : [], c -= p.length, h -= p.length)), p.push.apply(p, w(o.map(function (e) { return (i.added ? "+" : "-") + e }))), i.added ? g += o.length : v += o.length) : (c && (o.length <= 2 * u.context && e < a.length - 2 ? p.push.apply(p, w(f(o))) : (r = Math.min(o.length, u.context), p.push.apply(p, w(f(o.slice(0, r)))), n = { oldStart: c, oldLines: v - c + r, newStart: h, newLines: g - h + r, lines: p }, e >= a.length - 2 && o.length <= u.context && (t = /\n$/.test(l), r = /\n$/.test(s), e = 0 == o.length && p.length > n.oldLines, !t && e && 0 < l.length && p.splice(n.oldLines, 0, "\\ No newline at end of file"), (t || e) && r || p.push("\\ No newline at end of file")), d.push(n), h = c = 0, p = [])), v += o.length, g += o.length) }(i); return { oldFileName: e, newFileName: n, oldHeader: t, newHeader: r, hunks: d } } function x(e, n, t, r, i, o, l) { return function (e) { var n = []; e.oldFileName == e.newFileName && n.push("Index: " + e.oldFileName), n.push("==================================================================="), n.push("--- " + e.oldFileName + (void 0 === e.oldHeader ? "" : "\t" + e.oldHeader)), n.push("+++ " + e.newFileName + (void 0 === e.newHeader ? "" : "\t" + e.newHeader)); for (var t = 0; t < e.hunks.length; t++) { var r = e.hunks[t]; 0 === r.oldLines && --r.oldStart, 0 === r.newLines && --r.newStart, n.push("@@ -" + r.oldStart + "," + r.oldLines + " +" + r.newStart + "," + r.newLines + " @@"), n.push.apply(n, r.lines) } return n.join("\n") + "\n" }(L(e, n, t, r, i, o, l)) } function S(e, n) { if (n.length > e.length) return !1; for (var t = 0; t < n.length; t++)if (n[t] !== e[t]) return !1; return !0 } function k(e) { var n = function r(e) { var i = 0; var o = 0; e.forEach(function (e) { var n, t; "string" != typeof e ? (n = r(e.mine), t = r(e.theirs), void 0 !== i && (n.oldLines === t.oldLines ? i += n.oldLines : i = void 0), void 0 !== o && (n.newLines === t.newLines ? o += n.newLines : o = void 0)) : (void 0 === o || "+" !== e[0] && " " !== e[0] || o++, void 0 === i || "-" !== e[0] && " " !== e[0] || i++) }); return { oldLines: i, newLines: o } }(e.lines), t = n.oldLines, n = n.newLines; void 0 !== t ? e.oldLines = t : delete e.oldLines, void 0 !== n ? e.newLines = n : delete e.newLines } function b(e, n) { if ("string" != typeof e) return e; if (/^@@/m.test(e) || /^Index:/m.test(e)) return P(e)[0]; if (!n) throw new Error("Must provide a base reference or pass in a patch"); return L(void 0, void 0, n, e) } function F(e) { return e.newFileName && e.newFileName !== e.oldFileName } function N(e, n, t) { return n === t ? n : (e.conflict = !0, { mine: n, theirs: t }) } function H(e, n) { return e.oldStart < n.oldStart && e.oldStart + e.oldLines < n.oldStart } function C(e, n) { return { oldStart: e.oldStart, oldLines: e.oldLines, newStart: e.newStart + n, newLines: e.newLines, lines: e.lines } } function j(e, n, t, r) { var i = O(n), n = function (e, n) { var t = [], r = [], i = 0, o = !1, l = !1; for (; i < n.length && e.index < e.lines.length;) { var s = e.lines[e.index], u = n[i]; if ("+" === u[0]) break; if (o = o || " " !== s[0], r.push(u), i++, "+" === s[0]) for (l = !0; "+" === s[0];)t.push(s), s = e.lines[++e.index]; u.substr(1) === s.substr(1) ? (t.push(s), e.index++) : l = !0 } "+" === (n[i] || "")[0] && o && (l = !0); if (l) return t; for (; i < n.length;)r.push(n[i++]); return { merged: r, changes: t } }(t, i); n.merged ? (t = e.lines).push.apply(t, w(n.merged)) : z(e, r ? n : i, r ? i : n) } function z(e, n, t) { e.conflict = !0, e.lines.push({ conflict: !0, mine: n, theirs: t }) } function A(e, n, t) { for (; n.offset < t.offset && n.index < n.lines.length;) { var r = n.lines[n.index++]; e.lines.push(r), n.offset++ } } function E(e, n) { for (; n.index < n.lines.length;) { var t = n.lines[n.index++]; e.lines.push(t) } } function O(e) { for (var n = [], t = e.lines[e.index][0]; e.index < e.lines.length;) { var r = e.lines[e.index]; if ("-" === t && "+" === r[0] && (t = "+"), t !== r[0]) break; n.push(r), e.index++ } return n } function I(e) { return e.reduce(function (e, n) { return e && "-" === n[0] }, !0) } function $(e, n, t) { for (var r = 0; r < t; r++) { var i = n[n.length - t + r].substr(1); if (e.lines[e.index + r] !== " " + i) return } return e.index += t, 1 } g.tokenize = function (e) { return e.slice() }, g.join = g.removeEmpty = function (e) { return e }, e.Diff = t, e.applyPatch = y, e.applyPatches = function (e, i) { "string" == typeof e && (e = P(e)); var n = 0; !function t() { var r = e[n++]; if (!r) return i.complete(); i.loadFile(r, function (e, n) { return e ? i.complete(e) : (n = y(n, r, i), void i.patched(r, n, function (e) { return e ? i.complete(e) : void t() })) }) }() }, e.canonicalize = v, e.convertChangesToDMP = function (e) { for (var n, t, r = [], i = 0; i < e.length; i++)t = (n = e[i]).added ? 1 : n.removed ? -1 : 0, r.push([t, n.value]); return r }, e.convertChangesToXML = function (e) { for (var n = [], t = 0; t < e.length; t++) { var r = e[t]; r.added ? n.push("<ins>") : r.removed && n.push("<del>"), n.push(r.value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;")), r.added ? n.push("</ins>") : r.removed && n.push("</del>") } return n.join("") }, e.createPatch = function (e, n, t, r, i, o) { return x(e, e, n, t, r, i, o) }, e.createTwoFilesPatch = x, e.diffArrays = function (e, n, t) { return g.diff(e, n, t) }, e.diffChars = function (e, n, t) { return r.diff(e, n, t) }, e.diffCss = function (e, n, t) { return f.diff(e, n, t) }, e.diffJson = function (e, n, t) { return p.diff(e, n, t) }, e.diffLines = m, e.diffSentences = function (e, n, t) { return a.diff(e, n, t) }, e.diffTrimmedLines = function (e, n, t) { return t = i(t, { ignoreWhitespace: !0 }), u.diff(e, n, t) }, e.diffWords = function (e, n, t) { return t = i(t, { ignoreWhitespace: !0 }), s.diff(e, n, t) }, e.diffWordsWithSpace = function (e, n, t) { return s.diff(e, n, t) }, e.merge = function (e, n, t) { e = b(e, t), n = b(n, t); var r = {}; (e.index || n.index) && (r.index = e.index || n.index), (e.newFileName || n.newFileName) && (F(e) ? F(n) ? (r.oldFileName = N(r, e.oldFileName, n.oldFileName), r.newFileName = N(r, e.newFileName, n.newFileName), r.oldHeader = N(r, e.oldHeader, n.oldHeader), r.newHeader = N(r, e.newHeader, n.newHeader)) : (r.oldFileName = e.oldFileName, r.newFileName = e.newFileName, r.oldHeader = e.oldHeader, r.newHeader = e.newHeader) : (r.oldFileName = n.oldFileName || e.oldFileName, r.newFileName = n.newFileName || e.newFileName, r.oldHeader = n.oldHeader || e.oldHeader, r.newHeader = n.newHeader || e.newHeader)), r.hunks = []; for (var i = 0, o = 0, l = 0, s = 0; i < e.hunks.length || o < n.hunks.length;) { var u, a = e.hunks[i] || { oldStart: 1 / 0 }, f = n.hunks[o] || { oldStart: 1 / 0 }; H(a, f) ? (r.hunks.push(C(a, l)), i++, s += a.newLines - a.oldLines) : H(f, a) ? (r.hunks.push(C(f, s)), o++, l += f.newLines - f.oldLines) : (function (e, n, t, r, i) { var o, l = { offset: n, lines: t, index: 0 }, s = { offset: r, lines: i, index: 0 }; A(e, l, s), A(e, s, l); for (; l.index < l.lines.length && s.index < s.lines.length;) { var u = l.lines[l.index], a = s.lines[s.index]; "-" !== u[0] && "+" !== u[0] || "-" !== a[0] && "+" !== a[0] ? "+" === u[0] && " " === a[0] ? (o = e.lines).push.apply(o, w(O(l))) : "+" === a[0] && " " === u[0] ? (o = e.lines).push.apply(o, w(O(s))) : "-" === u[0] && " " === a[0] ? j(e, l, s) : "-" === a[0] && " " === u[0] ? j(e, s, l, !0) : u === a ? (e.lines.push(u), l.index++, s.index++) : z(e, O(l), O(s)) : function (e, n, t) { var r, i = O(n), o = O(t); if (I(i) && I(o)) { if (S(i, o) && $(t, i, i.length - o.length)) return (t = e.lines).push.apply(t, w(i)); if (S(o, i) && $(n, o, o.length - i.length)) return (r = e.lines).push.apply(r, w(o)) } else if (function (e, n) { return e.length === n.length && S(e, n) }(i, o)) return (r = e.lines).push.apply(r, w(i)); z(e, i, o) }(e, l, s) } E(e, l), E(e, s), k(e) }(u = { oldStart: Math.min(a.oldStart, f.oldStart), oldLines: 0, newStart: Math.min(a.newStart + l, f.oldStart + s), newLines: 0, lines: [] }, a.oldStart, a.lines, f.oldStart, f.lines), o++, i++, r.hunks.push(u)) } return r }, e.parsePatch = P, e.structuredPatch = L, Object.defineProperty(e, "__esModule", { value: !0 }) });
</script>
<script id="cc">
const slideInLeft = [
{
opacity: 0,
transform: 'translateX(1rem)'
},
{
opacity: 1,
transform: 'translateX(0)'
}
]
const slideOutLeft = [
{
opacity: 1,
transform: 'translateX(0)'
},
{
opacity: 0,
transform: 'translateX(-1rem)'
},
]
const slideInRight = [
{
opacity: 0,
transform: 'translateX(-1rem)'
},
{
opacity: 1,
transform: 'translateX(0)'
}
]
const slideOutRight = [
{
opacity: 1,
transform: 'translateX(0)'
},
{
opacity: 0,
transform: 'translateX(1rem)'
},
]
const slideInDown = [
{
opacity: 0,
transform: 'translateY(-1rem)'
},
{
opacity: 1,
transform: 'translateY(0)'
},
]
const slideOutUp = [
{
opacity: 1,
transform: 'translateY(0)'
},
{
opacity: 0,
transform: 'translateY(-1rem)'
},
]
const cc = {
createNewArticle() {
if (floGlobals.isSubAdmin) {
const title = getRef('get_article_title').value.trim()
// const setDefault = getRef('set_default_checkbox').checked
const plot = getRef('get_plot').value.trim()
const sectionTitles = parsePlot(plot)
let sections = []
if (sectionTitles) {
sections = sectionTitles.map(title => {
return {
id: floCrypto.randString(16, true),
title: title.trim(),
}
})
}
const uid = floCrypto.randString(16, true)
if (!floGlobals.appObjects.cc.wipArticles.includes(uid))
floGlobals.appObjects.cc.wipArticles.push(uid)
floGlobals.appObjects.cc['articleList'][uid] = {
title,
timestamp: Date.now(),
}
floGlobals.appObjects[uid] = {
public: true,
editors: [],
sections,
preview: {},
plot
}
Promise.all([
floCloudAPI.updateObjectData('cc'),
floCloudAPI.resetObjectData(uid)
])
.then((res) => {
closePopup()
notify('created article', 'success')
window.location.hash = `#/home?articleID=${uid}`
})
.catch(err => console.error(err))
} else {
notify('This action requires sub-admin privileges', 'error')
}
}
}
function parsePlot(plotText) {
let tstring = plotText.trim().replace(/\s+/g, " ") // collapse all whitespace to single whitespace
tstring = tstring.replace(/\)\s*\->\s*\(/g, ")->(");
tstring = tstring.split(")->("); // split string based on the delimiter
if (tstring.length > 0) {
if (tstring[0].trim()[0] == "(") {
tstring[0] = tstring[0].trim().slice(1)
}
else {
return false;
}
let lastobj = tstring[tstring.length - 1];
let lastobjlen = lastobj.length;
if (lastobj.trim()[lastobjlen - 1] == ")") {
tstring[tstring.length - 1] = lastobj.trim().slice(0, lastobjlen - 1)
}
else {
return false;
}
}
else {
return false;
}
return tstring;
}
function markAsWIP() {
if (floGlobals.isSubAdmin) {
getConfirmation('Mark as WIP article?').then(res => {
if (res) {
if (!floGlobals.appObjects.cc.wipArticles.includes(floGlobals.currentArticle.id)) {
floGlobals.appObjects.cc.wipArticles.filter(v => v).push(floGlobals.currentArticle.id)
floCloudAPI.updateObjectData('cc')
.then((res) => {
notify('Marked current article as WIP', 'success')
if (floGlobals.appObjects.cc.wipArticles.includes(floGlobals.currentArticle.id)) {
getRef('mark_as_done').classList.remove('hidden')
getRef('mark_as_wip').classList.add('hidden')
} else {
getRef('mark_as_done').classList.add('hidden')
getRef('mark_as_wip').classList.remove('hidden')
}
})
.catch(err => console.error(err))
} else {
notify('Current article is already default', 'error')
}
}
})
} else {
notify('This action requires sub-admin privileges', 'error')
}
}
function markAsDone() {
if (floGlobals.isSubAdmin) {
getConfirmation('Mark as done article?').then(res => {
if (res) {
if (floGlobals.appObjects.cc.wipArticles.includes(floGlobals.currentArticle.id)) {
floGlobals.appObjects.cc.wipArticles = floGlobals.appObjects.cc.wipArticles.filter(id => id && id !== floGlobals.currentArticle.id)
floCloudAPI.updateObjectData('cc')
.then((res) => {
notify('Marked current article as done', 'success')
if (floGlobals.appObjects.cc.wipArticles.includes(floGlobals.currentArticle.id)) {
getRef('mark_as_done').classList.remove('hidden')
getRef('mark_as_wip').classList.add('hidden')
} else {
getRef('mark_as_done').classList.add('hidden')
getRef('mark_as_wip').classList.remove('hidden')
}
})
.catch(err => console.error(err))
} else {
notify('Current article is already done', 'error')
}
}
})
} else {
notify('This action requires sub-admin privileges', 'error')
}
}
function renderPreview() {
const composedDocumentStructure = formatSelectedContent()
let frag = document.createDocumentFragment()
for (const section in composedDocumentStructure) {
frag.append(createElement('h3', {
textContent: floGlobals.currentArticle.sections[section].title
}))
composedDocumentStructure[section].forEach(({ content, uid }) => {
const group = createElement('section', {
className: 'preview-group',
attributes: { 'data-section-id': section, 'data-uid': uid },
innerHTML: content
})
group.append(createElement('div', {
className: 'preview-group__buttons flex align-center',
innerHTML: `
<button class="formatting-button move-up" title="Move up">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/></svg>
</button>
<button class="formatting-button remove-group" title="Remove from exporting">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M7 11v2h10v-2H7zm5-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/></svg>
</button>
`
}))
frag.append(group)
})
}
getRef('preview__title').textContent = floGlobals.appObjects.cc.articleList[floGlobals.currentArticle.id].title
getRef('preview__body').innerHTML = ''
getRef('preview__body').append(frag)
window.location.hash = `#/preview?articleID=${floGlobals.currentArticle.id}`
}
function getReadingTime(content) {
const wpm = 250;
const words = content.trim().split(/\s+/).length;
return Math.floor(words / wpm);
}
function downloadHTML(string, options = {}) {
const { title } = options
const element = html.node`<a href="${`${`data:text/html;charset=utf-8, ${encodeURIComponent(string)}`}`}" download="${`${title ? title : 'article'}.html`}" class="hidden"></a>`
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
function getCleanExportContent() {
const exportContent = getRef('preview__body').cloneNode(true)
exportContent.querySelectorAll('.preview-group').forEach(section => {
section.lastElementChild.remove()
section.replaceWith(...section.childNodes)
})
return DOMPurify.sanitize(exportContent.innerHTML, { FORBID_ATTR: ['style'], ADD_ATTR: ['target'] })
}
function exportSelection() {
const bodyTemplate = getRef('body_template').content.cloneNode(true)
const articleTitle = floGlobals.appObjects.cc.articleList[floGlobals.currentArticle.id].title
bodyTemplate.querySelector('#exported_title').textContent = articleTitle
bodyTemplate.querySelector('#exported_time').textContent = `Exported on ${getFormattedTime(Date.now())}`
bodyTemplate.querySelector('#export_body').innerHTML = getCleanExportContent()
let allContributors = new Set()
selectedContent.forEach(({ contributors }) => {
contributors.forEach(id => allContributors.add(id))
});
const frag = document.createDocumentFragment()
allContributors.forEach(id => {
frag.append(html.node`<div class="contributor">${id}</div>`)
})
bodyTemplate.querySelector('#article_contributors').append(frag)
const readingTime = getReadingTime(bodyTemplate.querySelector('#export_body').textContent)
bodyTemplate.querySelector('#reading_time').textContent = `${readingTime} min read`
let bodyAttributes = ''
if (!floGlobals.isSubAdmin) {
bodyTemplate.querySelector('header button').remove()
}
const bodyHTML = createElement('div')
bodyHTML.append(bodyTemplate)
const headTemplate = getRef('head_template').content.cloneNode(true)
headTemplate.querySelector('title').textContent = articleTitle
const headHTML = createElement('div')
headHTML.append(headTemplate)
const finalMarkup = `
<!DOCTYPE html>
<html lang="en">
<head>${headHTML.innerHTML}<\/head>
<body data-theme="light" ${bodyAttributes}>
${bodyHTML.innerHTML}
<\/body>
<\/html>
`
downloadHTML(finalMarkup, { title: articleTitle })
}
function goHome() {
window.location.hash = `#/home?articleID=${pagesData.params.articleID}`
}
function sharePreview() {
if (floGlobals.isSubAdmin) {
let allContributors = new Set()
selectedContent.forEach(({ contributors }) => {
contributors.forEach(id => allContributors.add(id))
});
if (floGlobals.appObjects[pagesData.params.articleID]?.preview?.id) {
floGlobals.appObjects[pagesData.params.articleID].preview.content = DOMPurify.sanitize(getRef('preview__body').innerHTML, { FORBID_ATTR: ['style'], ADD_ATTR: ['target'] })
} else {
floGlobals.appObjects[pagesData.params.articleID].preview = {
uid: floCrypto.randString(16, true),
content: DOMPurify.sanitize(getRef('preview__body').innerHTML, { FORBID_ATTR: ['style'], ADD_ATTR: ['target'] }),
contributors: [...allContributors]
}
}
floCloudAPI.updateObjectData(pagesData.params.articleID)
.then(() => {
notify('Created preview', 'success')
const uid = floGlobals.appObjects[pagesData.params.articleID].preview.uid
history.replaceState(null, null, `#/preview?articleID=${pagesData.params.articleID}&uid=${uid}`)
if (window.navigator.share) {
navigator.share({
title: "Share article preview",
url: window.location.href
})
} else {
getRef('shared_url').value = window.location.href
openPopup('share_popup')
}
})
.catch(err => notify(err, 'error'))
} else {
notify('This action requires sub-admin privileges', 'error')
}
}
function publishArticle() {
if (floGlobals.isSubAdmin) {
getConfirmation('Request publishing?', {
message: 'Send this article as publishing candidate on RanchiMall TImes',
confirmText: 'Request'
})
.then(res => {
if (res) {
const content = getCleanExportContent();
const readTime = getReadingTime(createElement('div', { innerHTML: content }).textContent)
let allContributors = new Set()
selectedContent.forEach(({ contributors }) => {
contributors.forEach(id => allContributors.add(id))
});
floCloudAPI.sendGeneralData({
articleID: pagesData.params.articleID,
title: floGlobals.appObjects.cc.articleList[floGlobals.currentArticle.id].title,
content,
readTime,
contributors: [...allContributors],
}, 'publishing_requests', {
application: 'RM_Times',
receiverID: 'FF5pewfsJxyrCvg8a2C8VXefeyVvKvQxmF'
})
.then(res => {
console.log(res)
notify('Publication request sent', 'success')
})
.catch(err => notify(err, 'error'))
}
})
} else {
notify('This action requires sub-admin privileges', 'error')
}
}
getRef('preview_page').addEventListener('click', e => {
if (e.target.closest('.move-up')) {
const currentElement = e.target.closest('.preview-group')
const previousSibling = currentElement.previousElementSibling
if (previousSibling?.dataset.sectionId === currentElement.dataset.sectionId) {
const clone = currentElement.cloneNode(true)
previousSibling.before(clone)
currentElement.remove()
}
} else if (e.target.closest('.move-down')) {
const currentElement = e.target.closest('.preview-group')
const nextSibling = currentElement.nextElementSibling
if (nextSibling?.dataset.sectionId === currentElement.dataset.sectionId) {
const clone = currentElement.cloneNode(true)
nextSibling.after(clone)
currentElement.remove()
}
} else if (e.target.closest('.remove-group')) {
const currentElement = e.target.closest('.preview-group')
const uid = currentElement.dataset.uid
if (floGlobals.currentArticle.id) {
getRef('article_wrapper').querySelector(`.content-card[data-uid="${uid}"] sm-checkbox`).checked = false
}
if ((!currentElement.nextElementSibling || currentElement.nextElementSibling.tagName === 'H3') && currentElement.previousElementSibling.tagName === 'H3') {
currentElement.previousElementSibling.remove()
}
currentElement.remove()
}
})
let activeEntry
getRef('article_wrapper').addEventListener("focusin", e => {
if (e.target.closest('.content__area')) {
const target = e.target.closest('.content__area')
activeEntry = target.closest('.content-card')
document.addEventListener('selectionchange', detectFormatting)
if (target.childNodes[0] === undefined) {
target.innerHTML = `<p><br/></p>`
}
childObserver.observe(target, {
childList: true,
})
target.addEventListener('input', checkEntry)
target.addEventListener('paste', checkEntry)
}
})
getRef('article_wrapper').addEventListener("focusout", e => {
if (e.target.closest('.content__area')) {
const target = e.target.closest('.content__area')
normalizeText(e.target.closest('.content__area'))
document.removeEventListener('selectionchange', detectFormatting)
target.removeEventListener('input', checkEntry)
target.removeEventListener('paste', checkEntry)
const selection = window.getSelection()
if (!e.relatedTarget?.closest('#text_toolbar')) {
getRef('text_toolbar').classList.add('hidden')
childObserver.disconnect()
activeEntry = undefined
}
}
})
const childObserver = new MutationObserver((mutations, observer) => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
if (mutation.removedNodes.length && mutation.target.childNodes[0] === undefined) {
observer.disconnect()
mutation.target.innerHTML = `<p><br/></p>`
childObserver.observe(mutation.target, {
childList: true,
})
}
}
})
})
document.execCommand("defaultParagraphSeparator", false, "p");
function formatDoc(sCmd, sValue) {
document.execCommand(sCmd, false, sValue);
checkEntry()
}
function incScore(value) {
let currentScore = parseFloat(getRef('update_score_field').value)
if (!currentScore) {
currentScore = 0
}
if (currentScore + value <= 100) {
currentScore += value;
getRef('update_score_field').value = parseFloat(currentScore.toFixed(1))
}
else {
notify(`You can't give score more than 100.`, 'error')
}
}
function updateScore() {
const newScore = parseFloat(getRef('update_score_field').value)
updateGenData(floGlobals.versionHistory.currentEntry, { tag: newScore })
document.querySelectorAll(`[data-vector-clock="${floGlobals.versionHistory.currentEntry}"] .content__score`).forEach(scoreElem => scoreElem.textContent = newScore)
document.querySelectorAll(`[data-vector-clock="${floGlobals.versionHistory.currentEntry}"] .content__score`).forEach(scoreElem => newScore > 0 ? scoreElem.parentNode.classList.add('score-button--filled') : scoreElem.parentNode.classList.remove('score-button--filled'))
floCloudAPI.tagApplicationData(floGlobals.versionHistory.currentEntry, newScore).then(res => {
notify('Score updated', 'success')
closePopup()
}).catch(err => notify(err, 'error'))
}
floGlobals.currentArticle = {}
const sectionIntersectionObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const section = entry.target.parentNode
const frag = document.createDocumentFragment();
const arrayOfElements = floGlobals.currentArticle.sections[section.dataset.sectionId].uniqueEntries
const startIndex = floGlobals.isSubAdmin ? section.children.length : section.children.length - 1
arrayOfElements.slice(startIndex, startIndex + 5).forEach(elem => frag.append(render.contentCard(elem)))
entry.target.parentNode.append(frag)
observer.unobserve(entry.target)
}
})
})
const sectionMutationObserver = new MutationObserver(mutationList => {
mutationList.forEach(mutation => {
if (mutation.type === 'childList' && mutation.addedNodes.length) {
sectionIntersectionObserver.observe(mutation.target.lastElementChild)
}
})
})
let isMobileView = false
const mobileQuery = window.matchMedia('(max-width: 40rem)')
function handleMobileChange(e) {
if (e.matches) {
// Mobile view
isMobileView = true
document.querySelectorAll('.article-section').forEach(section => {
sectionMutationObserver.observe(section, { childList: true })
})
} else {
// Desktop view
isMobileView = false
sectionMutationObserver.disconnect()
sectionIntersectionObserver.disconnect()
}
}
mobileQuery.addListener(handleMobileChange)
handleMobileChange(mobileQuery)
function getScoreElement(score) {
let scoreElement
if (floGlobals.isSubAdmin) {
scoreElement = html.node`<button class="score-button" title="Score this content" onclick="initScoreChange(event)"></button>`
} else {
scoreElement = html.node`<div class="flex align-center" title="Score"></div>`
}
if (score > 0) {
scoreElement.classList.add('score-button--filled')
}
scoreElement.innerHTML = `
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><path d="M0,0h24v24H0V0z" fill="none"/><path d="M0,0h24v24H0V0z" fill="none"/></g><g><g><polygon opacity=".3" points="12,15.4 8.24,17.67 9.24,13.39 5.92,10.51 10.3,10.13 12,6.1 13.71,10.14 18.09,10.52 14.77,13.4 15.77,17.68"/><path d="M22,9.24l-7.19-0.62L12,2L9.19,8.63L2,9.24l5.46,4.73L5.82,21L12,17.27L18.18,21l-1.63-7.03L22,9.24z M12,15.4l-3.76,2.27 l1-4.28l-3.32-2.88l4.38-0.38L12,6.1l1.71,4.04l4.38,0.38l-3.32,2.88l1,4.28L12,15.4z"/></g></g></svg>
<div class="content__score">${score}</div>
`
return scoreElement
}
const render = {
article(id, focusMode) {
floGlobals.currentArticle.id = id
parseArticleData(id)
const { title } = floGlobals.appObjects.cc.articleList[id]
const { writer, sections } = floGlobals.currentArticle
let assignedSection = localStorage.getItem(`${id}_assigned_section_${floGlobals.myFloID}`)
const sectionKeys = Object.keys(floGlobals.currentArticle.sections)
if (assignedSection === null && !floGlobals.isSubAdmin) {
const randomIndex = floCrypto.randInt(0, sectionKeys.length - 1)
assignedSection = sectionKeys[randomIndex]
localStorage.setItem(`${id}_assigned_section_${floGlobals.myFloID}`, assignedSection)
}
getRef('current_article_title').textContent = title
getRef('article_wrapper').innerHTML = ''
if (!floGlobals.isSubAdmin && focusMode && floGlobals.appObjects.cc.wipArticles.includes(id)) {
getRef('article_wrapper').append(render.section(assignedSection, sections[assignedSection]))
getRef('article_wrapper').append(html.node`
<div id="focus_mode_panel" class="flex align-center space-between">
<div class="grid gap-0-3">
<h4>Focus mode is on</h4>
<p>
You are only seeing one section assigned to you. Turn off focus mode to see all sections.
</p>
</div>
<div class="flex gap-0-3">
<button class="button" onclick="openPopup('plot_popup')">View plot</button>
<a id="focus_mode_button" href="${`#/home?articleID=${id}&focusMode=false`}" class="button">Turn off</a>
</div>
</div>
`)
floGlobals.focusMode = {
assignedSection,
active: true
}
} else {
const frag = document.createDocumentFragment()
for (const sectionID in sections) {
frag.append(render.section(sectionID, sections[sectionID]))
}
getRef('article_wrapper').append(frag)
floGlobals.focusMode = {
active: false
}
}
render.sectionOutline()
},
articleLink(details, isDefaultArticle, ref) {
const { uid, timestamp, title } = details
return html.for(ref, uid)`<a class="${`article-link flex interact ${isDefaultArticle ? 'default-article' : ''}`}" title=${isDefaultArticle ? 'Actively written article' : ''} href="${`#/home?articleID=${uid}&focusMode=true`}">${title}</a>`
},
contentCard(id, version = 0) {
const { html, contributors, score = 0, vectorClock } = getIterationDetails(id)
const clone = getRef('content_card_template').content.cloneNode(true).firstElementChild;
clone.dataset.uid = id
clone.dataset.vectorClock = vectorClock
if (!floGlobals.isSubAdmin) {
clone.querySelector('.content__area').setAttribute('contentEditable', true)
}
clone.querySelector('.content__area').innerHTML = DOMPurify.sanitize(html, { FORBID_ATTR: ['style'], ADD_ATTR: ['target'] })
let noOfContributors = 0
let latestContributor
for (const contributor in contributors) {
noOfContributors++
latestContributor = contributor
}
clone.querySelector('.content__author').innerHTML = `<div>${latestContributor}</div> ${noOfContributors === 1 ? '' : `<div> and ${noOfContributors - 1} more`}</div>`
clone.querySelector('.content__options').append(getScoreElement(score));
return clone
},
historyEntry(details, oldText) {
const { editor, timestamp, plainText, score, vectorClock } = details
const clone = getRef('history_entry_template').content.cloneNode(true).firstElementChild;
clone.dataset.vectorClock = vectorClock
clone.querySelector('.entry__time').textContent = getFormattedTime(timestamp)
clone.querySelector('.entry__time').after(getScoreElement(score))
clone.querySelector('.entry__author').textContent = editor
if (oldText !== '') {
const frag = document.createDocumentFragment()
Diff.diffWords(oldText, plainText).forEach((part) => {
if (part.hasOwnProperty('added') || part.hasOwnProperty('removed')) {
const type = part.added ? 'added' :
part.removed ? 'removed' : '';
frag.append(
createElement('span', {
textContent: part.value,
className: type
})
);
} else {
frag.append(
document.createTextNode(part.value)
)
}
});
clone.querySelector('.entry__changes').append(frag)
} else {
clone.querySelector('.entry__changes').textContent = plainText
}
return clone
},
section(sectionID, { title, uniqueEntries }) {
const sortByScore = getRef('sort_content_list').value === 'score'
const section = getRef('section_template').content.cloneNode(true).firstElementChild
const frag = document.createDocumentFragment()
section.dataset.sectionId = sectionID
section.querySelector('.heading').dataset.sectionId = sectionID
section.querySelector('.article-section').dataset.sectionId = sectionID
if (floGlobals.isSubAdmin) {
section.querySelector('.content-card--empty').remove()
}
section.querySelector('.section-title').textContent = title
floGlobals.currentArticle.sections[sectionID].uniqueEntries.sort((a, b) => {
const arrayA = floGlobals.currentArticle.uniqueEntries[a].iterations
const arrayB = floGlobals.currentArticle.uniqueEntries[b].iterations
return getGenData(arrayB[arrayB.length - 1])[sortByScore ? 'score' : 'timestamp'] - getGenData(arrayA[arrayA.length - 1])[sortByScore ? 'score' : 'timestamp']
}).slice(0, maxCardsPerSection).forEach(entry => {
const contentCard = render.contentCard(entry)
if (contentCard)
frag.append(contentCard)
})
if (floGlobals.currentArticle.sections[sectionID].uniqueEntries.length > maxCardsPerSection) {
section.querySelector('.heading').append(createElement('button', {
className: 'button see-more hide-on-mobile',
textContent: `See ${floGlobals.currentArticle.sections[sectionID].uniqueEntries.length - maxCardsPerSection} more entries`
}))
}
if (isMobileView)
sectionMutationObserver.observe(section.querySelector('.article-section'), { childList: true })
section.querySelector('.article-section').append(frag)
headingIntersectionObserver.observe(section.querySelector('.article-section'))
return section
},
sectionCard(details) {
const { title, id } = details
return html`
<div class="section-card flex align-center" data-section-id="${id}">
<svg class="icon handle" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/></svg>
<input placeholder="Section title" value="${title}"/>
</div>`
},
sectionOutline() {
const sections = floGlobals.appObjects[floGlobals.currentArticle.id].sections.filter(v => v).map(({ id, title }) => {
let shouldBeDisabled
let assignedState
if (floGlobals.focusMode.active) {
if (floGlobals.focusMode.assignedSection === id) {
assignedState = 'assigned'
assignedState = html`<span style="font-size: 0.8rem">Assigned</span>`
} else {
shouldBeDisabled = true
assignedState = html`<button class="button enable-focus-button" data-section-id="${id}">Assign</button>`
}
}
return html.for(getRef('article_outline'), id)`
<div class="flex gap-0-5 align-center">
<button class="outline-button" data-section-id="${id}" ?disabled=${shouldBeDisabled}>
${title}
</button>
${assignedState}
</div> `
})
renderElem(getRef('article_outline'), html`${sections}`)
if (floGlobals.appObjects[floGlobals.currentArticle.id].hasOwnProperty('plot')) {
getRef('plot_diagram').innerText = floGlobals.appObjects[floGlobals.currentArticle.id].plot.replace(/->/g, '\n').replace(/\(|\)/g, '')
getRef('plot_wrapper').classList.remove('hidden')
} else {
getRef('plot_wrapper').classList.add('hidden')
}
}
}
function parseArticleData(articleID) {
console.log('Parsing article data...', articleID)
const { sections, editors, public } = floGlobals.appObjects[articleID]
const generalData = floGlobals.generalData[`${articleID}_gd|${floGlobals.adminID}|${floGlobals.application}`]
floGlobals.currentArticle['sections'] = {}
sections.forEach(({ id, title }) => {
floGlobals.currentArticle['sections'][id] = {
expanded: false,
title,
uniqueEntries: new Set()
}
})
floGlobals.currentArticle['uniqueEntries'] = {}
for (const key in generalData) {
const { section, origin } = generalData[key].message
if (!floGlobals.currentArticle.uniqueEntries.hasOwnProperty(origin)) { // check if general data has origin that's already defined
floGlobals.currentArticle.uniqueEntries[origin] = {
iterations: []
}
}
if (floGlobals.currentArticle.sections[section]) {
floGlobals.currentArticle.sections[section].uniqueEntries.add(origin)
}
floGlobals.currentArticle.uniqueEntries[origin]['iterations'].push(generalData[key].vectorClock)
}
for (const sectionID in floGlobals.currentArticle.sections) {
floGlobals.currentArticle.sections[sectionID].uniqueEntries = [...floGlobals.currentArticle.sections[sectionID].uniqueEntries].reverse()
}
for (const entry in floGlobals.currentArticle.uniqueEntries) {
floGlobals.currentArticle.uniqueEntries[entry]['iterations'].sort((a, b) => getGenData(a).timestamp - getGenData(b).timestamp)
}
}
function getGenData(vectorClock) {
const { message: { section, origin, plainText, html, hash }, senderID, time, tag } = floGlobals.generalData[`${floGlobals.currentArticle.id}_gd|${floGlobals.adminID}|${floGlobals.application}`][vectorClock]
let tempDiv
if (!plainText) {
// convert html to innerText
tempDiv = createElement('div', {
innerHTML: html
})
}
return {
plainText: plainText ? plainText : tempDiv.innerText,
html,
editor: senderID,
hash,
score: tag ? tag : 0,
timestamp: time,
vectorClock,
}
}
function updateGenData(vectorClock, propsToUpdate = {}) {
floGlobals.generalData[`${floGlobals.currentArticle.id}_gd|${floGlobals.adminID}|${floGlobals.application}`][vectorClock] = { ...floGlobals.generalData[`${floGlobals.currentArticle.id}_gd|${floGlobals.adminID}|${floGlobals.application}`][vectorClock], ...propsToUpdate }
}
const checkEntry = debounce(e => {
const contentCard = activeEntry
const contentArea = contentCard.querySelector('.content__area')
const uid = contentCard.dataset.uid
const isUniqueEntry = contentArea.dataset.type === 'origin'
if (contentArea.innerText.trim() === '') {
contentCard.querySelector('.submit-entry').classList.add('hidden')
} else {
const cleanHTML = DOMPurify.sanitize(contentArea.innerHTML.split('\n').map(v => v.trim()).filter(v => v).join('\n'), { FORBID_ATTR: ['style'], ADD_ATTR: ['target'] })
const hash = Crypto.SHA256(cleanHTML)
let previousHash
if (!isUniqueEntry) {
({ hash: previousHash = '' } = getIterationDetails(uid))
} else {
previousHash = ''
}
if (previousHash !== hash)
contentCard.querySelector('.submit-entry').classList.remove('hidden')
else
contentCard.querySelector('.submit-entry').classList.add('hidden')
}
}, 300)
getRef('article_wrapper').addEventListener('click', e => {
if (e.target.closest('.submit-entry')) {
const submitButton = e.target.closest('.submit-entry')
const contentCard = e.target.closest('.content-card')
const contentArea = contentCard.querySelector('.content__area')
const uid = contentCard.dataset.uid
const isUniqueEntry = contentArea.dataset.type === 'origin'
const parentSection = contentCard.closest('.article-section')
const sectionID = parentSection.dataset.sectionId
const plainText = contentArea.innerText.trim()
if (plainText === '') return
if (contentArea.firstChild.nodeType === Node.TEXT_NODE) {
const clone = contentArea.firstChild.cloneNode(true)
const p = createElement('p')
p.append(clone)
contentArea.firstChild.remove()
contentArea.prepend(p)
}
const cleanHTML = DOMPurify.sanitize(contentArea.innerHTML.split('\n').map(v => v.trim()).filter(v => v).join('\n'), { FORBID_ATTR: ['style'], ADD_ATTR: ['target'] })
const hash = Crypto.SHA256(cleanHTML)
let previousVersion, previousHash
if (!isUniqueEntry) {
({ html: previousVersion, hash: previousHash = '', contributors } = getIterationDetails(uid))
} else {
previousHash = ''
}
if (previousHash !== hash) {
const timestamp = Date.now()
const entry = {
section: sectionID,
origin: isUniqueEntry ? floCrypto.randString(16, true) : uid,
plainText,
html: cleanHTML,
hash
}
submitButton.innerHTML = `<sm-spinner></sm-spinner>`
submitButton.disabled = true
floCloudAPI.sendGeneralData(entry, `${floGlobals.currentArticle.id}_gd`)
.then((res) => {
// Add result to general data
floGlobals.generalData[`${floGlobals.currentArticle.id}_gd|${floGlobals.adminID}|${floGlobals.application}`][res.vectorClock] = { ...res, message: entry }
submitButton.classList.add('hidden')
notify('Content submitted', 'success')
if (isUniqueEntry) {
contentArea.innerHTML = ''
floGlobals.currentArticle.sections[sectionID].uniqueEntries.push(entry.origin)
floGlobals.currentArticle.uniqueEntries[entry.origin] = { iterations: [res.vectorClock] }
// Insert new content card based on set filter
const newCard = render.contentCard(entry.origin)
if (getRef('sort_content_list').value === 'time') {
parentSection.firstElementChild.after(newCard)
newCard.animate(slideInRight, {
duration: 300,
fill: 'forwards',
easing: 'ease'
})
const width = newCard.getBoundingClientRect().width
const siblings = Array.from(newCard.parentNode.children)
const nextSiblings = siblings.slice(siblings.indexOf(newCard) + 1)
nextSiblings.forEach(elem => {
elem.animate([
{
transform: `translateX(-${width}px)`
},
{
transform: 'translateX(0)'
},
], {
duration: 200,
fill: 'forwards',
easing: 'ease'
})
.onfinish = (e) => {
e.target.cancel()
}
})
} else {
parentSection.append(newCard)
}
} else {
let noOfContributors = 0
for (const contributor in contributors) {
noOfContributors++
if (noOfContributors === 2)
break
}
if (noOfContributors < 2 && !contributors.hasOwnProperty(floGlobals.myFloID)) {
contentCard.querySelector('.content__author').textContent = `${floGlobals.myFloID} and 1 more`
}
contentCard.querySelector('.content__score').textContent = 0
contentCard.querySelector('.content__score').parentNode.classList.remove('score-button--filled')
floGlobals.currentArticle.uniqueEntries[entry.origin].iterations.push(res.vectorClock)
}
})
.catch(err => console.log(err))
.finally(() => {
submitButton.textContent = 'Submit'
submitButton.disabled = false
})
} else {
notify("Duplicate entry!", 'error')
}
} else if (e.target.closest('.version-history-button')) {
const entryUid = e.target.closest('.content-card').dataset.uid
if (floGlobals.versionHistory.isOpen && entryUid === floGlobals.versionHistory.entryUid)
hideVersionHistory()
else
showVersionHistory(entryUid)
} else if (e.target.closest('.content__contributors')) {
const entryUid = e.target.closest('.content-card').dataset.uid
const { contributors } = getIterationDetails(entryUid)
const frag = document.createDocumentFragment()
getRef('contributor_list').innerHTML = ''
for (const floID in contributors) {
const contributor = getRef('contributor_template').content.cloneNode(true)
contributor.querySelector('.contributor__id').textContent = floID
contributor.querySelector('.contributor__time').textContent = `Last edited: ${getFormattedTime(contributors[floID])}`
frag.prepend(contributor)
}
getRef('contributor_list').append(frag)
openPopup('contributors_popup')
} else if (e.target.closest('.see-more')) {
const target = e.target.closest('.see-more')
const sectionID = target.parentNode.dataset.sectionId
if (floGlobals.currentArticle.sections[sectionID].expanded) {
target.textContent = `See ${floGlobals.currentArticle.sections[sectionID].uniqueEntries.length - maxCardsPerSection} more entries`;
[...target.parentNode.nextElementSibling.children].slice(3).forEach(card => {
card.querySelector('sm-checkbox').checked = false
card.remove()
})
floGlobals.currentArticle.sections[sectionID].expanded = false
} else {
target.textContent = 'See less'
floGlobals.currentArticle.sections[sectionID].uniqueEntries.slice(maxCardsPerSection).forEach(entry => {
const contentCard = render.contentCard(entry)
if (contentCard)
target.parentNode.nextElementSibling.append(contentCard)
})
floGlobals.currentArticle.sections[sectionID].expanded = true
}
}
})
function initScoreChange(e) {
floGlobals.versionHistory.currentEntry = e.target.closest('.history-entry, .content-card').dataset.vectorClock
getRef('update_score_field').value = e.target.closest('.score-button').children[1].textContent
openPopup('scoring_popup')
getRef('update_score_form')._checkValidity()
}
getRef('article_wrapper').addEventListener("paste", e => {
const paste = (event.clipboardData || window.clipboardData).getData('text');
const selection = window.getSelection();
if (!selection.rangeCount) return false;
selection.deleteFromDocument();
selection.getRangeAt(0).insertNode(document.createTextNode(paste));
event.preventDefault();
})
const selectedContent = new Map()
let isContentSelected = false
getRef('article_wrapper').addEventListener("change", e => {
if (e.target.closest('sm-checkbox')) {
const contentCard = e.target.closest('.content-card')
const contentID = contentCard.dataset.uid
contentCard.classList.toggle('selected')
if (!floGlobals.isSubAdmin)
contentCard.querySelector('.content__area').toggleAttribute('contenteditable')
if (selectedContent.has(contentID)) {
selectedContent.delete(contentID)
} else {
const sectionID = contentCard.closest('.article-section').dataset.sectionId
const content = DOMPurify.sanitize(contentCard.querySelector('.content__area').innerHTML.split('\n').map(v => v.trim()).filter(v => v).join('\n'), { FORBID_ATTR: ['style'], ADD_ATTR: ['target'] })
.replace(/b>/gi, 'strong>').replace(/i>/gi, 'em>')
const contributors = Object.keys(getIterationDetails(contentID).contributors)
selectedContent.set(contentID, { content, sectionID, contributors })
}
const animOptions = {
duration: 150,
easing: 'ease',
}
const selectedContentSize = selectedContent.size
getRef('selected_entries_no').textContent = selectedContentSize
if (selectedContentSize === 1 && !isContentSelected) {
getRef('article_name_wrapper').classList.remove('hidden')
getRef('selected_content_options').classList.add('hidden')
animateTo(getRef('article_name_wrapper'), slideOutLeft, animOptions)
.onfinish = () => {
isContentSelected = true
getRef('article_name_wrapper').classList.add('hidden')
getRef('selected_content_options').classList.remove('hidden')
animateTo(getRef('selected_content_options'), slideInLeft, animOptions)
}
} else if (selectedContent.size === 0) {
getRef('article_name_wrapper').classList.add('hidden')
getRef('selected_content_options').classList.remove('hidden')
animateTo(getRef('selected_content_options'), slideOutRight, animOptions)
.onfinish = () => {
isContentSelected = false
getRef('article_name_wrapper').classList.remove('hidden')
getRef('selected_content_options').classList.add('hidden')
animateTo(getRef('article_name_wrapper'), slideInRight, animOptions)
}
}
}
})
function formatSelectedContent() {
const composedDocumentStructure = {}
floGlobals.appObjects[floGlobals.currentArticle.id].sections.forEach(section => composedDocumentStructure[section.id] = [])
selectedContent.forEach(({ content, sectionID }, uid) => {
composedDocumentStructure[sectionID].push({ content, uid })
})
for (const section in composedDocumentStructure) {
if (!composedDocumentStructure[section].length)
delete composedDocumentStructure[section]
}
return composedDocumentStructure
}
function selectAll() {
getRef('article_wrapper').querySelectorAll('sm-checkbox').forEach(checkbox => checkbox.checked = true)
}
function clearSelection() {
selectedContent.forEach((v, key) => {
getRef('article_wrapper').querySelector(`.content-card[data-uid="${key}"] sm-checkbox`).checked = false
})
selectedContent.clear()
}
function toggleOptionsPanel(type) {
const animInOptions = {
duration: 200,
fill: 'forwards',
easing: 'ease'
}
const animOutOptions = {
duration: 100,
fill: 'forwards',
easing: 'ease'
}
if (getRef('options_panel').classList.contains('hidden')) {
getRef('options_panel').classList.remove('hidden')
getRef('options_panel').animate(slideInDown, animInOptions)
getRef('article_wrapper').animate([
{
transform: 'translateY(-3rem)'
}, {
transform: 'translateY(0)'
}
], animInOptions)
} else {
getRef('options_panel').animate(slideOutUp, animOutOptions)
.onfinish = () => {
getRef('options_panel').classList.add('hidden')
}
getRef('article_wrapper').animate([
{
transform: 'translateY(0)'
}, {
transform: 'translateY(-3rem)'
}
], animOutOptions).onfinish = e => e.target.cancel()
}
}
getRef('article_outline').addEventListener('click', e => {
if (e.target.closest('.outline-button')) {
const sectionID = e.target.closest('.outline-button').dataset.sectionId
const target = document.querySelector(`.heading[data-section-id="${sectionID}"]`)
if (!target) return;
getRef('article_wrapper').scrollTo({
behavior: 'smooth',
top: target.getBoundingClientRect().top - getRef('article_wrapper').getBoundingClientRect().top + getRef('article_wrapper').scrollTop
})
closePopup()
} else if (e.target.closest('.enable-focus-button')) {
const sectionID = e.target.closest('.enable-focus-button').dataset.sectionId
localStorage.setItem(`${floGlobals.currentArticle.id}_assigned_section_${floGlobals.myFloID}`, sectionID)
location.hash = `#/home?articleID=${floGlobals.currentArticle.id}&focusMode=true`
render.article(floGlobals.currentArticle.id, true)
closePopup()
}
})
function toggleFocusMode() {
closePopup()
floGlobals.focusMode.active = !floGlobals.focusMode.active
location.hash = `#/home?articleID=${floGlobals.currentArticle.id}&focusMode=${floGlobals.focusMode.active}`
}
async function refreshArticle() {
await Promise.all([
floCloudAPI.requestObjectData(floGlobals.currentArticle.id),
floCloudAPI.requestGeneralData(`${floGlobals.currentArticle.id}_gd`)
])
render.article(floGlobals.currentArticle.id, floGlobals.focusMode.active)
notify('Article refreshed', 'success')
}
function transformScroll(event) {
if (!event.deltaY) {
return;
}
event.currentTarget.scrollBy({ left: event.deltaY * 5, behavior: 'smooth' });
event.preventDefault();
}
getRef('options_panel').addEventListener('wheel', transformScroll);
const activeHeading = debounce((target) => {
getRef('article_outline').querySelectorAll('.outline-button').forEach(elem => elem.classList.remove('outline-button--active'))
target.classList.add('outline-button--active')
target.scrollIntoView({ behavior: "smooth", block: "center" });
}, 150)
const headingIntersectionObserver = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
activeHeading(getRef('article_outline').querySelector(`[data-section-id="${entry.target.dataset.sectionId}"]`))
}
})
}, {
threshold: 0.6
})
function sortSectionEntries() {
const sortByScore = getRef('sort_content_list').value === 'score'
const originalObj = {}
for (const sectionID in floGlobals.currentArticle.sections) {
originalObj[sectionID] = [...floGlobals.currentArticle.sections[sectionID].uniqueEntries]
floGlobals.currentArticle.sections[sectionID].uniqueEntries.sort((a, b) => {
const arrayA = floGlobals.currentArticle.uniqueEntries[a].iterations
const arrayB = floGlobals.currentArticle.uniqueEntries[b].iterations
return getGenData(arrayB[arrayB.length - 1])[sortByScore ? 'score' : 'timestamp'] - getGenData(arrayA[arrayA.length - 1])[sortByScore ? 'score' : 'timestamp']
})
}
for (const sectionID in floGlobals.currentArticle.sections) {
if (floGlobals.currentArticle.sections[sectionID].uniqueEntries.some((v, index) => v !== originalObj[sectionID][index])) {
const allContentCards = {}
const section = getRef('article_wrapper').querySelector(`.article-section[data-section-id="${sectionID}"]`)
section.querySelectorAll('.content-card').forEach(card => {
allContentCards[card.dataset.uid] = card.cloneNode(true)
})
let emptyCard
if (!floGlobals.isSubAdmin)
emptyCard = section.firstElementChild.cloneNode(true)
section.innerHTML = ''
const frag = document.createDocumentFragment()
let toRender
if (floGlobals.currentArticle.sections[sectionID].expanded)
toRender = floGlobals.currentArticle.sections[sectionID].uniqueEntries
else
toRender = floGlobals.currentArticle.sections[sectionID].uniqueEntries.slice(0, maxCardsPerSection)
toRender.forEach((entry, index) => {
frag.append(allContentCards[entry] || render.contentCard(entry))
})
if (!floGlobals.isSubAdmin)
section.append(emptyCard)
section.append(frag)
}
}
}
getRef('sort_content_list').addEventListener('change', sortSectionEntries)
function renderSectionList() {
if (!floGlobals.isSubAdmin) return
getRef('edit_article_title').value = floGlobals.appObjects.cc.articleList[floGlobals.currentArticle.id].title
const sections = floGlobals.appObjects[floGlobals.currentArticle.id].sections.filter(v => v).map(section => {
return render.sectionCard(section)
})
renderElem(getRef('section_list_container'), html`${sections}`)
}
function insertEmptySection() {
const emptySection = html.node`
<div class="section-card flex align-center">
<svg class="icon handle" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/></svg>
<input placeholder="New section title"/>
<button class="remove">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none"></path>
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"></path>
</svg>
</button>
</div>
`
getRef('section_list_container').append(emptySection)
emptySection.querySelector('input').focus()
}
getRef('section_list_container').addEventListener('click', e => {
if (e.target.closest('.remove')) {
e.target.closest('.section-card').remove()
}
})
function saveSectionEdit() {
if (floGlobals.isSubAdmin) {
const newArticleTitle = getRef('edit_article_title').value.trim()
if (newArticleTitle !== '') {
if (floGlobals.appObjects.cc.articleList[floGlobals.currentArticle.id].title !== newArticleTitle) {
floGlobals.appObjects.cc.articleList[floGlobals.currentArticle.id].title = newArticleTitle
floCloudAPI.updateObjectData('cc')
.then((res) => {
getRef('current_article_title').textContent = newArticleTitle
notify('Renamed article', 'success')
}).catch(err => {
notify(err, 'error')
})
}
} else {
getRef('edit_article_title').value = floGlobals.appObjects.cc.articleList[floGlobals.currentArticle.id].title
}
const changedSections = [];
[...getRef('section_list_container').children].forEach(section => {
const title = section.querySelector('input').value.trim()
const sectionID = section.dataset.sectionId
if (title !== '')
changedSections.push({
id: sectionID || floCrypto.randString(16, true),
title
})
})
const didAddSection = changedSections.length > floGlobals.appObjects[floGlobals.currentArticle.id].sections.length
let didTitlesChange = false
if (!didAddSection) {
didTitlesChange = floGlobals.appObjects[floGlobals.currentArticle.id].sections.some(({ title }, index) => title !== changedSections[index].title)
}
if (!didAddSection && !didTitlesChange)
return notify('No changes made')
floGlobals.appObjects[floGlobals.currentArticle.id].sections = changedSections.filter(v => v)
buttonLoader(getRef('save_section_changes'), true)
floCloudAPI.updateObjectData(floGlobals.currentArticle.id)
.then((res) => {
render.sectionOutline()
const frag = document.createDocumentFragment();
const currentSections = {}
getRef('article_wrapper').querySelectorAll('.heading').forEach(heading => {
currentSections[heading.dataset.sectionId] = {
title: heading.textContent,
ref: heading
}
})
changedSections.forEach(elem => {
const { title, id } = elem
if (currentSections.hasOwnProperty(id)) {
// update title
currentSections[id].ref.textContent = title
} else {
// add new section
floGlobals.currentArticle.sections[id] = { title, uniqueEntries: [] }
frag.append(render.section(id, floGlobals.currentArticle.sections[id]))
}
})
getRef('article_wrapper').append(frag)
// rearrange sections in DOM
const sectionOrder = changedSections.map(v => v.id)
const sectionRefs = [...getRef('article_wrapper').children].filter(v => v.dataset.sectionId)
sectionRefs.sort((a, b) => sectionOrder.indexOf(a.dataset.sectionId) - sectionOrder.indexOf(b.dataset.sectionId))
sectionRefs.forEach(section => {
getRef('article_wrapper').append(section)
})
notify('Sections updated', 'success')
closePopup()
})
.catch(err => {
notify(err, 'error')
}).finally(() => {
buttonLoader(getRef('save_section_changes'), false)
})
} else {
closePopup()
notify('This action requires sub-admin privileges', 'error')
}
}
function getArticleList() {
const articleList = floGlobals.appObjects.cc.articleList
const sortBy = getRef('sort_article_list').value
const arrayOfObject = []
for (const key in articleList) {
const { timestamp, title } = articleList[key]
arrayOfObject.push({
uid: key,
timestamp,
title
})
}
if (sortBy === 'az') {
arrayOfObject.sort((a, b) => a.title.localeCompare(b.title, 'en', { numeric: true, sensitivity: 'base' }))
} else {
arrayOfObject.sort((a, b) => b.timestamp - a.timestamp)
}
return arrayOfObject
}
getRef('article_list_search').addEventListener('input', e => {
const searchKey = e.target.value.trim()
const options = {
keys: ['title'],
threshold: 0.3
}
const fuse = new Fuse(getArticleList(), options)
renderArticleList(searchKey === '' ? undefined : fuse.search(searchKey).map(v => v.item))
})
getRef('sort_article_list').addEventListener('change', e => { renderArticleList() })
function renderArticleList(articleList) {
const articlesList = (articleList || getArticleList()).map((article) => {
const isDefaultArticle = floGlobals.appObjects.cc.wipArticles.includes(article.uid)
return render.articleLink(article, isDefaultArticle, getRef('article_list'))
})
renderElem(getRef('article_list'), html`${articlesList}`)
}
function getIterationDetails(uid, targetIndex) {
const contributors = {}
const limit = targetIndex || floGlobals.currentArticle.uniqueEntries[uid].iterations.length - 1
for (let i = 0; i <= limit; i++) {
const { editor, timestamp } = getGenData(floGlobals.currentArticle.uniqueEntries[uid].iterations[i])
contributors[editor] = timestamp
}
const { html, hash, score } = getGenData(floGlobals.currentArticle.uniqueEntries[uid].iterations[limit]);
return {
html,
contributors,
hash,
score,
vectorClock: floGlobals.currentArticle.uniqueEntries[uid].iterations[limit]
}
}
floGlobals.versionHistory = {
isOpen: false,
entryUid: ''
}
function showVersionHistory(uid) {
floGlobals.versionHistory.entryUid = uid
const { iterations } = floGlobals.currentArticle.uniqueEntries[uid]
const frag = document.createDocumentFragment()
iterations.forEach((vectorClock, index) => {
const oldText = index !== 0 ? getGenData(iterations[index - 1]).plainText : ''
frag.prepend(render.historyEntry(getGenData(vectorClock), oldText))
})
getRef('version_timeline').innerHTML = ''
getRef('version_timeline').append(frag)
if (!floGlobals.versionHistory.isOpen) {
getRef('version_history_panel').classList.remove('hidden')
getRef('main_page').classList.add('active-sidebar')
floGlobals.versionHistory.isOpen = true
getRef('article_wrapper').style.overflow = 'hidden'
}
}
function hideVersionHistory() {
if (floGlobals.versionHistory.isOpen) {
getRef('version_history_panel').classList.add('hidden')
getRef('version_timeline').innerHTML = ''
getRef('main_page').classList.remove('active-sidebar')
floGlobals.versionHistory.isOpen = false
getRef('article_wrapper').style.overflow = 'auto'
}
}
function normalizeText(target) {
if (target) {
for (const child of target.children) {
if (child.textContent === ' ') {
child.after(document.createTextNode(' '))
child.remove()
}
else if (child.textContent.trim() === '')
child.remove()
}
target.normalize()
}
}
function manageFormattingOptions() {
const selection = window.getSelection();
if (selection.isCollapsed) {
getRef('text_toolbar').classList.add('hidden')
document.querySelectorAll('.formatting-button').forEach(elem => elem.classList.remove('active'))
} else {
getRef('create_link_href').value = ''
if (isMobileView) {
getRef('text_toolbar').classList.remove('hidden')
getRef('text_toolbar').style.transform = `none`
} else {
const pos = selection.getRangeAt(0).getBoundingClientRect()
const leftOffset = pos.left + 200 < window.innerWidth ? pos.left : pos.left - 220 + window.innerWidth - pos.left
const topOffeset = (pos.bottom + window.pageYOffset + 64) > window.innerHeight ? pos.top + window.pageYOffset - 56 : pos.bottom + window.pageYOffset
getRef('text_toolbar').style.transform = `translate(${leftOffset}px, calc(${topOffeset}px + 1rem))`
getRef('text_toolbar').classList.remove('hidden')
getRef('text_toolbar').style.transform = `translate(${leftOffset}px, calc(${topOffeset}px + 0.3rem))`
}
getRef('formatting_options').classList.remove('hidden')
getRef('link_panel').classList.add('hidden')
}
}
const detectFormatting = debounce((e) => {
manageFormattingOptions()
if (!window.getSelection().isCollapsed) {
[
['bold', 'strong_button'],
['italic', 'em_button'],
['underline', 'u_button'],
['superscript', 'sup_button'],
['subscript', 'sub_button'],
['link', 'a_button'],
].forEach(([state, id]) => {
if (state === 'link' ? isLink() : document.queryCommandState(state)) {
getRef(id).classList.add('active')
} else {
getRef(id).classList.remove('active')
}
})
}
}, 300)
function saveSelection() {
if (window.getSelection) {
const sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
const ranges = [];
for (let i = 0, len = sel.rangeCount; i < len; ++i) {
ranges.push(sel.getRangeAt(i));
}
return ranges;
}
} else if (document.selection && document.selection.createRange) {
return document.selection.createRange();
}
return null;
}
function restoreSelection(savedSel) {
if (savedSel) {
if (window.getSelection) {
const sel = window.getSelection();
sel.removeAllRanges();
for (let i = 0, len = savedSel.length; i < len; ++i) {
sel.addRange(savedSel[i]);
}
} else if (document.selection && savedSel.select) {
savedSel.select();
}
currentSelection = null
}
}
let currentSelection
function toggleLinkPanel(elem) {
if (elem && elem.classList.contains('active')) {
const selection = window.getSelection()
const startA = selection.anchorNode.parentNode
const endA = selection.focusNode.parentNode
getRef('create_link_href').value = (startA || endA).getAttribute('href')
} else {
getRef('create_link_href').value = ''
}
if (getRef('link_panel').classList.contains('hidden')) {
currentSelection = saveSelection()
} else {
}
getRef('formatting_options').classList.toggle('hidden')
getRef('link_panel').classList.toggle('hidden')
}
function isLink() {
const selection = window.getSelection()
const startA = selection.anchorNode.parentNode.tagName === 'A'
const endA = selection.focusNode.parentNode.tagName === 'A'
return startA || endA
}
function createLink() {
restoreSelection(currentSelection)
const url = getRef('create_link_href').value
if (isLink()) {
const selection = window.getSelection()
const startA = selection.anchorNode.parentNode
const endA = selection.focusNode.parentNode;
(startA || endA).setAttribute('href', url)
} else {
replaceSelectedText(createElement('a', {
attributes: {
href: url,
target: "_blank",
rel: "noopener noreferrer"
}
}))
}
checkEntry()
}
function replaceSelectedText(node) {
const selection = window.getSelection();
if (!selection.isCollapsed && selection.rangeCount) {
const range = selection.getRangeAt(0);
const originalText = range.toString()
range.deleteContents()
node.textContent = originalText
range.insertNode(node);
selection.anchorNode.parentNode.closest('.content__area')?.focus()
}
}
function insertNode(node) {
const selection = window.getSelection();
if (selection.rangeCount) {
const range = selection.getRangeAt(0);
range.insertNode(node)
}
}
function getSignedIn() {
return new Promise((resolve, reject) => {
if (window.location.hash.includes('sign_in') || window.location.hash.includes('sign_up')) {
routeTo(window.location.hash)
} else {
routeTo('landing', { isPreview: location.hash.includes('preview'), redirected: location.hash.includes('articleID') })
}
getRef('sign_in_button').onclick = () => {
resolve(getRef('private_key_field').value.trim())
getRef('private_key_field').value = ''
routeTo('loading')
}
getRef('sign_up_button').onclick = () => {
resolve(getRef('keys_generator').keys.privKey);
getRef('keys_generator').clearKeys();
routeTo('loading')
}
})
}
function signOut() {
getConfirmation('Sign out?', 'You are about to sign out of the app, continue?', 'Stay', 'Leave')
.then(async (res) => {
if (res) {
await floDapps.clearCredentials()
location.reload()
}
})
}
</script>
<script id="onLoadStartUp">
let maxCardsPerSection
let isShowingFloID = true
function onLoadStartUp() {
//floDapps.addStartUpFunction('Sample', Promised Function)
//floDapps.setAppObjectStores({sampleObs1:{}, sampleObs2:{options{autoIncrement:true, keyPath:'SampleKey'}, Indexes:{sampleIndex:{}}}})
floDapps.setCustomPrivKeyInput(getSignedIn)
floDapps.launchStartUp().then(async result => {
floGlobals.myFloID = floCrypto.toFloID(floDapps.user.id);
floGlobals.myBtcID = btcOperator.convert.legacy2bech(floGlobals.myFloID)
floGlobals.isSubAdmin = floGlobals.subAdmins.includes(floGlobals.myFloID)
getRef('user_flo_id').value = floGlobals.myFloID
getRef('user_btc_id').value = btcOperator.convert.legacy2bech(floGlobals.myFloID)
renderElem(getRef('user_popup_button'), html`
<svg class="icon margin-right-0-5" width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg"> <mask id="path-1-outside-1_16_6" maskUnits="userSpaceOnUse" x="3" y="3" width="58" height="58"> <rect fill="white" x="3" y="3" width="58" height="58" /> <path fill-rule="evenodd" clip-rule="evenodd" d="M31.946 43.6019C28.8545 47.1308 28.7789 51.9719 31.9811 55.1957C35.0078 52.2797 35.0105 46.8446 31.946 43.6019ZM31.9487 10.6835C24.6452 19.1291 24.9206 29.1137 31.9433 37.4108C39.0929 28.9436 39.1118 19.0076 31.9487 10.6808V10.6835ZM37.1111 35.051C43.1861 28.841 50.5976 27.4208 59 28.7654C56.2919 34.9754 52.4714 39.9353 45.7214 42.53C47.2118 41.3609 48.5699 40.4051 49.7984 39.3089C51.8504 37.481 53.303 35.267 54.0293 32.6075C54.2588 31.7678 53.9618 31.5302 53.1896 31.406C50.3627 30.9524 47.7086 31.5464 45.1733 32.702C40.9073 34.646 37.4324 37.6403 34.1735 40.8938C34.1168 40.9532 34.0925 41.0396 33.9899 41.2259C40.3754 41.4689 45.2381 44.0177 48.119 49.9064C44.3768 50.738 40.9532 50.5625 37.778 48.5213C40.1702 49.2557 42.557 49.7903 45.176 48.5483C44.852 48.0083 44.663 47.3765 44.231 47.0228C43.0511 46.0589 41.8415 45.0842 40.505 44.3498C38.4395 43.2212 36.131 42.692 33.5768 42.1682C37.0247 48.2081 36.1607 53.6918 31.946 59C29.9858 56.5295 28.6142 53.9456 28.2389 50.8946C27.8609 47.8328 28.7222 45.0518 30.1991 42.3896C26.9321 41.8658 19.8824 45.6485 18.7916 48.5645C21.3215 49.8443 23.7893 49.2611 26.33 48.3161C24.5048 50.1953 19.2425 50.9648 15.8108 49.8335C18.6593 44.0285 23.4977 41.4716 30.0263 41.2286C29.5457 40.7426 29.1893 40.3673 28.8167 40.0082C26.1005 37.3892 23.2142 34.9862 19.823 33.2366C17.0501 31.8056 14.1422 30.9092 10.9481 31.3871C10.5701 31.4438 10.1948 31.5356 9.6656 31.6436C10.6376 36.6386 13.9856 39.7355 18.1274 42.3464C13.775 41.8199 6.431 34.214 5 28.7546C13.397 27.3938 20.849 28.8842 26.897 35.2319C21.8939 24.089 24.5561 14.2259 31.946 5C39.2765 14.153 41.9657 23.9459 37.1111 35.051" /> </mask> <path fill-rule="evenodd" clip-rule="evenodd" d="M31.946 43.6019C28.8545 47.1308 28.7789 51.9719 31.9811 55.1957C35.0078 52.2797 35.0105 46.8446 31.946 43.6019ZM31.9487 10.6835C24.6452 19.1291 24.9206 29.1137 31.9433 37.4108C39.0929 28.9436 39.1118 19.0076 31.9487 10.6808V10.6835ZM37.1111 35.051C43.1861 28.841 50.5976 27.4208 59 28.7654C56.2919 34.9754 52.4714 39.9353 45.7214 42.53C47.2118 41.3609 48.5699 40.4051 49.7984 39.3089C51.8504 37.481 53.303 35.267 54.0293 32.6075C54.2588 31.7678 53.9618 31.5302 53.1896 31.406C50.3627 30.9524 47.7086 31.5464 45.1733 32.702C40.9073 34.646 37.4324 37.6403 34.1735 40.8938C34.1168 40.9532 34.0925 41.0396 33.9899 41.2259C40.3754 41.4689 45.2381 44.0177 48.119 49.9064C44.3768 50.738 40.9532 50.5625 37.778 48.5213C40.1702 49.2557 42.557 49.7903 45.176 48.5483C44.852 48.0083 44.663 47.3765 44.231 47.0228C43.0511 46.0589 41.8415 45.0842 40.505 44.3498C38.4395 43.2212 36.131 42.692 33.5768 42.1682C37.0247 48.2081 36.1607 53.6918 31.946 59C29.9858 56.5295 28.6142 53.9456 28.2389 50.8946C27.8609 47.8328 28.7222 45.0518 30.1991 42.3896C26.9321 41.8658 19.8824 45.6485 18.7916 48.5645C21.3215 49.8443 23.7893 49.2611 26.33 48.3161C24.5048 50.1953 19.2425 50.9648 15.8108 49.8335C18.6593 44.0285 23.4977 41.4716 30.0263 41.2286C29.5457 40.7426 29.1893 40.3673 28.8167 40.0082C26.1005 37.3892 23.2142 34.9862 19.823 33.2366C17.0501 31.8056 14.1422 30.9092 10.9481 31.3871C10.5701 31.4438 10.1948 31.5356 9.6656 31.6436C10.6376 36.6386 13.9856 39.7355 18.1274 42.3464C13.775 41.8199 6.431 34.214 5 28.7546C13.397 27.3938 20.849 28.8842 26.897 35.2319C21.8939 24.089 24.5561 14.2259 31.946 5C39.2765 14.153 41.9657 23.9459 37.1111 35.051" /> <path d="M31.946 43.6019L32.6728 42.915L31.918 42.1163L31.1938 42.943L31.946 43.6019ZM31.9811 55.1957L31.2716 55.9004L31.9657 56.5992L32.6749 55.9159L31.9811 55.1957ZM31.9487 10.6835L32.7051 11.3376L32.9487 11.0559V10.6835H31.9487ZM31.9433 37.4108L31.18 38.0569L31.9442 38.9597L32.7074 38.056L31.9433 37.4108ZM31.9487 10.6808L32.7068 10.0287L30.9487 7.98495V10.6808H31.9487ZM59 28.7654L59.9166 29.1651L60.4326 27.9819L59.158 27.778L59 28.7654ZM45.7214 42.53L45.1042 41.7432L46.0802 43.4634L45.7214 42.53ZM49.7984 39.3089L49.1332 38.5622L49.1326 38.5628L49.7984 39.3089ZM54.0293 32.6075L53.0647 32.3439L53.0646 32.3441L54.0293 32.6075ZM53.1896 31.406L53.3484 30.4187L53.348 30.4186L53.1896 31.406ZM45.1733 32.702L45.588 33.612L45.5881 33.6119L45.1733 32.702ZM34.1735 40.8938L33.467 40.1861L33.4585 40.1946L33.4501 40.2033L34.1735 40.8938ZM33.9899 41.2259L33.114 40.7435L32.3319 42.1635L33.9519 42.2252L33.9899 41.2259ZM48.119 49.9064L48.3359 50.8826L49.5751 50.6072L49.0173 49.4669L48.119 49.9064ZM37.778 48.5213L38.0715 47.5653L37.2372 49.3625L37.778 48.5213ZM45.176 48.5483L45.6045 49.4518L46.6008 48.9794L46.0335 48.0338L45.176 48.5483ZM44.231 47.0228L44.8645 46.2491L44.8637 46.2484L44.231 47.0228ZM40.505 44.3498L40.9866 43.4734L40.9845 43.4723L40.505 44.3498ZM33.5768 42.1682L33.7777 41.1886L31.6127 40.7446L32.7083 42.664L33.5768 42.1682ZM31.946 59L31.1626 59.6216L31.9457 60.6085L32.7292 59.6218L31.946 59ZM28.2389 50.8946L29.2314 50.7725L29.2314 50.7721L28.2389 50.8946ZM30.1991 42.3896L31.0736 42.8747L31.7652 41.6279L30.3574 41.4022L30.1991 42.3896ZM18.7916 48.5645L17.855 48.2141L17.5413 49.0527L18.3402 49.4568L18.7916 48.5645ZM26.33 48.3161L27.0473 49.0128L25.9814 47.3788L26.33 48.3161ZM15.8108 49.8335L14.9131 49.393L14.4073 50.4237L15.4977 50.7832L15.8108 49.8335ZM30.0263 41.2286L30.0635 42.2279L32.3372 42.1433L30.7373 40.5255L30.0263 41.2286ZM28.8167 40.0082L28.1226 40.7281L28.1228 40.7282L28.8167 40.0082ZM19.823 33.2366L19.3644 34.1252L19.3645 34.1253L19.823 33.2366ZM10.9481 31.3871L10.8001 30.3981L10.7998 30.3982L10.9481 31.3871ZM9.6656 31.6436L9.46564 30.6638L8.49474 30.8619L8.68401 31.8346L9.6656 31.6436ZM18.1274 42.3464L18.0073 43.3392L18.6607 41.5005L18.1274 42.3464ZM5 28.7546L4.84003 27.7675L3.75363 27.9435L4.03268 29.0082L5 28.7546ZM26.897 35.2319L26.173 35.9217L27.8093 34.8223L26.897 35.2319ZM31.946 5L32.7265 4.37488L31.9461 3.40036L31.1655 4.37483L31.946 5ZM31.1938 42.943C27.7974 46.8199 27.6566 52.261 31.2716 55.9004L32.6906 54.491C29.9012 51.6828 29.9116 47.4417 32.6982 44.2609L31.1938 42.943ZM32.6749 55.9159C36.1304 52.5867 36.081 46.5214 32.6728 42.915L31.2192 44.2888C33.94 47.1678 33.8852 51.9727 31.2873 54.4755L32.6749 55.9159ZM31.1923 10.0294C27.4048 14.4092 25.5379 19.2455 25.5737 24.1102C25.6095 28.971 27.5438 33.7608 31.18 38.0569L32.7066 36.7647C29.3201 32.7637 27.6054 28.4127 27.5736 24.0955C27.5419 19.7822 29.1891 15.4034 32.7051 11.3376L31.1923 10.0294ZM32.7074 38.056C36.4083 33.6729 38.31 28.8504 38.3133 23.9938C38.3165 19.1359 36.42 14.3451 32.7068 10.0287L31.1906 11.3329C34.6405 15.3433 36.3161 19.6839 36.3133 23.9925C36.3104 28.3024 34.6279 32.6815 31.1792 36.7656L32.7074 38.056ZM30.9487 10.6808V10.6835H32.9487V10.6808H30.9487ZM37.8259 35.7503C43.6131 29.8345 50.6632 28.444 58.842 29.7528L59.158 27.778C50.532 26.3976 42.7591 27.8475 36.3963 34.3517L37.8259 35.7503ZM58.0834 28.3657C55.4402 34.4268 51.7791 39.1301 45.3626 41.5966L46.0802 43.4634C53.1637 40.7405 57.1436 35.524 59.9166 29.1651L58.0834 28.3657ZM46.3386 43.3168C47.7733 42.1914 49.2057 41.178 50.4642 40.055L49.1326 38.5628C47.9341 39.6322 46.6503 40.5304 45.1042 41.7432L46.3386 43.3168ZM50.4636 40.0556C52.6466 38.111 54.2121 35.7338 54.994 32.871L53.0646 32.3441C52.3939 34.8002 51.0542 36.851 49.1332 38.5622L50.4636 40.0556ZM54.9939 32.8711C55.1262 32.3872 55.2248 31.6946 54.7698 31.1186C54.3643 30.6053 53.7257 30.4794 53.3484 30.4187L53.0308 32.3933C53.1086 32.4058 53.17 32.4181 53.2184 32.43C53.2671 32.442 53.2955 32.4518 53.3091 32.4572C53.3227 32.4626 53.3139 32.4606 53.2926 32.4461C53.2699 32.4307 53.2353 32.4025 53.2004 32.3583C53.1832 32.3366 53.1677 32.3132 53.1543 32.2886C53.1409 32.264 53.1307 32.2402 53.1231 32.2182C53.1079 32.1742 53.1053 32.144 53.1049 32.1366C53.1046 32.1302 53.1059 32.1427 53.1007 32.1797C53.0955 32.2165 53.0849 32.27 53.0647 32.3439L54.9939 32.8711ZM53.348 30.4186C50.274 29.9254 47.4176 30.5801 44.7585 31.7921L45.5881 33.6119C47.9996 32.5127 50.4514 31.9794 53.0312 32.3934L53.348 30.4186ZM44.7586 31.792C40.3293 33.8104 36.7535 36.9051 33.467 40.1861L34.88 41.6015C38.1113 38.3755 41.4853 35.4816 45.588 33.612L44.7586 31.792ZM33.4501 40.2033C33.3063 40.354 33.2291 40.5156 33.2033 40.5674C33.1672 40.64 33.1517 40.6749 33.114 40.7435L34.8658 41.7083C34.9307 41.5906 34.9786 41.4891 34.9945 41.4572C35.0049 41.4363 35.0004 41.4467 34.9882 41.4667C34.9731 41.4914 34.9435 41.5355 34.8969 41.5843L33.4501 40.2033ZM33.9519 42.2252C37.0233 42.3421 39.6629 43.0109 41.8597 44.3098C44.0485 45.6039 45.857 47.5582 47.2207 50.3459L49.0173 49.4669C47.5001 46.3659 45.4368 44.1014 42.8776 42.5882C40.3265 41.0798 37.342 40.3527 34.0279 40.2266L33.9519 42.2252ZM47.9021 48.9302C44.3241 49.7253 41.1983 49.5313 38.3188 47.6801L37.2372 49.3625C40.7081 51.5937 44.4295 51.7507 48.3359 50.8826L47.9021 48.9302ZM37.4845 49.4773C39.9089 50.2215 42.6205 50.8669 45.6045 49.4518L44.7475 47.6448C42.4935 48.7137 40.4315 48.2899 38.0715 47.5653L37.4845 49.4773ZM46.0335 48.0338C45.8849 47.7862 45.8212 47.6158 45.6292 47.2504C45.4736 46.9544 45.2423 46.5584 44.8645 46.2491L43.5975 47.7965C43.6517 47.8409 43.7309 47.9377 43.8588 48.181C43.9503 48.3549 44.1431 48.7704 44.3185 49.0628L46.0335 48.0338ZM44.8637 46.2484C43.6913 45.2906 42.4149 44.2582 40.9866 43.4734L40.0234 45.2262C41.2681 45.9102 42.4109 46.8272 43.5983 47.7972L44.8637 46.2484ZM40.9845 43.4723C38.7711 42.2628 36.3236 41.7107 33.7777 41.1886L33.3759 43.1478C35.9384 43.6733 38.1079 44.1796 40.0255 45.2273L40.9845 43.4723ZM32.7083 42.664C34.346 45.5328 34.9255 48.203 34.6411 50.7474C34.3555 53.3015 33.1882 55.8274 31.1628 58.3782L32.7292 59.6218C34.9185 56.8644 36.2905 53.9944 36.6287 50.9696C36.9679 47.9351 36.2555 44.8435 34.4453 41.6724L32.7083 42.664ZM32.7294 58.3784C30.8397 55.9968 29.577 53.5818 29.2314 50.7725L27.2464 51.0167C27.6514 54.3094 29.1319 57.0622 31.1626 59.6216L32.7294 58.3784ZM29.2314 50.7721C28.8877 47.9881 29.6602 45.4224 31.0736 42.8747L29.3247 41.9045C27.7842 44.6812 26.8341 47.6775 27.2464 51.0171L29.2314 50.7721ZM30.3574 41.4022C29.3303 41.2375 28.1125 41.4187 26.9159 41.7622C25.6987 42.1117 24.4105 42.6564 23.2045 43.307C21.999 43.9574 20.8488 44.728 19.9166 45.543C19.005 46.3399 18.2137 47.2553 17.855 48.2141L19.7282 48.9149C19.9149 48.4157 20.4139 47.7647 21.233 47.0487C22.0314 46.3507 23.0524 45.6616 24.1541 45.0672C25.2552 44.4732 26.4102 43.9882 27.4678 43.6846C28.5458 43.3751 29.4344 43.2798 30.0408 43.377L30.3574 41.4022ZM18.3402 49.4568C21.2702 50.939 24.0958 50.214 26.6786 49.2534L25.9814 47.3788C23.4828 48.3082 21.3728 48.7496 19.243 47.6722L18.3402 49.4568ZM25.6127 47.6194C24.9334 48.3187 23.4333 48.9382 21.5294 49.2141C19.6667 49.484 17.6562 49.3889 16.1239 48.8838L15.4977 50.7832C17.3971 51.4094 19.7336 51.4952 21.8162 51.1934C23.8578 50.8976 25.9014 50.1927 27.0473 49.0128L25.6127 47.6194ZM16.7085 50.274C18.0554 47.5292 19.8486 45.5942 22.044 44.3081C24.2482 43.0169 26.918 42.345 30.0635 42.2279L29.9891 40.2293C26.606 40.3552 23.5923 41.0832 21.0331 42.5824C18.4649 44.0869 16.4147 46.3328 14.9131 49.393L16.7085 50.274ZM30.7373 40.5255C30.2757 40.0586 29.8942 39.6578 29.5106 39.2882L28.1228 40.7282C28.4844 41.0768 28.8157 41.4266 29.3153 41.9317L30.7373 40.5255ZM29.5108 39.2883C26.7637 36.6396 23.7967 34.1615 20.2815 32.3479L19.3645 34.1253C22.6317 35.8109 25.4373 38.1388 28.1226 40.7281L29.5108 39.2883ZM20.2816 32.348C17.4111 30.8666 14.2829 29.877 10.8001 30.3981L11.0961 32.3761C14.0015 31.9414 16.6891 32.7446 19.3644 34.1252L20.2816 32.348ZM10.7998 30.3982C10.388 30.4599 9.94468 30.566 9.46564 30.6638L9.86556 32.6234C10.4449 32.5052 10.7522 32.4277 11.0964 32.376L10.7998 30.3982ZM8.68401 31.8346C9.73479 37.2345 13.3673 40.5279 17.5941 43.1923L18.6607 41.5005C14.6039 38.9431 11.5404 36.0427 10.6472 31.4526L8.68401 31.8346ZM18.2475 41.3536C17.3819 41.2489 16.2594 40.7693 14.9918 39.9335C13.742 39.1093 12.427 37.9891 11.1875 36.7057C8.68437 34.1137 6.62095 30.9947 5.96732 28.5011L4.03268 29.0082C4.81005 31.9739 7.13413 35.3875 9.74885 38.095C11.0683 39.4613 12.4947 40.6825 13.8908 41.6031C15.2691 42.512 16.6967 43.1806 18.0073 43.3392L18.2475 41.3536ZM5.15997 29.7417C13.3246 28.4186 20.4108 29.874 26.173 35.9217L27.621 34.5421C21.2872 27.8944 13.4694 26.369 4.84003 27.7675L5.15997 29.7417ZM27.8093 34.8223C25.3851 29.4232 24.8341 24.3801 25.7561 19.5859C26.6809 14.7778 29.1009 10.1516 32.7265 5.62517L31.1655 4.37483C27.4012 9.07433 24.7952 13.9927 23.7921 19.2082C22.7863 24.4378 23.4058 29.8977 25.9847 35.6415L27.8093 34.8223ZM31.1655 5.62512C34.7619 10.1157 37.1747 14.7077 38.1168 19.4869C39.0562 24.2523 38.5469 29.2701 36.1948 34.6504L38.0274 35.4515C40.5299 29.7268 41.1033 24.2956 40.0791 19.1001C39.0576 13.9183 36.4606 9.03731 32.7265 4.37488L31.1655 5.62512Z" mask="url(#path-1-outside-1_16_6)" /> </svg>
<div>${floGlobals.myFloID}</div>
`)
setInterval(() => {
if (!isShowingFloID) {
renderElem(getRef('user_popup_button'), html`
<svg class="icon margin-right-0-5" width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg"> <mask id="path-1-outside-1_16_6" maskUnits="userSpaceOnUse" x="3" y="3" width="58" height="58"> <rect fill="white" x="3" y="3" width="58" height="58" /> <path fill-rule="evenodd" clip-rule="evenodd" d="M31.946 43.6019C28.8545 47.1308 28.7789 51.9719 31.9811 55.1957C35.0078 52.2797 35.0105 46.8446 31.946 43.6019ZM31.9487 10.6835C24.6452 19.1291 24.9206 29.1137 31.9433 37.4108C39.0929 28.9436 39.1118 19.0076 31.9487 10.6808V10.6835ZM37.1111 35.051C43.1861 28.841 50.5976 27.4208 59 28.7654C56.2919 34.9754 52.4714 39.9353 45.7214 42.53C47.2118 41.3609 48.5699 40.4051 49.7984 39.3089C51.8504 37.481 53.303 35.267 54.0293 32.6075C54.2588 31.7678 53.9618 31.5302 53.1896 31.406C50.3627 30.9524 47.7086 31.5464 45.1733 32.702C40.9073 34.646 37.4324 37.6403 34.1735 40.8938C34.1168 40.9532 34.0925 41.0396 33.9899 41.2259C40.3754 41.4689 45.2381 44.0177 48.119 49.9064C44.3768 50.738 40.9532 50.5625 37.778 48.5213C40.1702 49.2557 42.557 49.7903 45.176 48.5483C44.852 48.0083 44.663 47.3765 44.231 47.0228C43.0511 46.0589 41.8415 45.0842 40.505 44.3498C38.4395 43.2212 36.131 42.692 33.5768 42.1682C37.0247 48.2081 36.1607 53.6918 31.946 59C29.9858 56.5295 28.6142 53.9456 28.2389 50.8946C27.8609 47.8328 28.7222 45.0518 30.1991 42.3896C26.9321 41.8658 19.8824 45.6485 18.7916 48.5645C21.3215 49.8443 23.7893 49.2611 26.33 48.3161C24.5048 50.1953 19.2425 50.9648 15.8108 49.8335C18.6593 44.0285 23.4977 41.4716 30.0263 41.2286C29.5457 40.7426 29.1893 40.3673 28.8167 40.0082C26.1005 37.3892 23.2142 34.9862 19.823 33.2366C17.0501 31.8056 14.1422 30.9092 10.9481 31.3871C10.5701 31.4438 10.1948 31.5356 9.6656 31.6436C10.6376 36.6386 13.9856 39.7355 18.1274 42.3464C13.775 41.8199 6.431 34.214 5 28.7546C13.397 27.3938 20.849 28.8842 26.897 35.2319C21.8939 24.089 24.5561 14.2259 31.946 5C39.2765 14.153 41.9657 23.9459 37.1111 35.051" /> </mask> <path fill-rule="evenodd" clip-rule="evenodd" d="M31.946 43.6019C28.8545 47.1308 28.7789 51.9719 31.9811 55.1957C35.0078 52.2797 35.0105 46.8446 31.946 43.6019ZM31.9487 10.6835C24.6452 19.1291 24.9206 29.1137 31.9433 37.4108C39.0929 28.9436 39.1118 19.0076 31.9487 10.6808V10.6835ZM37.1111 35.051C43.1861 28.841 50.5976 27.4208 59 28.7654C56.2919 34.9754 52.4714 39.9353 45.7214 42.53C47.2118 41.3609 48.5699 40.4051 49.7984 39.3089C51.8504 37.481 53.303 35.267 54.0293 32.6075C54.2588 31.7678 53.9618 31.5302 53.1896 31.406C50.3627 30.9524 47.7086 31.5464 45.1733 32.702C40.9073 34.646 37.4324 37.6403 34.1735 40.8938C34.1168 40.9532 34.0925 41.0396 33.9899 41.2259C40.3754 41.4689 45.2381 44.0177 48.119 49.9064C44.3768 50.738 40.9532 50.5625 37.778 48.5213C40.1702 49.2557 42.557 49.7903 45.176 48.5483C44.852 48.0083 44.663 47.3765 44.231 47.0228C43.0511 46.0589 41.8415 45.0842 40.505 44.3498C38.4395 43.2212 36.131 42.692 33.5768 42.1682C37.0247 48.2081 36.1607 53.6918 31.946 59C29.9858 56.5295 28.6142 53.9456 28.2389 50.8946C27.8609 47.8328 28.7222 45.0518 30.1991 42.3896C26.9321 41.8658 19.8824 45.6485 18.7916 48.5645C21.3215 49.8443 23.7893 49.2611 26.33 48.3161C24.5048 50.1953 19.2425 50.9648 15.8108 49.8335C18.6593 44.0285 23.4977 41.4716 30.0263 41.2286C29.5457 40.7426 29.1893 40.3673 28.8167 40.0082C26.1005 37.3892 23.2142 34.9862 19.823 33.2366C17.0501 31.8056 14.1422 30.9092 10.9481 31.3871C10.5701 31.4438 10.1948 31.5356 9.6656 31.6436C10.6376 36.6386 13.9856 39.7355 18.1274 42.3464C13.775 41.8199 6.431 34.214 5 28.7546C13.397 27.3938 20.849 28.8842 26.897 35.2319C21.8939 24.089 24.5561 14.2259 31.946 5C39.2765 14.153 41.9657 23.9459 37.1111 35.051" /> <path d="M31.946 43.6019L32.6728 42.915L31.918 42.1163L31.1938 42.943L31.946 43.6019ZM31.9811 55.1957L31.2716 55.9004L31.9657 56.5992L32.6749 55.9159L31.9811 55.1957ZM31.9487 10.6835L32.7051 11.3376L32.9487 11.0559V10.6835H31.9487ZM31.9433 37.4108L31.18 38.0569L31.9442 38.9597L32.7074 38.056L31.9433 37.4108ZM31.9487 10.6808L32.7068 10.0287L30.9487 7.98495V10.6808H31.9487ZM59 28.7654L59.9166 29.1651L60.4326 27.9819L59.158 27.778L59 28.7654ZM45.7214 42.53L45.1042 41.7432L46.0802 43.4634L45.7214 42.53ZM49.7984 39.3089L49.1332 38.5622L49.1326 38.5628L49.7984 39.3089ZM54.0293 32.6075L53.0647 32.3439L53.0646 32.3441L54.0293 32.6075ZM53.1896 31.406L53.3484 30.4187L53.348 30.4186L53.1896 31.406ZM45.1733 32.702L45.588 33.612L45.5881 33.6119L45.1733 32.702ZM34.1735 40.8938L33.467 40.1861L33.4585 40.1946L33.4501 40.2033L34.1735 40.8938ZM33.9899 41.2259L33.114 40.7435L32.3319 42.1635L33.9519 42.2252L33.9899 41.2259ZM48.119 49.9064L48.3359 50.8826L49.5751 50.6072L49.0173 49.4669L48.119 49.9064ZM37.778 48.5213L38.0715 47.5653L37.2372 49.3625L37.778 48.5213ZM45.176 48.5483L45.6045 49.4518L46.6008 48.9794L46.0335 48.0338L45.176 48.5483ZM44.231 47.0228L44.8645 46.2491L44.8637 46.2484L44.231 47.0228ZM40.505 44.3498L40.9866 43.4734L40.9845 43.4723L40.505 44.3498ZM33.5768 42.1682L33.7777 41.1886L31.6127 40.7446L32.7083 42.664L33.5768 42.1682ZM31.946 59L31.1626 59.6216L31.9457 60.6085L32.7292 59.6218L31.946 59ZM28.2389 50.8946L29.2314 50.7725L29.2314 50.7721L28.2389 50.8946ZM30.1991 42.3896L31.0736 42.8747L31.7652 41.6279L30.3574 41.4022L30.1991 42.3896ZM18.7916 48.5645L17.855 48.2141L17.5413 49.0527L18.3402 49.4568L18.7916 48.5645ZM26.33 48.3161L27.0473 49.0128L25.9814 47.3788L26.33 48.3161ZM15.8108 49.8335L14.9131 49.393L14.4073 50.4237L15.4977 50.7832L15.8108 49.8335ZM30.0263 41.2286L30.0635 42.2279L32.3372 42.1433L30.7373 40.5255L30.0263 41.2286ZM28.8167 40.0082L28.1226 40.7281L28.1228 40.7282L28.8167 40.0082ZM19.823 33.2366L19.3644 34.1252L19.3645 34.1253L19.823 33.2366ZM10.9481 31.3871L10.8001 30.3981L10.7998 30.3982L10.9481 31.3871ZM9.6656 31.6436L9.46564 30.6638L8.49474 30.8619L8.68401 31.8346L9.6656 31.6436ZM18.1274 42.3464L18.0073 43.3392L18.6607 41.5005L18.1274 42.3464ZM5 28.7546L4.84003 27.7675L3.75363 27.9435L4.03268 29.0082L5 28.7546ZM26.897 35.2319L26.173 35.9217L27.8093 34.8223L26.897 35.2319ZM31.946 5L32.7265 4.37488L31.9461 3.40036L31.1655 4.37483L31.946 5ZM31.1938 42.943C27.7974 46.8199 27.6566 52.261 31.2716 55.9004L32.6906 54.491C29.9012 51.6828 29.9116 47.4417 32.6982 44.2609L31.1938 42.943ZM32.6749 55.9159C36.1304 52.5867 36.081 46.5214 32.6728 42.915L31.2192 44.2888C33.94 47.1678 33.8852 51.9727 31.2873 54.4755L32.6749 55.9159ZM31.1923 10.0294C27.4048 14.4092 25.5379 19.2455 25.5737 24.1102C25.6095 28.971 27.5438 33.7608 31.18 38.0569L32.7066 36.7647C29.3201 32.7637 27.6054 28.4127 27.5736 24.0955C27.5419 19.7822 29.1891 15.4034 32.7051 11.3376L31.1923 10.0294ZM32.7074 38.056C36.4083 33.6729 38.31 28.8504 38.3133 23.9938C38.3165 19.1359 36.42 14.3451 32.7068 10.0287L31.1906 11.3329C34.6405 15.3433 36.3161 19.6839 36.3133 23.9925C36.3104 28.3024 34.6279 32.6815 31.1792 36.7656L32.7074 38.056ZM30.9487 10.6808V10.6835H32.9487V10.6808H30.9487ZM37.8259 35.7503C43.6131 29.8345 50.6632 28.444 58.842 29.7528L59.158 27.778C50.532 26.3976 42.7591 27.8475 36.3963 34.3517L37.8259 35.7503ZM58.0834 28.3657C55.4402 34.4268 51.7791 39.1301 45.3626 41.5966L46.0802 43.4634C53.1637 40.7405 57.1436 35.524 59.9166 29.1651L58.0834 28.3657ZM46.3386 43.3168C47.7733 42.1914 49.2057 41.178 50.4642 40.055L49.1326 38.5628C47.9341 39.6322 46.6503 40.5304 45.1042 41.7432L46.3386 43.3168ZM50.4636 40.0556C52.6466 38.111 54.2121 35.7338 54.994 32.871L53.0646 32.3441C52.3939 34.8002 51.0542 36.851 49.1332 38.5622L50.4636 40.0556ZM54.9939 32.8711C55.1262 32.3872 55.2248 31.6946 54.7698 31.1186C54.3643 30.6053 53.7257 30.4794 53.3484 30.4187L53.0308 32.3933C53.1086 32.4058 53.17 32.4181 53.2184 32.43C53.2671 32.442 53.2955 32.4518 53.3091 32.4572C53.3227 32.4626 53.3139 32.4606 53.2926 32.4461C53.2699 32.4307 53.2353 32.4025 53.2004 32.3583C53.1832 32.3366 53.1677 32.3132 53.1543 32.2886C53.1409 32.264 53.1307 32.2402 53.1231 32.2182C53.1079 32.1742 53.1053 32.144 53.1049 32.1366C53.1046 32.1302 53.1059 32.1427 53.1007 32.1797C53.0955 32.2165 53.0849 32.27 53.0647 32.3439L54.9939 32.8711ZM53.348 30.4186C50.274 29.9254 47.4176 30.5801 44.7585 31.7921L45.5881 33.6119C47.9996 32.5127 50.4514 31.9794 53.0312 32.3934L53.348 30.4186ZM44.7586 31.792C40.3293 33.8104 36.7535 36.9051 33.467 40.1861L34.88 41.6015C38.1113 38.3755 41.4853 35.4816 45.588 33.612L44.7586 31.792ZM33.4501 40.2033C33.3063 40.354 33.2291 40.5156 33.2033 40.5674C33.1672 40.64 33.1517 40.6749 33.114 40.7435L34.8658 41.7083C34.9307 41.5906 34.9786 41.4891 34.9945 41.4572C35.0049 41.4363 35.0004 41.4467 34.9882 41.4667C34.9731 41.4914 34.9435 41.5355 34.8969 41.5843L33.4501 40.2033ZM33.9519 42.2252C37.0233 42.3421 39.6629 43.0109 41.8597 44.3098C44.0485 45.6039 45.857 47.5582 47.2207 50.3459L49.0173 49.4669C47.5001 46.3659 45.4368 44.1014 42.8776 42.5882C40.3265 41.0798 37.342 40.3527 34.0279 40.2266L33.9519 42.2252ZM47.9021 48.9302C44.3241 49.7253 41.1983 49.5313 38.3188 47.6801L37.2372 49.3625C40.7081 51.5937 44.4295 51.7507 48.3359 50.8826L47.9021 48.9302ZM37.4845 49.4773C39.9089 50.2215 42.6205 50.8669 45.6045 49.4518L44.7475 47.6448C42.4935 48.7137 40.4315 48.2899 38.0715 47.5653L37.4845 49.4773ZM46.0335 48.0338C45.8849 47.7862 45.8212 47.6158 45.6292 47.2504C45.4736 46.9544 45.2423 46.5584 44.8645 46.2491L43.5975 47.7965C43.6517 47.8409 43.7309 47.9377 43.8588 48.181C43.9503 48.3549 44.1431 48.7704 44.3185 49.0628L46.0335 48.0338ZM44.8637 46.2484C43.6913 45.2906 42.4149 44.2582 40.9866 43.4734L40.0234 45.2262C41.2681 45.9102 42.4109 46.8272 43.5983 47.7972L44.8637 46.2484ZM40.9845 43.4723C38.7711 42.2628 36.3236 41.7107 33.7777 41.1886L33.3759 43.1478C35.9384 43.6733 38.1079 44.1796 40.0255 45.2273L40.9845 43.4723ZM32.7083 42.664C34.346 45.5328 34.9255 48.203 34.6411 50.7474C34.3555 53.3015 33.1882 55.8274 31.1628 58.3782L32.7292 59.6218C34.9185 56.8644 36.2905 53.9944 36.6287 50.9696C36.9679 47.9351 36.2555 44.8435 34.4453 41.6724L32.7083 42.664ZM32.7294 58.3784C30.8397 55.9968 29.577 53.5818 29.2314 50.7725L27.2464 51.0167C27.6514 54.3094 29.1319 57.0622 31.1626 59.6216L32.7294 58.3784ZM29.2314 50.7721C28.8877 47.9881 29.6602 45.4224 31.0736 42.8747L29.3247 41.9045C27.7842 44.6812 26.8341 47.6775 27.2464 51.0171L29.2314 50.7721ZM30.3574 41.4022C29.3303 41.2375 28.1125 41.4187 26.9159 41.7622C25.6987 42.1117 24.4105 42.6564 23.2045 43.307C21.999 43.9574 20.8488 44.728 19.9166 45.543C19.005 46.3399 18.2137 47.2553 17.855 48.2141L19.7282 48.9149C19.9149 48.4157 20.4139 47.7647 21.233 47.0487C22.0314 46.3507 23.0524 45.6616 24.1541 45.0672C25.2552 44.4732 26.4102 43.9882 27.4678 43.6846C28.5458 43.3751 29.4344 43.2798 30.0408 43.377L30.3574 41.4022ZM18.3402 49.4568C21.2702 50.939 24.0958 50.214 26.6786 49.2534L25.9814 47.3788C23.4828 48.3082 21.3728 48.7496 19.243 47.6722L18.3402 49.4568ZM25.6127 47.6194C24.9334 48.3187 23.4333 48.9382 21.5294 49.2141C19.6667 49.484 17.6562 49.3889 16.1239 48.8838L15.4977 50.7832C17.3971 51.4094 19.7336 51.4952 21.8162 51.1934C23.8578 50.8976 25.9014 50.1927 27.0473 49.0128L25.6127 47.6194ZM16.7085 50.274C18.0554 47.5292 19.8486 45.5942 22.044 44.3081C24.2482 43.0169 26.918 42.345 30.0635 42.2279L29.9891 40.2293C26.606 40.3552 23.5923 41.0832 21.0331 42.5824C18.4649 44.0869 16.4147 46.3328 14.9131 49.393L16.7085 50.274ZM30.7373 40.5255C30.2757 40.0586 29.8942 39.6578 29.5106 39.2882L28.1228 40.7282C28.4844 41.0768 28.8157 41.4266 29.3153 41.9317L30.7373 40.5255ZM29.5108 39.2883C26.7637 36.6396 23.7967 34.1615 20.2815 32.3479L19.3645 34.1253C22.6317 35.8109 25.4373 38.1388 28.1226 40.7281L29.5108 39.2883ZM20.2816 32.348C17.4111 30.8666 14.2829 29.877 10.8001 30.3981L11.0961 32.3761C14.0015 31.9414 16.6891 32.7446 19.3644 34.1252L20.2816 32.348ZM10.7998 30.3982C10.388 30.4599 9.94468 30.566 9.46564 30.6638L9.86556 32.6234C10.4449 32.5052 10.7522 32.4277 11.0964 32.376L10.7998 30.3982ZM8.68401 31.8346C9.73479 37.2345 13.3673 40.5279 17.5941 43.1923L18.6607 41.5005C14.6039 38.9431 11.5404 36.0427 10.6472 31.4526L8.68401 31.8346ZM18.2475 41.3536C17.3819 41.2489 16.2594 40.7693 14.9918 39.9335C13.742 39.1093 12.427 37.9891 11.1875 36.7057C8.68437 34.1137 6.62095 30.9947 5.96732 28.5011L4.03268 29.0082C4.81005 31.9739 7.13413 35.3875 9.74885 38.095C11.0683 39.4613 12.4947 40.6825 13.8908 41.6031C15.2691 42.512 16.6967 43.1806 18.0073 43.3392L18.2475 41.3536ZM5.15997 29.7417C13.3246 28.4186 20.4108 29.874 26.173 35.9217L27.621 34.5421C21.2872 27.8944 13.4694 26.369 4.84003 27.7675L5.15997 29.7417ZM27.8093 34.8223C25.3851 29.4232 24.8341 24.3801 25.7561 19.5859C26.6809 14.7778 29.1009 10.1516 32.7265 5.62517L31.1655 4.37483C27.4012 9.07433 24.7952 13.9927 23.7921 19.2082C22.7863 24.4378 23.4058 29.8977 25.9847 35.6415L27.8093 34.8223ZM31.1655 5.62512C34.7619 10.1157 37.1747 14.7077 38.1168 19.4869C39.0562 24.2523 38.5469 29.2701 36.1948 34.6504L38.0274 35.4515C40.5299 29.7268 41.1033 24.2956 40.0791 19.1001C39.0576 13.9183 36.4606 9.03731 32.7265 4.37488L31.1655 5.62512Z" mask="url(#path-1-outside-1_16_6)" /> </svg>
<div>${floGlobals.myFloID}</div>
`)
isShowingFloID = true
} else {
renderElem(getRef('user_popup_button'), html`
<svg class="icon margin-right-0-5" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><path d="M17.06,11.57C17.65,10.88,18,9.98,18,9c0-1.86-1.27-3.43-3-3.87L15,3h-2v2h-2V3H9v2H6v2h2v10H6v2h3v2h2v-2h2v2h2v-2 c2.21,0,4-1.79,4-4C19,13.55,18.22,12.27,17.06,11.57z M10,7h4c1.1,0,2,0.9,2,2s-0.9,2-2,2h-4V7z M15,17h-5v-4h5c1.1,0,2,0.9,2,2 S16.1,17,15,17z"/></g></svg>
<div >${floGlobals.myBtcID}</div>
`)
isShowingFloID = false
}
}, 1000 * 60);
maxCardsPerSection = floGlobals.isSubAdmin ? 3 : 2
if (floGlobals.isSubAdmin) {
document.querySelectorAll('.admin-option').forEach(elem => elem.classList.remove('hidden'))
} else {
document.querySelectorAll('.admin-option').forEach(elem => elem.classList.add('hidden'))
}
await Promise.all([
floCloudAPI.requestObjectData('cc'),
])
getRef('preview_options').innerHTML = `
${floGlobals.isSubAdmin ? `
<button class="icon-only" title="Share preview" onclick="sharePreview()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <g> <rect fill="none" height="24" width="24" /> </g> <g> <path d="M16,5l-1.42,1.42l-1.59-1.59V16h-1.98V4.83L9.42,6.42L8,5l4-4L16,5z M20,10v11c0,1.1-0.9,2-2,2H6c-1.11,0-2-0.9-2-2V10 c0-1.11,0.89-2,2-2h3v2H6v11h12V10h-3V8h3C19.1,8,20,8.89,20,10z" /> </g> </svg>
</button>
<button class="icon-only" title="Export selection" onclick="exportSelection()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <g> <rect fill="none" height="24" width="24" /> </g> <g> <path d="M18,15v3H6v-3H4v3c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-3H18z M17,11l-1.41-1.41L13,12.17V4h-2v8.17L8.41,9.59L7,11l5,5 L17,11z" /> </g> </svg>
</button>
`: ''}
<sm-button variant="primary" onclick="${floGlobals.isSubAdmin ? 'publishArticle()' : 'exportSelection()'}">
${floGlobals.isSubAdmin ? `
<svg class="icon margin-right-0-5" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <path d="M0 0h24v24H0V0z" fill="none" /> <path d="M5 4h14v2H5zm0 10h4v6h6v-6h4l-7-7-7 7zm8-2v6h-2v-6H9.83L12 9.83 14.17 12H13z" /> </svg>
Publish
`: `
<svg class="icon margin-right-0-5" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <g> <rect fill="none" height="24" width="24" /> </g> <g> <path d="M18,15v3H6v-3H4v3c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-3H18z M17,11l-1.41-1.41L13,12.17V4h-2v8.17L8.41,9.59L7,11l5,5 L17,11z" /> </g> </svg>
Download
`}
</sm-button>
`
if (window.location.hash.includes('sign_in') || window.location.hash.includes('sign_up')) {
const hash = floGlobals.preview || floGlobals.redirectToArticle || ' '
history.replaceState(null, null, hash)
}
routeTo(window.location.hash, { firstLoad: true })
console.log(result)
}).catch(error => console.error(error))
}
</script>
<template id="head_template">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<style>
* {
padding: 0;
margin: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
font-family: "Roboto", sans-serif
}
:root {
font-size: clamp(1rem, 1.2vmax, 1.2rem)
}
html,
body {
height: 100%;
scroll-behavior: smooth
}
body {
color: rgba(var(--text-color), 1);
background: rgba(var(--background-color), 1)
}
body,
body * {
--accent-color: rgb(0, 156, 78);
--text-color: 36, 36, 36;
--background-color: 252, 252, 252;
--foreground-color: rgb(255, 255, 255);
--danger-color: rgb(255, 75, 75);
--like-color: #e91e63;
scrollbar-width: thin
}
body[data-theme=dark],
body[data-theme=dark] * {
--accent-color: rgb(14, 230, 122);
--text-color: 230, 230, 230;
--text-color-light: 170, 170, 170;
--background-color: 10, 10, 10;
--foreground-color: rgb(24, 24, 24);
--danger-color: rgb(255, 106, 106)
}
body[data-theme=dark] sm-popup::part(popup) {
background-color: var(--foreground-color)
}
p {
font-size: .9rem;
max-width: 70ch;
color: rgba(var(--text-color), 0.8)
}
p * {
font-family: inherit
}
a {
text-decoration: none;
color: var(--accent-color);
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 400;
font-family: "Calistoga", cursive;
}
sm-copy {
font-size: 0.9rem;
--button-border-radius: 0.5rem;
}
.flex {
display: -webkit-box;
display: -ms-flexbox;
display: flex
}
.flex-direction-column {
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column
}
.grid {
display: grid
}
.flow-column {
grid-auto-flow: column
}
.gap-0-5 {
gap: .5rem
}
.gap-1 {
gap: 1rem
}
.gap-1-5 {
gap: 1.5rem
}
.gap-2 {
gap: 2rem
}
.gap-3 {
gap: 3rem
}
.justify-self-center {
justify-self: center
}
.justify-self-start {
justify-self: start
}
.justify-self-end {
justify-self: end
}
.align-center {
align-items: center;
}
.icon {
width: 1.2rem;
height: 1.2rem;
fill: rgba(var(--text-color), 0.8);
-ms-flex-negative: 0;
flex-shrink: 0
}
button,
.button {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
position: relative;
display: -webkit-inline-box;
display: -ms-inline-flexbox;
display: inline-flex;
border: none;
background-color: transparent;
overflow: hidden;
color: inherit;
cursor: pointer;
-webkit-transition: -webkit-transform .3s;
transition: -webkit-transform .3s;
transition: transform .3s;
transition: transform .3s, -webkit-transform .3s;
-webkit-tap-highlight-color: transparent;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
font-size: .9rem;
font-weight: 500
}
.button {
white-space: nowrap;
padding: .6rem 1rem;
border-radius: .3rem;
background-color: rgba(var(--text-color), 0.06);
color: rgba(var(--text-color), 0.8);
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center
}
.button--primary {
background-color: var(--accent-color);
color: rgba(var(--background-color), 1)
}
button:active,
.button:active,
sm-button:not([disabled]):active,
.interact:active {
-webkit-transform: scale(0.9);
transform: scale(0.9)
}
#confirmation_popup,
#prompt_popup {
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
}
#confirmation_popup h4 {
font-weight: 500;
margin-bottom: 0.5rem;
}
#confirmation_popup sm-button {
margin: 0;
}
#confirmation_popup .flex {
padding: 0;
margin-top: 1rem;
}
#confirmation_popup .flex sm-button:first-of-type {
margin-right: 0.6rem;
margin-left: auto;
}
.popup__header {
display: grid;
gap: .5rem;
width: 100%;
padding: 0 1.5rem 0 .5rem;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
grid-template-columns: auto 1fr auto
}
.popup__header__close {
padding: .5rem
}
#sign_in,
#sign_up {
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center
}
#sign_in sm-form,
#sign_up sm-form {
margin: 2rem 0;
--gap: 1rem
}
#sign_in header,
#sign_up header {
padding: 1.5rem 0
}
#sign_up sm-copy {
font-size: .9rem
}
#sign_up h5 {
font-weight: 500;
color: rgba(var(--text-color), 0.8)
}
.card {
padding: 1rem;
border-radius: .5rem;
background-color: rgba(var(--text-color), 0.04)
}
.warning {
background-color: khaki;
color: rgba(0, 0, 0, .7);
padding: 1rem;
border-radius: .5rem;
line-height: 1.5
}
#main_header {
display: grid;
gap: 1rem;
position: sticky;
top: 0;
padding: 1rem;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
grid-template-columns: 1fr auto auto;
background-color: rgba(var(--background-color), 1);
z-index: 1
}
.logo {
color: inherit;
display: grid;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
width: 100%;
grid-template-columns: auto 1fr;
gap: 0 .5rem;
margin-right: 1rem
}
.logo h4 {
text-transform: capitalize;
font-size: .9rem;
font-weight: 500
}
.main-logo {
height: 1.4rem;
width: 1.4rem;
fill: rgba(var(--text-color), 1);
stroke: none
}
article {
padding-bottom: 3rem;
gap: 1rem
}
#export_body {
position: relative;
margin-bottom: 1rem;
padding-bottom: 1.5rem;
}
#export_body::after {
justify-self: center;
position: absolute;
bottom: 0;
content: "";
width: 3rem;
height: .3rem;
border-radius: .5rem;
background-color: rgba(var(--text-color), 0.5)
}
article p {
line-height: 1.8;
font-size: 1rem
}
.page-layout {
display: grid;
grid-template-columns: 1rem minmax(0, 1fr) 1rem
}
.page-layout>* {
grid-column: 2/3
}
.hero-section {
display: grid;
grid-template-columns: minmax(0, 1fr);
margin-bottom: 1.5rem;
padding-top: 1.5rem
}
time,
#reading_time {
font-size: .8rem
}
h1 {
font-size: 1.4rem;
margin-bottom: 1rem
}
h3:not(:first-of-type) {
margin-top: 2rem
}
.full-bleed {
grid-column: 1/-1
}
.quote-template {
position: relative;
padding: 1rem;
margin: 1rem 0;
border-radius: .2rem;
border: solid thin rgba(var(--text-color), 0.3);
-webkit-box-shadow: .3rem .5rem 0 .1rem rgba(var(--text-color), 0.8);
box-shadow: .3rem .5rem 0 .1rem rgba(var(--text-color), 0.8);
overflow: hidden;
justify-self: center;
padding-left: 1.3rem
}
.quote-template figcaption {
margin-top: .5rem;
color: rgba(var(--text-color), 0.8);
font-size: .8rem;
margin-left: auto
}
#article_contributors {
flex-wrap: wrap;
gap: 0.3rem;
margin: 0.5rem 0 1rem 0;
}
.contributor {
font-size: 0.8rem;
background-color: rgba(var(--text-color), 0.06);
border-radius: 0.3rem;
padding: 0.3rem 0.5rem;
}
.upvote {
display: grid;
grid-template-columns: auto 1fr;
position: relative;
padding: .8rem;
border-radius: 2rem;
background-color: var(--foreground-color);
-webkit-box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .1);
box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .1);
border: solid rgba(var(--text-color), 0.2) thin
}
.upvote>* {
pointer-events: none
}
.upvote:active {
-webkit-transform: none;
transform: none
}
.upvote:active .icon {
-webkit-transform: scale(0.7);
transform: scale(0.7)
}
.upvote.liked {
background-color: var(--like-color);
color: #fff
}
.upvote.liked .icon {
fill: #fff
}
.expanding-heart,
.ring {
grid-area: 1/1
}
.ring {
border: .1rem solid var(--like-color);
border-radius: 50%;
height: .5rem;
width: .5rem;
justify-self: center
}
.upvote .icon {
grid-area: 1/1;
fill: var(--like-color);
height: 1.5rem;
width: 1.5rem;
-webkit-transition: -webkit-transform .2s;
transition: -webkit-transform .2s;
transition: transform .2s;
transition: transform .2s, -webkit-transform .2s
}
.temp-count,
#like_count {
grid-area: 1/2
}
.temp-count:not(:empty),
#like_count:not(:empty) {
margin-left: .4rem
}
footer {
padding: 3rem 1.5rem;
justify-items: center
}
@media screen and (min-width: 40rem) {
sm-popup {
--width: 24rem
}
.popup__header {
padding: 1rem 1.5rem 0 1rem
}
.page-layout {
grid-template-columns: 1fr 60ch 1fr
}
h1 {
font-size: 2rem
}
}
@media(any-hover: hover) {
::-webkit-scrollbar {
width: .5rem;
height: .5rem
}
::-webkit-scrollbar-thumb {
background: rgba(var(--text-color), 0.3);
border-radius: 1rem
}
::-webkit-scrollbar-thumb:hover {
background: rgba(var(--text-color), 0.5)
}
}
.hidden {
display: none
}
</style>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Calistoga&family=Roboto:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap"
rel="stylesheet">
</template>
<template id="body_template">
<header id="main_header">
<a href="https://ranchimall.github.io/rmtimes/" class="logo">
<svg class="main-logo" viewBox="0 0 27.25 32">
<title>RanchiMall</title>
<path
d="M27.14,30.86c-.74-2.48-3-4.36-8.25-6.94a20,20,0,0,1-4.2-2.49,6,6,0,0,1-1.25-1.67,4,4,0,0,1,0-2.26c.37-1.08.79-1.57,3.89-4.55a11.66,11.66,0,0,0,3.34-4.67,6.54,6.54,0,0,0,.05-2.82C20,3.6,18.58,2,16.16.49c-.89-.56-1.29-.64-1.3-.24a3,3,0,0,1-.3.72l-.3.55L13.42.94C13,.62,12.4.26,12.19.15c-.4-.2-.73-.18-.72.05a9.39,9.39,0,0,1-.61,1.33s-.14,0-.27-.13C8.76.09,8-.27,8,.23A11.73,11.73,0,0,1,6.76,2.6C4.81,5.87,2.83,7.49.77,7.49c-.89,0-.88,0-.61,1,.22.85.33.92,1.09.69A5.29,5.29,0,0,0,3,8.33c.23-.17.45-.29.49-.26a2,2,0,0,1,.22.63A1.31,1.31,0,0,0,4,9.34a5.62,5.62,0,0,0,2.27-.87L7,8l.13.55c.19.74.32.82,1,.65a7.06,7.06,0,0,0,3.46-2.47l.6-.71-.06.64c-.17,1.63-1.3,3.42-3.39,5.42L6.73,14c-3.21,3.06-3,5.59.6,8a46.77,46.77,0,0,0,4.6,2.41c.28.13,1,.52,1.59.87,3.31,2,4.95,3.92,4.95,5.93a2.49,2.49,0,0,0,.07.77h0c.09.09,0,.1.9-.14a2.61,2.61,0,0,0,.83-.32,3.69,3.69,0,0,0-.55-1.83A11.14,11.14,0,0,0,17,26.81a35.7,35.7,0,0,0-5.1-2.91C9.37,22.64,8.38,22,7.52,21.17a3.53,3.53,0,0,1-1.18-2.48c0-1.38.71-2.58,2.5-4.23,2.84-2.6,3.92-3.91,4.67-5.65a3.64,3.64,0,0,0,.42-2A3.37,3.37,0,0,0,13.61,5l-.32-.74.29-.48c.17-.27.37-.63.46-.8l.15-.3.44.64a5.92,5.92,0,0,1,1,2.81,5.86,5.86,0,0,1-.42,1.94c0,.12-.12.3-.15.4a9.49,9.49,0,0,1-.67,1.1,28,28,0,0,1-4,4.29C8.62,15.49,8.05,16.44,8,17.78a3.28,3.28,0,0,0,1.11,2.76c.95,1,2.07,1.74,5.25,3.32,3.64,1.82,5.22,2.9,6.41,4.38A4.78,4.78,0,0,1,21.94,31a3.21,3.21,0,0,0,.14.92,1.06,1.06,0,0,0,.43-.05l.83-.22.46-.12-.06-.46c-.21-1.53-1.62-3.25-3.94-4.8a37.57,37.57,0,0,0-5.22-2.82A13.36,13.36,0,0,1,11,21.19a3.36,3.36,0,0,1-.8-4.19c.41-.85.83-1.31,3.77-4.15,2.39-2.31,3.43-4.13,3.43-6a5.85,5.85,0,0,0-2.08-4.29c-.23-.21-.44-.43-.65-.65A2.5,2.5,0,0,1,15.27.69a10.6,10.6,0,0,1,2.91,2.78A4.16,4.16,0,0,1,19,6.16a4.91,4.91,0,0,1-.87,3c-.71,1.22-1.26,1.82-4.27,4.67a9.47,9.47,0,0,0-2.07,2.6,2.76,2.76,0,0,0-.33,1.54,2.76,2.76,0,0,0,.29,1.47c.57,1.21,2.23,2.55,4.65,3.73a32.41,32.41,0,0,1,5.82,3.24c2.16,1.6,3.2,3.16,3.2,4.8a1.94,1.94,0,0,0,.09.76,4.54,4.54,0,0,0,1.66-.4C27.29,31.42,27.29,31.37,27.14,30.86ZM6.1,7h0a3.77,3.77,0,0,1-1.46.45L4,7.51l.68-.83a25.09,25.09,0,0,0,3-4.82A12,12,0,0,1,8.28.76c.11-.12.77.32,1.53,1l.63.58-.57.84A10.34,10.34,0,0,1,6.1,7Zm5.71-1.78A9.77,9.77,0,0,1,9.24,7.18h0a5.25,5.25,0,0,1-1.17.28l-.58,0,.65-.78a21.29,21.29,0,0,0,2.1-3.12c.22-.41.42-.76.44-.79s.5.43.9,1.24L12,5ZM13.41,3a2.84,2.84,0,0,1-.45.64,11,11,0,0,1-.9-.91l-.84-.9.19-.45c.34-.79.39-.8,1-.31A9.4,9.4,0,0,1,13.8,2.33q-.18.34-.39.69Z" />
</svg>
<h4>RanchiMall Times</h4>
</a>
<theme-toggle></theme-toggle>
<button id="user_button" class="hidden" onclick="openPopup('user_popup')">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M12 5.9c1.16 0 2.1.94 2.1 2.1s-.94 2.1-2.1 2.1S9.9 9.16 9.9 8s.94-2.1 2.1-2.1m0 9c2.97 0 6.1 1.46 6.1 2.1v1.1H5.9V17c0-.64 3.13-2.1 6.1-2.1M12 4C9.79 4 8 5.79 8 8s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 9c-2.67 0-8 1.34-8 4v3h16v-3c0-2.66-5.33-4-8-4z" />
</svg>
</button>
</header>
<article class="page-layout">
<section class="hero-section">
<h1 id="exported_title"></h1>
<div class="flex align-center">
<time id="exported_time"></time>&nbsp;&nbsp;<span id="reading_time"></span>
</div>
</section>
<section id="export_body" class="grid gap-1"></section>
<section>
<h4>Article by -</h4>
<div id="article_contributors" class="flex"></div>
<span>created with RanchiMall Content collaboration app</span>
</section>
</article>
<script>
const themeToggle = document.createElement('template');
themeToggle.innerHTML = `
<style>
*{
padding: 0;
margin: 0;
box-sizing: border-box;
}
:host{
cursor: pointer;
--height: 2.5rem;
--width: 2.5rem;
}
.theme-toggle {
display: flex;
position: relative;
width: 1.4rem;
height: 1.4rem;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.theme-toggle::after{
content: '';
position: absolute;
height: var(--height);
width: var(--width);
top: 50%;
left: 50%;
opacity: 0;
border-radius: 50%;
pointer-events: none;
transition: transform 0.3s, opacity 0.3s;
transform: translate(-50%, -50%) scale(1.2);
background-color: rgba(var(--text-color), 0.12);
}
:host(:focus-within) .theme-toggle{
outline: none;
}
:host(:focus-within) .theme-toggle::after{
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
.icon {
position: absolute;
height: 100%;
width: 100%;
fill: rgba(var(--text-color), 1);
transition: transform 0.3s, opacity 0.1s;
}
.theme-switcher__checkbox {
display: none;
}
:host([checked]) .moon-icon {
transform: translateY(50%);
opacity: 0;
}
:host(:not([checked])) .sun-icon {
transform: translateY(50%);
opacity: 0;
}
</style>
<label class="theme-toggle" title="Change theme" tabindex="0">
<slot name="light-mode-icon">
<svg xmlns="http://www.w3.org/2000/svg" class="icon moon-icon" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><rect fill="none" height="24" width="24"/><path d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"/></svg>
</slot>
<slot name="dark-mode-icon">
<svg xmlns="http://www.w3.org/2000/svg" class="icon sun-icon" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><rect fill="none" height="24" width="24"/><path d="M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"/></svg>
</slot>
</label>
`;
class ThemeToggle extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: 'open'
}).append(themeToggle.content.cloneNode(true));
this.isChecked = false;
this.hasTheme = 'light';
this.toggleState = this.toggleState.bind(this);
this.fireEvent = this.fireEvent.bind(this);
this.handleThemeChange = this.handleThemeChange.bind(this);
}
static get observedAttributes() {
return ['checked'];
}
daylight() {
this.hasTheme = 'light';
document.body.dataset.theme = 'light';
this.setAttribute('aria-checked', 'false');
}
nightlight() {
this.hasTheme = 'dark';
document.body.dataset.theme = 'dark';
this.setAttribute('aria-checked', 'true');
}
toggleState() {
this.toggleAttribute('checked');
this.fireEvent();
}
handleKeyDown(e) {
if (e.key === ' ') {
this.toggleState();
}
}
handleThemeChange(e) {
if (e.detail.theme !== this.hasTheme) {
if (e.detail.theme === 'dark') {
this.setAttribute('checked', '');
}
else {
this.removeAttribute('checked');
}
}
}
fireEvent() {
this.dispatchEvent(
new CustomEvent('themechange', {
bubbles: true,
composed: true,
detail: {
theme: this.hasTheme
}
})
);
}
connectedCallback() {
this.setAttribute('role', 'switch');
this.setAttribute('aria-label', 'theme toggle');
if (localStorage.getItem(`${window.location.hostname}-theme`) === "dark") {
this.nightlight();
this.setAttribute('checked', '');
} else if (localStorage.getItem(`${window.location.hostname}-theme`) === "light") {
this.daylight();
this.removeAttribute('checked');
}
else {
if (window.matchMedia(`(prefers-color-scheme: dark)`).matches) {
this.nightlight();
this.setAttribute('checked', '');
} else {
this.daylight();
this.removeAttribute('checked');
}
}
this.addEventListener("click", this.toggleState);
this.addEventListener("keydown", this.handleKeyDown);
document.addEventListener('themechange', this.handleThemeChange);
}
disconnectedCallback() {
this.removeEventListener("click", this.toggleState);
this.removeEventListener("keydown", this.handleKeyDown);
document.removeEventListener('themechange', this.handleThemeChange);
}
attributeChangedCallback(name, oldVal, newVal) {
if (name === 'checked') {
if (this.hasAttribute('checked')) {
this.nightlight();
localStorage.setItem(`${window.location.hostname}-theme`, "dark");
} else {
this.daylight();
localStorage.setItem(`${window.location.hostname}-theme`, "light");
}
}
}
}
window.customElements.define('theme-toggle', ThemeToggle);
</script>
</template>
<template id="voting_enabled">
<footer class="grid gap-1-5">
<h4>Loved the article? Don't forget leave a like.</h4>
<button id="upvote_button" class="button upvote">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0z" fill="none" />
<path
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
</svg>
<div id="like_count">Loading...</div>
</button>
</footer>
<sm-popup id="confirmation_popup">
<h4 id="confirm_title"></h4>
<p id="confirm_message"></p>
<div class="flex align-center">
<sm-button variant="no-outline" class="cancel-btn">Cancel</sm-button>
<sm-button variant="no-outline" class="submit-btn">OK</sm-button>
</div>
</sm-popup>
<sm-popup id="sign_in_popup">
<header slot="header" class="popup__header">
<button class="popup__header__close" onclick="closePopup()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
</svg>
</button>
</header>
<section id="sign_in" class="">
<div class="grid gap-0-5">
<h3>Sign in to like this article</h3>
<p>Liking an article supports the creators.</p>
</div>
<sm-form>
<sm-input id="private_key_field" type="password" placeholder="FLO/BTC private key"
error-text="Private key is invalid" data-private-key required></sm-input>
<sm-button id="sign_in_button" variant="primary" disabled>Sign In</sm-button>
</sm-form>
<div class="grid gap-0-5">
<p>New here? Generate your FLO credentials below to continue</p>
<button class="button" onclick="generateCredentials()">
Get FLO credentials
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000">
<path d="M0 0h24v24H0z" fill="none" />
<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" />
</svg>
</button>
</div>
</section>
<section id="sign_up" class="grid gap-1-5 hidden">
<button class="justify-self-start" onclick="goToSignIn()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0z" fill="none" />
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
</svg>
</button>
<div class="grid gap-0-5">
<h3>FLO credentials</h3>
<p>You can use FLO credentials with RanchiMall Times and all RanchiMall FLO apps. </p>
</div>
<div class="grid gap-1-5 card">
<div class="grid gap-0-5">
<h5>FLO ID</h5>
<sm-copy id="generated_flo_id"></sm-copy>
</div>
<div class="grid gap-0-5">
<h5>Private key</h5>
<sm-copy id="generated_private_key"></sm-copy>
</div>
</div>
<sm-button id="sign_up_button" variant="primary">Sign in with these credentials</sm-button>
<strong class="warning">
Keep your private key secure and don't share with anyone.
Once lost there is no way to recover private key.
</strong>
</section>
</sm-popup>
<sm-popup id="user_popup">
<header slot="header" class="popup__header">
<div class="flex align-center">
<button class="popup__header__close" onclick="closePopup()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
</svg>
</button>
</div>
</header>
<section class="grid gap-1-5">
<div class="grid gap-0-5">
<h5>My FLO ID</h5>
<sm-copy id="user_flo_id"></sm-copy>
</div>
<sm-button class="danger" onclick="signOut()">Sign out</sm-button>
</section>
</sm-popup>
<script id="floGlobals">
/* Constants for FLO blockchain operations !!Make sure to add this at beginning!! */
const floGlobals = {
//Required for all
blockchain: "FLO",
//Required for blockchain API operators
apiURL: {
FLO: ['https://livenet.flocha.in/', 'https://flosight.duckdns.org/'],
FLO_TEST: ['https://testnet-flosight.duckdns.org/', 'https://testnet.flocha.in/']
},
adminID: "FF5pewfsJxyrCvg8a2C8VXefeyVvKvQxmF",
sendAmt: 0.001,
fee: 0.0005,
//Required for Supernode operations
SNStorageID: "FNaN9McoBAEFUjkRmNQRYLmBF8SpS7Tgfk",
supernodes: {}, //each supnernode must be stored as floID : {uri:<uri>,pubKey:<publicKey>}
//for cloud apps
subAdmins: [],
application: "RM_Times",
appObjects: {},
generalData: {},
lastVC: {}
}
</script>
<script>
const slideInLeft = [
{
opacity: 0,
transform: 'translateX(1rem)'
},
{
opacity: 1,
transform: 'translateX(0)'
}
]
const slideOutLeft = [
{
opacity: 1,
transform: 'translateX(0)'
},
{
opacity: 0,
transform: 'translateX(-1rem)'
},
]
const slideInRight = [
{
opacity: 0,
transform: 'translateX(-1rem)'
},
{
opacity: 1,
transform: 'translateX(0)'
}
]
const slideOutRight = [
{
opacity: 1,
transform: 'translateX(0)'
},
{
opacity: 0,
transform: 'translateX(1rem)'
},
]
const slideInDown = [
{
opacity: 0,
transform: 'translateY(-1rem)'
},
{
opacity: 1,
transform: 'translateY(0)'
},
]
const slideOutUp = [
{
opacity: 1,
transform: 'translateY(0)'
},
{
opacity: 0,
transform: 'translateY(-1rem)'
},
]
// Global variables
const domRefs = {};
let timerId;
const currentYear = new Date().getFullYear();
//Checks for internet connection status
if (!navigator.onLine)
notify(
"There seems to be a problem connecting to the internet, Please check you internet connection.",
"error",
{ sound: true }
);
window.addEventListener("offline", () => {
notify(
"There seems to be a problem connecting to the internet, Please check you internet connection.",
"error",
{ pinned: true, sound: true }
);
});
window.addEventListener("online", () => {
getRef("notification_drawer").clearAll();
notify("We are back online.", "success");
});
// Use instead of document.getElementById
function getRef(elementId) {
if (!domRefs.hasOwnProperty(elementId)) {
domRefs[elementId] = {
count: 1,
ref: null,
};
return document.getElementById(elementId);
} else {
if (domRefs[elementId].count < 3) {
domRefs[elementId].count = domRefs[elementId].count + 1;
return document.getElementById(elementId);
} else {
if (!domRefs[elementId].ref)
domRefs[elementId].ref = document.getElementById(elementId);
return domRefs[elementId].ref;
}
}
}
let zIndex = 50
// function required for popups or modals to appear
function openPopup(popupId, pinned) {
zIndex++
getRef(popupId).setAttribute('style', `z-index: ${zIndex}`)
getRef(popupId).show({ pinned })
return getRef(popupId);
}
// hides the popup or modal
function closePopup() {
if (popupStack.peek() === undefined)
return;
popupStack.peek().popup.hide()
}
document.addEventListener('popupclosed', e => {
switch (e.target.id) {
case 'sign_in_popup':
getRef('sign_in').classList.remove('hidden')
getRef('sign_in').style = ''
getRef('sign_up').classList.add('hidden')
break
}
})
// displays a popup for asking permission. Use this instead of JS confirm
const getConfirmation = (title, options = {}) => {
return new Promise(resolve => {
const { message, cancelText = 'Cancel', confirmText = 'OK' } = options
openPopup('confirmation_popup', true)
getRef('confirm_title').textContent = title;
getRef('confirm_message').textContent = message;
let cancelButton = getRef('confirmation_popup').children[2].children[0],
submitButton = getRef('confirmation_popup').children[2].children[1]
submitButton.textContent = confirmText
cancelButton.textContent = cancelText
submitButton.onclick = () => {
closePopup()
resolve(true);
}
cancelButton.onclick = () => {
closePopup()
resolve(false);
}
})
}
let currentArticleID
window.addEventListener("load", () => {
currentArticleID = document.body.dataset.articleId
document.body.classList.remove('hidden')
document.querySelectorAll('sm-input[data-private-key]').forEach(input => input.customValidation = floCrypto.getPubKeyHex)
document.addEventListener('keyup', (e) => {
if (e.key === 'Escape') {
closePopup()
}
})
document.addEventListener('copy', () => {
notify('copied', 'success')
})
});
function animateTo(element, keyframes, options) {
const anime = element.animate(keyframes, { ...options, fill: 'both' })
anime.finished.then(() => {
anime.commitStyles()
anime.cancel()
})
return anime
}
// Use when a function needs to be executed after user finishes changes
const debounce = (callback, wait) => {
let timeoutId = null;
return (...args) => {
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => {
callback.apply(null, args);
}, wait);
};
}
let tempVoteCount = 0
getRef('upvote_button').addEventListener('mouseup', function () {
if (isLoggedIn) {
tempVoteCount++;
const animOptions = {
fill: 'forwards',
duration: 300,
ease: 'easing',
}
const ring = document.createElement('div')
ring.classList.add('ring')
ring.animate([
{ transform: 'none' },
{ transform: 'scale(6)', opacity: 0 }
], animOptions).onfinish = e => {
e.target.cancel()
ring.remove()
}
this.append(ring)
this.firstElementChild.animate([
{ transform: 'scale(0.5)' },
{ transform: 'scale(1.4)', offset: 0.5 },
{ transform: 'none' },
], animOptions).onfinish = e => e.target.cancel()
}
})
function animateLikeCount(voteCount = 1, articleID) {
const animOptions = {
fill: 'forwards',
duration: 150,
ease: 'easing',
}
floGlobals.appObjects.rmTimes.articles[articleID].votes += voteCount
getRef('like_count').animate(slideOutUp, animOptions)
.onfinish = (e) => {
e.target.cancel()
}
const tempCount = document.createElement('div')
tempCount.classList.add('temp-count')
tempCount.textContent = floGlobals.appObjects.rmTimes.articles[articleID].votes
getRef('like_count').after(tempCount)
tempCount.animate(slideInUp, animOptions)
.onfinish = () => {
getRef('like_count').textContent = floGlobals.appObjects.rmTimes.articles[articleID].votes
tempCount.remove()
}
}
getRef('upvote_button').addEventListener('click', debounce(() => {
if (isLoggedIn) {
floCloudAPI.sendGeneralData({
voteCount: tempVoteCount,
}, `article_${currentArticleID}_votes`)
.then(res => {
tempVoteCount = 0
console.log('up voted')
})
.catch(err => console.log(err))
} else {
openPopup('sign_in_popup')
}
}, 300))
function generateCredentials() {
const animOptions = {
fill: 'forwards',
duration: 150,
ease: 'easing',
}
const { floID, privKey } = floCrypto.generateNewID()
getRef('generated_flo_id').value = floID
getRef('generated_private_key').value = privKey
animateTo(getRef('sign_in'), slideOutLeft, animOptions).onfinish = () => {
getRef('sign_in').classList.add('hidden')
getRef('sign_up').classList.remove('hidden')
animateTo(getRef('sign_up'), slideInLeft, animOptions)
}
}
function goToSignIn() {
const animOptions = {
fill: 'forwards',
duration: 150,
ease: 'easing',
}
animateTo(getRef('sign_up'), slideOutRight, animOptions).onfinish = () => {
getRef('sign_in').classList.remove('hidden')
animateTo(getRef('sign_in'), slideInRight, animOptions)
getRef('sign_up').classList.add('hidden')
}
}
function signOut() {
getConfirmation('Sign out?', 'You are about to sign out of the app, continue?', 'Stay', 'Leave')
.then(async (res) => {
if (res) {
await floDapps.clearCredentials()
getRef('user_button').classList.add('hidden')
location.reload()
}
})
}
</script>
<script id="onLoadStartUp">
floGlobals.isSubAdmin = false
let isLoggedIn = false
function onLoadStartUp() {
//floDapps.addStartUpFunction('Sample', Promised Function)
//floDapps.setAppObjectStores({sampleObs1:{}, sampleObs2:{options{autoIncrement:true, keyPath:'SampleKey'}, Indexes:{sampleIndex:{}}}})
let firstLoad = true
floDapps.setMidStartup(() => new Promise(async (resolve, reject) => {
await floCloudAPI.requestObjectData('rmTimes')
await floCloudAPI.requestGeneralData(`article_${currentArticleID}_votes`, {
lowerVectorClock: floGlobals.appObjects.rmTimes.articles[currentArticleID].lastCountedVC + 1,
callback: (allVotes, e) => {
if (firstLoad) {
for (const vote in allVotes) {
floGlobals.appObjects.rmTimes.articles[currentArticleID].votes += allVotes[vote].message.voteCount || 1
}
getRef('like_count').textContent = floGlobals.appObjects.rmTimes.articles[currentArticleID].votes
} else {
for (const msg in allVotes) {
animateLikeCount(allVotes[msg].message.voteCount, currentArticleID)
}
}
firstLoad = false
}
})
resolve(true)
}))
floDapps.setCustomPrivKeyInput(() => new Promise((resolve, reject) => {
getRef('sign_in_button').onclick = () => {
resolve(getRef('private_key_field').value.trim())
getRef('private_key_field').value = ''
closePopup()
}
getRef('sign_up_button').onclick = () => {
resolve(getRef('generated_private_key').value.trim())
getRef('generated_private_key').value = ''
closePopup()
}
}))
floDapps.launchStartUp().then(async result => {
isLoggedIn = true
if (!floCrypto.validateFloID(floDapps.user.id)) {
floGlobals.myBtcID = floDapps.user.id
const type = coinjs.addressDecode(floGlobals.myBtcID).type
if (type === 'standard') {
floGlobals.myFloID = btcOperator.convert.legacy2legacy(floGlobals.myBtcID, 0x23);
} else if (type === 'bech32') {
floGlobals.myFloID = btcOperator.convert.bech2legacy(floGlobals.myBtcID, 0x23);
} else {
notify(`Multisig address can't be used to sign in`, 'error');
return;
}
} else {
floGlobals.myFloID = floDapps.user.id
floGlobals.myBtcID = btcOperator.convert.legacy2bech(floDapps.user.id)
}
floGlobals.isSubAdmin = floGlobals.subAdmins.includes(floGlobals.myFloID)
getRef('user_flo_id').value = floGlobals.myFloID
getRef('user_button').classList.remove('hidden')
console.log(result)
}).catch(error => console.error(error))
}
</script>
<script>
const smButton = document.createElement('template')
smButton.innerHTML = `
<style>
*{
padding: 0;
margin: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
:host{
display: -webkit-inline-box;
display: -ms-inline-flexbox;
display: inline-flex;
width: auto;
--accent-color: #4d2588;
--text-color: 17, 17, 17;
--background-color: 255, 255, 255;
--padding: 0.6rem 1.2rem;
--border-radius: 0.3rem;
--background: rgba(var(--text-color), 0.1);
}
:host([variant='primary']) .button{
background: var(--accent-color);
color: rgba(var(--background-color), 1);
}
:host([variant='outlined']) .button{
-webkit-box-shadow: 0 0 0 1px rgba(var(--text-color), 0.2) inset;
box-shadow: 0 0 0 1px rgba(var(--text-color), 0.2) inset;
background: transparent;
color: var(--accent-color);
}
:host([variant='no-outline']) .button{
background: inherit;
color: var(--accent-color);
}
:host([disabled]){
pointer-events: none;
cursor: not-allowed;
}
.button {
position: relative;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
width: 100%;
padding: var(--padding);
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
border-radius: var(--border-radius);
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
transition: box-shadow 0.3s, background-color 0.3s;
font-family: inherit;
font-size: 0.9rem;
font-weight: 500;
background-color: var(--background);
-webkit-tap-highlight-color: transparent;
outline: none;
overflow: hidden;
border: none;
color: inherit;
align-items: center;
}
:host([disabled]) .button{
pointer-events: none;
cursor: not-allowed;
opacity: 0.6;
color: rgba(var(--text-color), 1);
background-color: rgba(var(--text-color), 0.3);
}
@media (hover: hover){
:host(:not([disabled])) .button:hover,
:host(:focus-within:not([disabled])) .button{
-webkit-box-shadow: 0 0.1rem 0.1rem rgba(0, 0, 0, 0.1), 0 0.2rem 0.8rem rgba(0, 0, 0, 0.12);
box-shadow: 0 0.1rem 0.1rem rgba(0, 0, 0, 0.1), 0 0.2rem 0.8rem rgba(0, 0, 0, 0.12);
}
:host([variant='outlined']:not([disabled])) .button:hover,
:host(:focus-within[variant='outlined']:not([disabled])) .button:hover{
-webkit-box-shadow: 0 0 0 1px rgba(var(--text-color), 0.2) inset, 0 0.1rem 0.1rem rgba(0, 0, 0, 0.1), 0 0.4rem 0.8rem rgba(0, 0, 0, 0.12);
box-shadow: 0 0 0 1px rgba(var(--text-color), 0.2) inset, 0 0.1rem 0.1rem rgba(0, 0, 0, 0.1), 0 0.4rem 0.8rem rgba(0, 0, 0, 0.12);
}
}
@media (hover: none){
:host(:not([disabled])) .button:active{
-webkit-box-shadow: 0 0.1rem 0.1rem rgba(0, 0, 0, 0.1), 0 0.2rem 0.8rem rgba(0, 0, 0, 0.2);
box-shadow: 0 0.1rem 0.1rem rgba(0, 0, 0, 0.1), 0 0.2rem 0.8rem rgba(0, 0, 0, 0.2);
}
:host([variant='outlined']) .button:active{
-webkit-box-shadow: 0 0 0 1px rgba(var(--text-color), 0.2) inset, 0 0.1rem 0.1rem rgba(0, 0, 0, 0.1), 0 0.4rem 0.8rem rgba(0, 0, 0, 0.2);
box-shadow: 0 0 0 1px rgba(var(--text-color), 0.2) inset, 0 0.1rem 0.1rem rgba(0, 0, 0, 0.1), 0 0.4rem 0.8rem rgba(0, 0, 0, 0.2);
}
}
</style>
<div part="button" class="button">
<slot></slot>
</div>`;
customElements.define('sm-button',
class extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: 'open'
}).append(smButton.content.cloneNode(true));
}
static get observedAttributes() {
return ['disabled'];
}
get disabled() {
return this.hasAttribute('disabled');
}
set disabled(value) {
if (value) {
this.setAttribute('disabled', '');
} else {
this.removeAttribute('disabled');
}
}
focusIn() {
this.focus();
}
handleKeyDown(e) {
if (!this.hasAttribute('disabled') && (e.key === 'Enter' || e.key === ' ')) {
e.preventDefault();
this.click();
}
}
connectedCallback() {
if (!this.hasAttribute('disabled')) {
this.setAttribute('tabindex', '0');
}
this.setAttribute('role', 'button');
this.addEventListener('keydown', this.handleKeyDown);
}
attributeChangedCallback(name) {
if (name === 'disabled') {
if (this.hasAttribute('disabled')) {
this.removeAttribute('tabindex');
} else {
this.setAttribute('tabindex', '0');
}
this.setAttribute('aria-disabled', this.hasAttribute('disabled'));
}
}
})
const smInput = document.createElement('template')
smInput.innerHTML = `
<style>
*{
padding: 0;
margin: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
input[type="search"]::-webkit-search-decoration,
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-results-button,
input[type="search"]::-webkit-search-results-decoration { display: none; }
input[type=number] {
-moz-appearance:textfield;
}
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
margin: 0;
}
input::-ms-reveal,
input::-ms-clear {
display: none;
}
input:invalid{
outline: none;
-webkit-box-shadow: none;
box-shadow: none;
}
::-moz-focus-inner{
border: none;
}
:host{
display: -webkit-box;
display: -ms-flexbox;
display: flex;
--accent-color: #4d2588;
--text-color: 17, 17, 17;
--background-color: 255, 255, 255;
--success-color: #00C853;
--danger-color: red;
--width: 100%;
--icon-gap: 0.5rem;
--border-radius: 0.3rem;
--padding: 0.7rem 1rem;
--background: rgba(var(--text-color), 0.06);
}
.hide{
opacity: 0 !important;
pointer-events: none !important;
}
.hidden{
display: none;
}
.icon {
fill: rgba(var(--text-color), 0.6);
height: 1.4rem;
width: 1.4rem;
border-radius: 1rem;
cursor: pointer;
min-width: 0;
}
:host(.round) .input{
border-radius: 10rem;
}
.input {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
cursor: text;
min-width: 0;
text-align: left;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
position: relative;
gap: var(--icon-gap);
padding: var(--padding);
border-radius: var(--border-radius);
-webkit-transition: opacity 0.3s;
-o-transition: opacity 0.3s;
transition: opacity 0.3s;
background: var(--background);
width: 100%;
outline: none;
}
.input.readonly .clear{
opacity: 0 !important;
margin-right: -2rem;
pointer-events: none !important;
}
.readonly{
pointer-events: none;
}
.input:focus-within:not(.readonly){
box-shadow: 0 0 0 0.1rem var(--accent-color) inset !important;
}
.disabled{
pointer-events: none;
opacity: 0.6;
}
.label {
font-size: inherit;
opacity: .7;
font-weight: 400;
position: absolute;
top: 0;
-webkit-transition: -webkit-transform 0.3s;
transition: -webkit-transform 0.3s;
-o-transition: transform 0.3s;
transition: transform 0.3s;
transition: transform 0.3s, -webkit-transform 0.3s;
-webkit-transform-origin: left;
-ms-transform-origin: left;
transform-origin: left;
pointer-events: none;
white-space: nowrap;
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
width: 100%;
user-select: none;
will-change: transform;
}
.outer-container{
position: relative;
width: var(--width);
}
.container{
width: 100%;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
position: relative;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
}
input{
font-size: inherit;
border: none;
background: transparent;
outline: none;
color: rgba(var(--text-color), 1);
width: 100%;
}
:host(:not([variant="outlined"])) .animate-label .container input {
-webkit-transform: translateY(0.6rem);
-ms-transform: translateY(0.6rem);
transform: translateY(0.6rem);
}
:host(:not([variant="outlined"])) .animate-label .label {
-webkit-transform: translateY(-0.7em) scale(0.8);
-ms-transform: translateY(-0.7em) scale(0.8);
transform: translateY(-0.7em) scale(0.8);
opacity: 1;
color: var(--accent-color)
}
:host([variant="outlined"]) .input {
box-shadow: 0 0 0 0.1rem var(--border-color, rgba(var(--text-color), 0.4)) inset;
background: rgba(var(--background-color), 1);
}
:host([variant="outlined"]) .label {
width: max-content;
margin-left: -0.5rem;
padding: 0 0.5rem;
}
:host([variant="outlined"]) .animate-label .label {
-webkit-transform: translate(0.1rem, -1.5rem) scale(0.8);
-ms-transform: translate(0.1rem, -1.5rem) scale(0.8);
transform: translate(0.1rem, -1.5rem) scale(0.8);
opacity: 1;
background: rgba(var(--background-color), 1);
}
.animate-label:focus-within:not(.readonly) .label{
color: var(--accent-color)
}
.feedback-text:not(:empty){
display: flex;
width: 100%;
text-align: left;
font-size: 0.9rem;
align-items: center;
padding: 0.8rem 0;
color: rgba(var(--text-color), 0.8);
}
.success{
color: var(--success-color);
}
.error{
color: var(--danger-color);
}
.status-icon{
margin-right: 0.2rem;
}
.status-icon--error{
fill: var(--danger-color);
}
.status-icon--success{
fill: var(--success-color);
}
@media (any-hover: hover){
.icon:hover{
background: rgba(var(--text-color), 0.1);
}
}
</style>
<div class="outer-container">
<label part="input" class="input">
<slot name="icon"></slot>
<div class="container">
<input type="text"/>
<div part="placeholder" class="label"></div>
</div>
<svg class="icon clear hide" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-11.414L9.172 7.757 7.757 9.172 10.586 12l-2.829 2.828 1.415 1.415L12 13.414l2.828 2.829 1.415-1.415L13.414 12l2.829-2.828-1.415-1.415L12 10.586z"/></svg>
</label>
<p class="feedback-text"></p>
</div>
`;
customElements.define('sm-input',
class extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: 'open'
}).append(smInput.content.cloneNode(true));
this.inputParent = this.shadowRoot.querySelector('.input');
this.input = this.shadowRoot.querySelector('input');
this.clearBtn = this.shadowRoot.querySelector('.clear');
this.label = this.shadowRoot.querySelector('.label');
this.feedbackText = this.shadowRoot.querySelector('.feedback-text');
this.outerContainer = this.shadowRoot.querySelector('.outer-container');
this._helperText = '';
this._errorText = '';
this.isRequired = false;
this.hideRequired = false;
this.validationFunction = undefined;
this.reflectedAttributes = ['value', 'required', 'disabled', 'type', 'inputmode', 'readonly', 'min', 'max', 'pattern', 'minlength', 'maxlength', 'step'];
this.reset = this.reset.bind(this);
this.focusIn = this.focusIn.bind(this);
this.focusOut = this.focusOut.bind(this);
this.fireEvent = this.fireEvent.bind(this);
this.checkInput = this.checkInput.bind(this);
this.vibrate = this.vibrate.bind(this);
}
static get observedAttributes() {
return ['value', 'placeholder', 'required', 'disabled', 'type', 'inputmode', 'readonly', 'min', 'max', 'pattern', 'minlength', 'maxlength', 'step', 'helper-text', 'error-text', 'hiderequired'];
}
get value() {
return this.input.value;
}
set value(val) {
this.input.value = val;
this.checkInput();
this.fireEvent();
}
get placeholder() {
return this.getAttribute('placeholder');
}
set placeholder(val) {
this.setAttribute('placeholder', val);
}
get type() {
return this.getAttribute('type');
}
set type(val) {
this.setAttribute('type', val);
}
get validity() {
return this.input.validity;
}
get disabled() {
return this.hasAttribute('disabled');
}
set disabled(value) {
if (value)
this.inputParent.classList.add('disabled');
else
this.inputParent.classList.remove('disabled');
}
get readOnly() {
return this.hasAttribute('readonly');
}
set readOnly(value) {
if (value) {
this.setAttribute('readonly', '');
} else {
this.removeAttribute('readonly');
}
}
set customValidation(val) {
this.validationFunction = val;
}
set errorText(val) {
this._errorText = val;
}
set helperText(val) {
this._helperText = val;
}
get isValid() {
if (this.input.value !== '') {
const _isValid = this.input.checkValidity();
let _customValid = true;
if (this.validationFunction) {
_customValid = Boolean(this.validationFunction(this.input.value));
}
if (_isValid && _customValid) {
this.feedbackText.classList.remove('error');
this.feedbackText.classList.add('success');
this.feedbackText.textContent = '';
} else {
if (this._errorText) {
this.feedbackText.classList.add('error');
this.feedbackText.classList.remove('success');
this.feedbackText.innerHTML = `
<svg class="status-icon status-icon--error" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm0-8v6h2V7h-2z"/></svg>
${this._errorText}
`;
}
}
return (_isValid && _customValid);
}
}
reset() {
this.value = '';
}
focusIn() {
this.input.focus();
}
focusOut() {
this.input.blur();
}
fireEvent() {
let event = new Event('input', {
bubbles: true,
cancelable: true,
composed: true
});
this.dispatchEvent(event);
}
checkInput(e) {
if (!this.hasAttribute('readonly')) {
if (this.input.value.trim() !== '') {
this.clearBtn.classList.remove('hide');
} else {
this.clearBtn.classList.add('hide');
if (this.isRequired && !this.hideRequired) {
this.feedbackText.textContent = '*required';
}
}
}
if (!this.hasAttribute('placeholder') || this.getAttribute('placeholder').trim() === '') return;
if (this.input.value !== '') {
if (this.animate)
this.inputParent.classList.add('animate-label');
else
this.label.classList.add('hide');
} else {
if (this.animate)
this.inputParent.classList.remove('animate-label');
else
this.label.classList.remove('hide');
}
}
vibrate() {
this.outerContainer.animate([
{ transform: 'translateX(-1rem)' },
{ transform: 'translateX(1rem)' },
{ transform: 'translateX(-0.5rem)' },
{ transform: 'translateX(0.5rem)' },
{ transform: 'translateX(0)' },
], {
duration: 300,
easing: 'ease'
});
}
connectedCallback() {
this.animate = this.hasAttribute('animate');
this.setAttribute('role', 'textbox');
this.input.addEventListener('input', this.checkInput);
this.clearBtn.addEventListener('click', this.reset);
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
if (this.reflectedAttributes.includes(name)) {
if (this.hasAttribute(name)) {
this.input.setAttribute(name, this.getAttribute(name) ? this.getAttribute(name) : '');
}
else {
this.input.removeAttribute(name);
}
}
if (name === 'placeholder') {
this.label.textContent = newValue;
this.setAttribute('aria-label', newValue);
}
else if (this.hasAttribute('value')) {
this.checkInput();
}
else if (name === 'type') {
if (this.hasAttribute('type') && this.getAttribute('type') === 'number') {
this.input.setAttribute('inputmode', 'numeric');
}
}
else if (name === 'helper-text') {
this._helperText = this.getAttribute('helper-text');
}
else if (name === 'error-text') {
this._errorText = this.getAttribute('error-text');
}
else if (name === 'required') {
this.isRequired = this.hasAttribute('required');
if (this.isRequired && !this.hideRequired) {
this.feedbackText.textContent = '';
} else {
this.feedbackText.textContent = '*required';
}
if (this.isRequired) {
this.setAttribute('aria-required', 'true');
}
else {
this.setAttribute('aria-required', 'false');
}
}
else if (name === 'hiderequired') {
this.hideRequired = this.hasAttribute('hiderequired')
}
else if (name === 'readonly') {
if (this.hasAttribute('readonly')) {
this.inputParent.classList.add('readonly');
} else {
this.inputParent.classList.remove('readonly');
}
}
else if (name === 'disabled') {
if (this.hasAttribute('disabled')) {
this.inputParent.classList.add('disabled');
}
else {
this.inputParent.classList.remove('disabled');
}
}
}
}
disconnectedCallback() {
this.input.removeEventListener('input', this.checkInput);
this.clearBtn.removeEventListener('click', this.reset);
}
})
//popup
class Stack {
constructor() {
this.items = [];
}
push(element) {
this.items.push(element);
}
pop() {
if (this.items.length == 0)
return "Underflow";
return this.items.pop();
}
peek() {
return this.items[this.items.length - 1];
}
}
const popupStack = new Stack();
const smPopup = document.createElement('template');
smPopup.innerHTML = `
<style>
*{
padding: 0;
margin: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
:host{
position: fixed;
display: -ms-grid;
display: grid;
z-index: 10;
--width: 100%;
--height: auto;
--min-width: auto;
--min-height: auto;
--backdrop-background: rgba(0, 0, 0, 0.6);
--border-radius: 0.8rem 0.8rem 0 0;
}
.popup-container{
display: -ms-grid;
display: grid;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
place-items: center;
z-index: 10;
touch-action: none;
}
:host(.stacked) .popup{
-webkit-transform: scale(0.9) translateY(-2rem) !important;
transform: scale(0.9) translateY(-2rem) !important;
}
.background{
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
pointer-events: none;
background: var(--backdrop-background);
-webkit-transition: opacity 0.3s;
-o-transition: opacity 0.3s;
transition: opacity 0.3s;
}
.popup{
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
flex-direction: column;
position: relative;
-ms-flex-item-align: end;
align-self: flex-end;
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
width: var(--width);
min-width: var(--min-width);
height: var(--height);
min-height: var(--min-height);
max-height: 90vh;
border-radius: var(--border-radius);
background: rgba(var(--background-color, (255,255,255)), 1);
-webkit-box-shadow: 0 -1rem 2rem #00000020;
box-shadow: 0 -1rem 2rem #00000020;
}
.container-header{
display: -webkit-box;
display: flex;
width: 100%;
touch-action: none;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.popup-top{
display: -webkit-box;
display: flex;
width: 100%;
}
.popup-body{
display: -webkit-box;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
width: 100%;
padding: var(--body-padding, 1.5rem);
overflow-y: auto;
}
.hide{
display:none;
}
@media screen and (min-width: 640px){
:host{
--border-radius: 0.5rem;
}
.popup{
-ms-flex-item-align: center;
-ms-grid-row-align: center;
align-self: center;
border-radius: var(--border-radius);
height: var(--height);
-webkit-box-shadow: 0 3rem 2rem -0.5rem #00000040;
box-shadow: 0 3rem 2rem -0.5rem #00000040;
}
}
@media screen and (max-width: 640px){
.popup-top{
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
flex-direction: column;
-webkit-box-align: center;
align-items: center;
}
.handle{
height: 0.3rem;
width: 2rem;
background: rgba(var(--text-color, (17,17,17)), .4);
border-radius: 1rem;
margin: 0.5rem 0;
}
}
@media (any-hover: hover){
::-webkit-scrollbar{
width: 0.5rem;
}
::-webkit-scrollbar-thumb{
background: rgba(var(--text-color, (17,17,17)), 0.3);
border-radius: 1rem;
&:hover{
background: rgba(var(--text-color, (17,17,17))), 0.5);
}
}
}
</style>
<div class="popup-container hide" role="dialog">
<div part="background" class="background"></div>
<div part="popup" class="popup">
<div part="popup-header" class="popup-top">
<div class="handle"></div>
<slot name="header"></slot>
</div>
<div part="popup-body" class="popup-body">
<slot></slot>
</div>
</div>
</div>
`;
customElements.define('sm-popup', class extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: 'open'
}).append(smPopup.content.cloneNode(true));
this.allowClosing = false;
this.isOpen = false;
this.pinned = false;
this.offset = 0;
this.touchStartY = 0;
this.touchEndY = 0;
this.touchStartTime = 0;
this.touchEndTime = 0;
this.touchEndAnimation = undefined;
this.focusable
this.autoFocus
this.mutationObserver
this.popupContainer = this.shadowRoot.querySelector('.popup-container');
this.backdrop = this.shadowRoot.querySelector('.background');
this.dialogBox = this.shadowRoot.querySelector('.popup');
this.popupBodySlot = this.shadowRoot.querySelector('.popup-body slot');
this.popupHeader = this.shadowRoot.querySelector('.popup-top');
this.resumeScrolling = this.resumeScrolling.bind(this);
this.setStateOpen = this.setStateOpen.bind(this);
this.show = this.show.bind(this);
this.hide = this.hide.bind(this);
this.handleTouchStart = this.handleTouchStart.bind(this);
this.handleTouchMove = this.handleTouchMove.bind(this);
this.handleTouchEnd = this.handleTouchEnd.bind(this);
this.detectFocus = this.detectFocus.bind(this);
}
static get observedAttributes() {
return ['open'];
}
get open() {
return this.isOpen;
}
animateTo(element, keyframes, options) {
const anime = element.animate(keyframes, { ...options, fill: 'both' })
anime.finished.then(() => {
anime.commitStyles()
anime.cancel()
})
return anime
}
resumeScrolling() {
const scrollY = document.body.style.top;
window.scrollTo(0, parseInt(scrollY || '0') * -1);
document.body.style.overflow = '';
document.body.style.top = 'initial';
}
setStateOpen() {
if (!this.isOpen || this.offset) {
const animOptions = {
duration: 300,
easing: 'ease'
}
const initialAnimation = (window.innerWidth > 640) ? 'scale(1.1)' : `translateY(${this.offset ? `${this.offset}px` : '100%'})`
this.animateTo(this.dialogBox, [
{
opacity: this.offset ? 1 : 0,
transform: initialAnimation
},
{
opacity: 1,
transform: 'none'
},
], animOptions)
}
}
show(options = {}) {
const { pinned = false } = options;
if (!this.isOpen) {
const animOptions = {
duration: 300,
easing: 'ease'
}
popupStack.push({
popup: this,
permission: pinned
});
if (popupStack.items.length > 1) {
this.animateTo(popupStack.items[popupStack.items.length - 2].popup.shadowRoot.querySelector('.popup'), [
{ transform: 'none' },
{ transform: (window.innerWidth > 640) ? 'scale(0.95)' : 'translateY(-1.5rem)' },
], animOptions)
}
this.popupContainer.classList.remove('hide');
if (!this.offset)
this.backdrop.animate([
{ opacity: 0 },
{ opacity: 1 },
], animOptions)
this.setStateOpen()
this.dispatchEvent(
new CustomEvent("popupopened", {
bubbles: true,
detail: {
popup: this,
}
})
);
this.pinned = pinned;
this.isOpen = true;
document.body.style.overflow = 'hidden';
document.body.style.top = `-${window.scrollY}px`;
const elementToFocus = this.autoFocus || this.focusable[0];
elementToFocus.tagName.includes('SM-') ? elementToFocus.focusIn() : elementToFocus.focus();
if (!this.hasAttribute('open'))
this.setAttribute('open', '');
}
}
hide() {
const animOptions = {
duration: 150,
easing: 'ease'
}
this.backdrop.animate([
{ opacity: 1 },
{ opacity: 0 }
], animOptions)
this.animateTo(this.dialogBox, [
{
opacity: 1,
transform: (window.innerWidth > 640) ? 'none' : `translateY(${this.offset ? `${this.offset}px` : '0'})`
},
{
opacity: 0,
transform: (window.innerWidth > 640) ? 'scale(1.1)' : 'translateY(100%)'
},
], animOptions).finished
.finally(() => {
this.popupContainer.classList.add('hide');
this.dialogBox.style = ''
this.removeAttribute('open');
if (this.forms.length) {
this.forms.forEach(form => form.reset());
}
this.dispatchEvent(
new CustomEvent("popupclosed", {
bubbles: true,
detail: {
popup: this,
}
})
);
this.isOpen = false;
})
popupStack.pop();
if (popupStack.items.length) {
this.animateTo(popupStack.items[popupStack.items.length - 1].popup.shadowRoot.querySelector('.popup'), [
{ transform: (window.innerWidth > 640) ? 'scale(0.95)' : 'translateY(-1.5rem)' },
{ transform: 'none' },
], animOptions)
} else {
this.resumeScrolling();
}
}
handleTouchStart(e) {
this.offset = 0
this.popupHeader.addEventListener('touchmove', this.handleTouchMove, { passive: true });
this.popupHeader.addEventListener('touchend', this.handleTouchEnd, { passive: true });
this.touchStartY = e.changedTouches[0].clientY;
this.touchStartTime = e.timeStamp;
}
handleTouchMove(e) {
if (this.touchStartY < e.changedTouches[0].clientY) {
this.offset = e.changedTouches[0].clientY - this.touchStartY;
this.touchEndAnimation = window.requestAnimationFrame(() => {
this.dialogBox.style.transform = `translateY(${this.offset}px)`;
});
}
}
handleTouchEnd(e) {
this.touchEndTime = e.timeStamp;
cancelAnimationFrame(this.touchEndAnimation);
this.touchEndY = e.changedTouches[0].clientY;
this.threshold = this.dialogBox.getBoundingClientRect().height * 0.3;
if (this.touchEndTime - this.touchStartTime > 200) {
if (this.touchEndY - this.touchStartY > this.threshold) {
if (this.pinned) {
this.setStateOpen();
return;
} else
this.hide();
} else {
this.setStateOpen();
}
} else {
if (this.touchEndY > this.touchStartY)
if (this.pinned) {
this.setStateOpen();
return;
}
else
this.hide();
}
this.popupHeader.removeEventListener('touchmove', this.handleTouchMove, { passive: true });
this.popupHeader.removeEventListener('touchend', this.handleTouchEnd, { passive: true });
}
detectFocus(e) {
if (e.key === 'Tab') {
const lastElement = this.focusable[this.focusable.length - 1];
const firstElement = this.focusable[0];
if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault();
lastElement.tagName.includes('SM-') ? lastElement.focusIn() : lastElement.focus();
} else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault();
firstElement.tagName.includes('SM-') ? firstElement.focusIn() : firstElement.focus();
}
}
}
updateFocusableList() {
this.focusable = this.querySelectorAll('sm-button:not([disabled]), button:not([disabled]), [href], sm-input, input:not([readonly]), sm-select, select, sm-checkbox, sm-textarea, textarea, [tabindex]:not([tabindex="-1"])')
this.autoFocus = this.querySelector('[autofocus]')
}
connectedCallback() {
this.popupBodySlot.addEventListener('slotchange', () => {
this.forms = this.querySelectorAll('sm-form');
this.updateFocusableList()
});
this.popupContainer.addEventListener('mousedown', e => {
if (e.target === this.popupContainer && !this.pinned) {
if (this.pinned) {
this.setStateOpen();
} else
this.hide();
}
});
const resizeObserver = new ResizeObserver(entries => {
for (let entry of entries) {
if (entry.contentBoxSize) {
// Firefox implements `contentBoxSize` as a single content rect, rather than an array
const contentBoxSize = Array.isArray(entry.contentBoxSize) ? entry.contentBoxSize[0] : entry.contentBoxSize;
this.threshold = contentBoxSize.blockSize.height * 0.3;
} else {
this.threshold = entry.contentRect.height * 0.3;
}
}
});
resizeObserver.observe(this);
this.mutationObserver = new MutationObserver(entries => {
this.updateFocusableList()
})
this.mutationObserver.observe(this, { attributes: true, childList: true, subtree: true })
this.addEventListener('keydown', this.detectFocus);
this.popupHeader.addEventListener('touchstart', this.handleTouchStart, { passive: true });
}
disconnectedCallback() {
this.removeEventListener('keydown', this.detectFocus);
resizeObserver.unobserve();
this.mutationObserver.disconnect()
this.popupHeader.removeEventListener('touchstart', this.handleTouchStart, { passive: true });
}
attributeChangedCallback(name) {
if (name === 'open') {
if (this.hasAttribute('open')) {
this.show();
}
}
}
});
const smCopy = document.createElement('template');
smCopy.innerHTML = `
<style>
*{
padding: 0;
margin: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
:host{
display: -webkit-box;
display: flex;
--accent-color: #4d2588;
--text-color: 17, 17, 17;
--background-color: 255, 255, 255;
--padding: 0;
--background-color: inherit;
--button-background-color: rgba(var(--text-color), 0.2);
--button-border-radius: 0.3rem;
}
.copy{
display: grid;
width: 100%;
gap: 0.5rem;
padding: var(--padding);
align-items: center;
grid-template-columns: minmax(0, 1fr) auto;
}
:host(:not([clip-text])) .copy-content{
overflow-wrap: break-word;
word-wrap: break-word;
}
:host([clip-text]) .copy-content{
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.copy-button{
display: inline-flex;
justify-content: center;
cursor: pointer;
border: none;
padding: 0.4rem;
background-color: inherit;
border-radius: var(--button-border-radius);
}
.copy-button:active{
background-color: var(--button-background-color);
}
.icon{
height: 1.2rem;
width: 1.2rem;
fill: rgba(var(--text-color), 0.8);
}
@media (any-hover: hover){
.copy:hover .copy-button{
opacity: 1;
}
.copy-button{
opacity: 0.6;
}
.copy-button:hover{
background-color: var(--button-background-color);
}
}
</style>
<section class="copy">
<p class="copy-content"></p>
<button part="button" class="copy-button" title="copy">
<slot name="copy-icon">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M7 6V3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1h-3v3c0 .552-.45 1-1.007 1H4.007A1.001 1.001 0 0 1 3 21l.003-14c0-.552.45-1 1.007-1H7zM5.003 8L5 20h10V8H5.003zM9 6h8v10h2V4H9v2z"/></svg>
</slot>
</button>
</section>
`;
customElements.define('sm-copy',
class extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: 'open'
}).append(smCopy.content.cloneNode(true));
this.copyContent = this.shadowRoot.querySelector('.copy-content');
this.copyButton = this.shadowRoot.querySelector('.copy-button');
this.copy = this.copy.bind(this);
}
static get observedAttributes() {
return ['value'];
}
set value(val) {
this.setAttribute('value', val);
}
get value() {
return this.getAttribute('value');
}
fireEvent() {
this.dispatchEvent(
new CustomEvent('copy', {
composed: true,
bubbles: true,
cancelable: true,
})
);
}
copy() {
navigator.clipboard.writeText(this.copyContent.textContent)
.then(res => this.fireEvent())
.catch(err => console.error(err));
}
connectedCallback() {
this.copyButton.addEventListener('click', this.copy);
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'value') {
this.copyContent.textContent = newValue;
}
}
disconnectedCallback() {
this.copyButton.removeEventListener('click', this.copy);
}
});
const smForm = document.createElement('template');
smForm.innerHTML = `
<style>
*{
padding: 0;
margin: 0;
box-sizing: border-box;
}
:host{
display: flex;
width: 100%;
}
form{
display: grid;
gap: var(--gap, 1.5rem);
width: 100%;
}
</style>
<form part="form" onsubmit="return false">
<slot></slot>
</form>
`;
customElements.define('sm-form', class extends HTMLElement {
constructor() {
super()
this.attachShadow({
mode: 'open'
}).append(smForm.content.cloneNode(true))
this.form = this.shadowRoot.querySelector('form');
this.formElements
this.requiredElements
this.submitButton
this.resetButton
this.allRequiredValid = false;
this.debounce = this.debounce.bind(this)
this._checkValidity = this._checkValidity.bind(this)
this.handleKeydown = this.handleKeydown.bind(this)
this.reset = this.reset.bind(this)
this.elementsChanged = this.elementsChanged.bind(this)
}
debounce(callback, wait) {
let timeoutId = null;
return (...args) => {
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => {
callback.apply(null, args);
}, wait);
};
}
_checkValidity() {
this.allRequiredValid = this.requiredElements.every(elem => elem.isValid)
if (!this.submitButton) return;
if (this.allRequiredValid) {
this.submitButton.disabled = false;
}
else {
this.submitButton.disabled = true;
}
}
handleKeydown(e) {
if (e.key === 'Enter' && e.target.tagName !== 'SM-TEXTAREA') {
if (this.allRequiredValid) {
if (this.submitButton && this.submitButton.tagName === 'SM-BUTTON') {
this.submitButton.click()
}
this.dispatchEvent(new CustomEvent('submit', {
bubbles: true,
composed: true,
}))
}
else {
this.requiredElements.find(elem => !elem.isValid).vibrate()
}
}
}
reset() {
this.formElements.forEach(elem => elem.reset())
}
elementsChanged() {
this.formElements = [...this.querySelectorAll('sm-input, sm-textarea, sm-checkbox, tags-input, file-input, sm-switch, sm-radio')]
this.requiredElements = this.formElements.filter(elem => elem.hasAttribute('required'));
this.submitButton = this.querySelector('[variant="primary"], [type="submit"]');
this.resetButton = this.querySelector('[type="reset"]');
if (this.resetButton) {
this.resetButton.addEventListener('click', this.reset);
}
this._checkValidity()
}
connectedCallback() {
const slot = this.shadowRoot.querySelector('slot')
slot.addEventListener('slotchange', this.elementsChanged)
this.addEventListener('input', this.debounce(this._checkValidity, 100));
this.addEventListener('keydown', this.debounce(this.handleKeydown, 100));
}
disconnectedCallback() {
this.removeEventListener('input', this.debounce(this._checkValidity, 100));
this.removeEventListener('keydown', this.debounce(this.handleKeydown, 100));
}
})
</script>
</template>
</body>
</html>